2023-05-12 20:01:37 +02:00
|
|
|
use calloop::LoopHandle;
|
2024-09-10 19:38:48 +02:00
|
|
|
use focus::target::WindowGroup;
|
2025-02-13 21:05:36 +01:00
|
|
|
use grabs::{MenuAlignment, SeatMoveGrabState};
|
2023-10-25 19:40:26 +02:00
|
|
|
use indexmap::IndexMap;
|
2024-08-14 21:56:20 +03:00
|
|
|
use layout::TilingExceptions;
|
2023-06-22 21:30:45 +02:00
|
|
|
use std::{
|
|
|
|
|
collections::HashMap,
|
2024-09-13 19:22:57 +02:00
|
|
|
sync::{atomic::Ordering, Mutex},
|
2025-01-31 14:13:33 -08:00
|
|
|
thread,
|
2023-07-06 00:03:26 +02:00
|
|
|
time::{Duration, Instant},
|
2023-06-22 21:30:45 +02:00
|
|
|
};
|
2023-07-05 23:48:10 +02:00
|
|
|
use wayland_backend::server::ClientId;
|
2022-07-04 15:28:03 +02:00
|
|
|
|
2025-01-31 14:13:33 -08:00
|
|
|
use crate::wayland::{
|
|
|
|
|
handlers::data_device,
|
|
|
|
|
protocols::workspace::{State as WState, WorkspaceCapabilities},
|
|
|
|
|
};
|
2024-06-07 19:51:47 +02:00
|
|
|
use cosmic_comp_config::{
|
2025-01-31 14:13:33 -08:00
|
|
|
workspace::{PinnedWorkspace, WorkspaceLayout, WorkspaceMode},
|
2025-02-13 21:08:31 +01:00
|
|
|
TileBehavior, ZoomConfig, ZoomMovement,
|
2024-06-07 19:51:47 +02:00
|
|
|
};
|
2025-01-31 14:13:33 -08:00
|
|
|
use cosmic_config::ConfigSet;
|
2025-02-19 14:07:51 -08:00
|
|
|
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};
|
2024-09-09 20:02:12 +02:00
|
|
|
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
|
2023-07-11 17:12:56 +02:00
|
|
|
use keyframe::{ease, functions::EaseInOutCubic};
|
2022-07-04 15:28:03 +02:00
|
|
|
use smithay::{
|
2024-06-07 19:44:59 +02:00
|
|
|
backend::{input::TouchSlot, renderer::element::RenderElementStates},
|
2023-01-23 22:56:42 +01:00
|
|
|
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,
|
|
|
|
|
},
|
2024-04-19 14:57:17 +02:00
|
|
|
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType,
|
2023-01-23 22:56:42 +01:00
|
|
|
},
|
2023-01-18 20:23:41 +01:00
|
|
|
input::{
|
2024-09-13 19:22:57 +02:00
|
|
|
pointer::{
|
|
|
|
|
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
|
|
|
|
|
},
|
2023-01-18 20:23:41 +01:00
|
|
|
Seat,
|
|
|
|
|
},
|
2025-02-27 18:14:35 +01:00
|
|
|
output::{Output, WeakOutput},
|
2023-11-14 13:13:42 -08:00
|
|
|
reexports::{
|
2025-01-31 14:13:33 -08:00
|
|
|
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},
|
2023-11-14 13:13:42 -08:00
|
|
|
},
|
2024-04-10 15:49:08 +02:00
|
|
|
utils::{IsAlive, Logical, Point, Rectangle, Serial, Size},
|
2022-03-30 22:00:44 +02:00
|
|
|
wayland::{
|
2024-09-13 19:22:57 +02:00
|
|
|
compositor::{with_states, SurfaceAttributes},
|
2023-01-18 20:23:41 +01:00
|
|
|
seat::WaylandFocus,
|
2023-11-14 13:13:42 -08:00
|
|
|
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,
|
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
|
|
|
};
|
2025-01-31 14:13:33 -08:00
|
|
|
use tracing::error;
|
2022-05-02 17:43:58 +02:00
|
|
|
|
2022-07-04 15:28:03 +02:00
|
|
|
use crate::{
|
2024-03-07 13:14:53 -06:00
|
|
|
backend::render::animations::spring::{Spring, SpringParams},
|
2024-04-03 16:02:27 +02:00
|
|
|
config::Config,
|
2024-07-12 16:03:17 -07:00
|
|
|
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,
|
2024-07-12 16:03:17 -07:00
|
|
|
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::{
|
2024-09-04 11:13:59 -05:00
|
|
|
WorkspaceGroupHandle, WorkspaceHandle, WorkspaceState, WorkspaceUpdateGuard,
|
2023-11-07 18:46:25 +01:00
|
|
|
},
|
2022-07-04 15:28:03 +02: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;
|
2022-10-27 20:40:55 +02:00
|
|
|
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;
|
2023-01-16 20:31:43 +01:00
|
|
|
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},
|
2023-08-11 18:15:22 +02:00
|
|
|
swap_indicator::{swap_indicator, SwapIndicator},
|
2023-12-20 20:45:47 +00:00
|
|
|
CosmicWindow, MaximizedState,
|
2023-07-05 23:57:38 +02:00
|
|
|
},
|
2024-04-03 16:02:27 +02:00
|
|
|
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
|
2023-12-20 20:29:44 +00:00
|
|
|
grabs::{
|
2024-04-04 19:17:03 -07:00
|
|
|
tab_items, window_items, GrabStartData, Item, MenuGrab, MoveGrab, ReleaseMode, ResizeEdge,
|
|
|
|
|
ResizeGrab,
|
2023-12-20 20:29:44 +00:00
|
|
|
},
|
2023-12-07 19:53:41 +00:00
|
|
|
layout::{
|
2023-12-20 20:29:44 +00:00
|
|
|
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);
|
2024-03-07 13:14:53 -06:00
|
|
|
const GESTURE_MAX_LENGTH: f64 = 150.0;
|
|
|
|
|
const GESTURE_POSITION_THRESHOLD: f64 = 0.5;
|
|
|
|
|
const GESTURE_VELOCITY_THRESHOLD: f64 = 0.02;
|
2024-06-28 12:24:09 +02:00
|
|
|
const MOVE_GRAB_Y_OFFSET: f64 = 16.;
|
2025-03-13 12:50:02 -07:00
|
|
|
const ACTIVATION_TOKEN_EXPIRE_TIME: Duration = Duration::from_secs(5);
|
2023-07-06 00:03:26 +02:00
|
|
|
|
2023-07-17 21:11:45 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum Trigger {
|
2024-04-03 16:02:27 +02:00
|
|
|
KeyboardSwap(shortcuts::Binding, NodeDesc),
|
|
|
|
|
KeyboardMove(shortcuts::Modifiers),
|
2023-07-17 21:11:45 +02:00
|
|
|
Pointer(u32),
|
2024-04-04 19:17:03 -07:00
|
|
|
Touch(TouchSlot),
|
2023-07-17 21:11:45 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-19 19:44:57 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum OverviewMode {
|
|
|
|
|
None,
|
2023-07-17 21:11:45 +02:00
|
|
|
Started(Trigger, Instant),
|
2024-07-15 16:30:54 +02:00
|
|
|
Active(Trigger),
|
2023-08-11 18:15:22 +02:00
|
|
|
Ended(Option<Trigger>, Instant),
|
2023-05-19 19:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-06 00:03:26 +02:00
|
|
|
impl OverviewMode {
|
|
|
|
|
pub fn alpha(&self) -> Option<f32> {
|
|
|
|
|
match self {
|
|
|
|
|
OverviewMode::Started(_, start) => {
|
2023-07-11 17:12:56 +02:00
|
|
|
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
|
|
|
}
|
2024-07-15 16:30:54 +02:00
|
|
|
OverviewMode::Active(_) => Some(1.0),
|
2023-08-11 18:15:22 +02:00
|
|
|
OverviewMode::Ended(_, end) => {
|
2023-07-11 17:12:56 +02:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-15 16:30:54 +02:00
|
|
|
|
2025-04-15 16:15:24 +02:00
|
|
|
pub fn is_active(&self) -> bool {
|
|
|
|
|
matches!(self, OverviewMode::Started(_, _) | OverviewMode::Active(_))
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-15 16:30:54 +02:00
|
|
|
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),
|
2024-07-15 16:30:54 +02:00
|
|
|
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, _) => {
|
2023-07-11 17:12:56 +02:00
|
|
|
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
|
|
|
}
|
2024-07-15 16:30:54 +02:00
|
|
|
ResizeMode::Active(_, _) => Some(1.0),
|
2023-07-06 00:03:26 +02:00
|
|
|
ResizeMode::Ended(end, _) => {
|
2023-07-11 17:12:56 +02:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-15 16:30:54 +02:00
|
|
|
|
|
|
|
|
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 {
|
2024-02-21 13:24:56 -08:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 14:33:11 +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)]
|
2022-07-04 15:28:03 +02:00
|
|
|
pub struct Shell {
|
2023-10-25 19:40:26 +02:00
|
|
|
pub workspaces: Workspaces,
|
|
|
|
|
|
2025-02-04 14:33:11 +01:00
|
|
|
pub pending_windows: Vec<PendingWindow>,
|
|
|
|
|
pub pending_layers: Vec<PendingLayer>,
|
2023-11-07 18:46:25 +01:00
|
|
|
pub pending_activations: HashMap<ActivationKey, ActivationContext>,
|
2023-01-27 19:51:23 +01:00
|
|
|
pub override_redirect_windows: Vec<X11Surface>,
|
2023-11-14 13:13:42 -08:00
|
|
|
pub session_lock: Option<SessionLock>,
|
2024-04-05 13:53:35 +02:00
|
|
|
pub seats: Seats,
|
2025-02-27 18:14:35 +01:00
|
|
|
pub previous_workspace_idx: Option<(Serial, WeakOutput, usize)>,
|
2022-07-04 15:28:03 +02:00
|
|
|
|
2023-10-10 13:55:34 -04:00
|
|
|
theme: cosmic::Theme,
|
2024-06-07 19:51:47 +02:00
|
|
|
pub active_hint: bool,
|
2023-05-19 19:44:57 +02:00
|
|
|
overview_mode: OverviewMode,
|
2023-08-11 18:15:22 +02:00
|
|
|
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>,
|
2025-01-23 15:45:00 +01:00
|
|
|
zoom_state: Option<ZoomState>,
|
2024-08-14 21:56:20 +03:00
|
|
|
tiling_exceptions: TilingExceptions,
|
2024-06-07 19:51:47 +02:00
|
|
|
|
|
|
|
|
#[cfg(feature = "debug")]
|
2024-06-19 16:36:02 +02:00
|
|
|
pub debug_active: bool,
|
2022-03-24 20:32:31 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-14 13:13:42 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct SessionLock {
|
|
|
|
|
pub ext_session_lock: ExtSessionLockV1,
|
|
|
|
|
pub surfaces: HashMap<Output, LockSurface>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 13:14:53 -06:00
|
|
|
#[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())
|
|
|
|
|
}
|
2024-03-28 12:34:46 +01:00
|
|
|
|
|
|
|
|
pub fn is_animating(&self) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
self,
|
|
|
|
|
WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd(_, _)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-03-07 13:14:53 -06:00
|
|
|
}
|
|
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct WorkspaceSet {
|
2024-03-07 13:14:53 -06:00
|
|
|
previously_active: Option<(usize, WorkspaceDelta)>,
|
2024-03-21 12:53:52 +01:00
|
|
|
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,
|
2023-10-10 13:55:34 -04:00
|
|
|
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,
|
2023-10-10 13:55:34 -04:00
|
|
|
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
|
|
|
|
|
},
|
2025-02-11 17:00:49 -08:00
|
|
|
// 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);
|
|
|
|
|
}
|
2025-02-21 12:34:00 -08:00
|
|
|
state.set_workspace_capabilities(
|
|
|
|
|
&workspace_handle,
|
2025-01-31 14:13:33 -08:00
|
|
|
WorkspaceCapabilities::Activate
|
|
|
|
|
| WorkspaceCapabilities::SetTilingState
|
|
|
|
|
| WorkspaceCapabilities::Pin
|
|
|
|
|
| WorkspaceCapabilities::Move,
|
2025-02-21 12:34:00 -08:00
|
|
|
);
|
2023-10-10 13:55:34 -04:00
|
|
|
Workspace::new(workspace_handle, output.clone(), tiling, theme.clone())
|
2022-09-28 12:01:29 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-31 14:13:33 -08: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
|
2023-11-20 21:19:04 +01:00
|
|
|
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
|
2023-11-20 21:19:04 +01:00
|
|
|
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
|
|
|
*/
|
2023-11-20 21:19:04 +01:00
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
impl WorkspaceSet {
|
2022-11-22 17:09:49 +01:00
|
|
|
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,
|
2023-10-10 13:55:34 -04:00
|
|
|
theme: cosmic::Theme,
|
2022-11-22 17:09:49 +01:00
|
|
|
) -> WorkspaceSet {
|
2023-10-25 18:14:28 +02:00
|
|
|
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 {
|
2023-05-22 20:19:11 +02:00
|
|
|
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,
|
2023-10-10 13:55:34 -04:00
|
|
|
theme,
|
2023-12-20 19:51:11 +00:00
|
|
|
sticky_layer,
|
2024-02-08 20:28:59 +01:00
|
|
|
minimized_windows: Vec::new(),
|
2024-10-11 13:26:11 -07:00
|
|
|
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,
|
2024-03-07 13:14:53 -06:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 17:00:45 -07:00
|
|
|
// 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);
|
2024-05-03 17:00:45 -07:00
|
|
|
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 {
|
2024-08-22 16:47:22 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
2024-03-07 13:14:53 -06:00
|
|
|
return Ok(true);
|
|
|
|
|
}
|
2023-05-25 00:10:24 +02:00
|
|
|
Ok(false)
|
2022-09-28 12:01:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 13:14:53 -06: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)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-26 12:08:46 -07:00
|
|
|
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 {
|
2025-03-26 12:08:46 -07:00
|
|
|
workspace.set_output(new_output, explicit);
|
2023-10-25 19:40:26 +02:00
|
|
|
}
|
|
|
|
|
self.output = new_output.clone();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 12:50:02 -07:00
|
|
|
fn refresh(&mut self) {
|
2023-05-22 20:19:11 +02:00
|
|
|
if let Some((_, start)) = self.previously_active {
|
2024-03-07 13:14:53 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
2023-05-22 20:19:11 +02:00
|
|
|
}
|
2023-09-14 15:30:08 +02:00
|
|
|
} else {
|
2025-03-13 12:50:02 -07:00
|
|
|
self.workspaces[self.active].refresh();
|
2023-09-14 15:30:08 +02:00
|
|
|
}
|
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>) {
|
2023-10-11 19:06:13 +02:00
|
|
|
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,
|
2023-10-10 13:55:34 -04:00
|
|
|
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
|
|
|
|
2025-03-13 12:50:02 -07: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
|
2025-01-31 14:13:33 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 13:47:17 -08:00
|
|
|
// remove other empty workspaces
|
2022-09-28 12:01:29 +02:00
|
|
|
let len = self.workspaces.len();
|
2025-01-30 13:47:17 -08:00
|
|
|
let kept: Vec<bool> = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.iter()
|
|
|
|
|
.enumerate()
|
|
|
|
|
.map(|(i, workspace)| {
|
2025-01-31 14:13:33 -08:00
|
|
|
let previous_is_empty = i > 0
|
|
|
|
|
&& self
|
|
|
|
|
.workspaces
|
|
|
|
|
.get(i - 1)
|
|
|
|
|
.map_or(false, |w| w.is_empty() && !w.pinned);
|
2025-03-13 12:50:02 -07:00
|
|
|
let keep = if workspace.can_auto_remove(xdg_activation_state) {
|
2025-01-30 13:47:17 -08:00
|
|
|
// 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
|
|
|
|
2025-01-30 13:47:17 -08:00
|
|
|
let mut iter = kept.iter();
|
2022-09-28 12:01:29 +02:00
|
|
|
self.workspaces.retain(|_| *iter.next().unwrap());
|
2025-01-30 13:47:17 -08:00
|
|
|
self.active -= kept
|
2022-10-25 17:52:18 +02:00
|
|
|
.iter()
|
|
|
|
|
.take(self.active + 1)
|
2025-01-30 13:47:17 -08:00
|
|
|
.filter(|kept| !**kept)
|
2022-10-25 17:52:18 +02:00
|
|
|
.count();
|
2022-11-08 09:38:43 +01:00
|
|
|
|
2025-01-30 13:47:17 -08:00
|
|
|
if kept.iter().any(|val| *val == false) {
|
2025-03-14 10:50:57 -07:00
|
|
|
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
|
|
|
}
|
2025-03-14 13:43:05 -07: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>,
|
2024-06-07 19:51:47 +02:00
|
|
|
pub layout: WorkspaceLayout,
|
2023-10-25 19:40:26 +02:00
|
|
|
mode: WorkspaceMode,
|
2024-02-08 14:25:18 -05:00
|
|
|
autotile: bool,
|
|
|
|
|
autotile_behavior: TileBehavior,
|
2023-10-10 13:55:34 -04:00
|
|
|
theme: cosmic::Theme,
|
2025-01-31 14:13:33 -08:00
|
|
|
// Persisted workspace to add on first `output_add`
|
|
|
|
|
persisted_workspaces: Vec<PinnedWorkspace>,
|
2023-10-25 19:40:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Workspaces {
|
2023-10-10 13:55:34 -04:00
|
|
|
pub fn new(config: &Config, theme: cosmic::Theme) -> Workspaces {
|
2023-10-25 19:40:26 +02:00
|
|
|
Workspaces {
|
|
|
|
|
sets: IndexMap::new(),
|
|
|
|
|
backup_set: None,
|
2024-06-07 19:51:47 +02:00
|
|
|
layout: config.cosmic_conf.workspaces.workspace_layout,
|
2024-02-08 14:25:18 -05:00
|
|
|
mode: config.cosmic_conf.workspaces.workspace_mode,
|
|
|
|
|
autotile: config.cosmic_conf.autotile,
|
|
|
|
|
autotile_behavior: config.cosmic_conf.autotile_behavior,
|
2023-10-10 13:55:34 -04:00
|
|
|
theme,
|
2025-01-31 14:13:33 -08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 13:18:06 -07:00
|
|
|
let mut set = self
|
2023-10-11 19:06:13 +02:00
|
|
|
.backup_set
|
|
|
|
|
.take()
|
|
|
|
|
.map(|mut set| {
|
2025-03-26 12:08:46 -07:00
|
|
|
set.set_output(output, false);
|
2023-10-11 19:06:13 +02:00
|
|
|
set
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_else(|| {
|
2025-03-14 10:40:33 -07:00
|
|
|
WorkspaceSet::new(workspace_state, &output, self.autotile, self.theme.clone())
|
2023-10-11 19:06:13 +02:00
|
|
|
});
|
2023-10-25 19:40:26 +02:00
|
|
|
workspace_state.add_group_output(&set.group, &output);
|
|
|
|
|
|
2025-01-31 14:13:33 -08:00
|
|
|
// If this is the first output added, create workspaces for pinned workspaces from config
|
|
|
|
|
for pinned in std::mem::take(&mut self.persisted_workspaces) {
|
|
|
|
|
tracing::error!("pinned workspace: {:?}", pinned);
|
|
|
|
|
let workspace = create_workspace_from_pinned(
|
|
|
|
|
&pinned,
|
|
|
|
|
workspace_state,
|
|
|
|
|
output,
|
|
|
|
|
&set.group,
|
|
|
|
|
false,
|
|
|
|
|
self.theme.clone(),
|
|
|
|
|
);
|
|
|
|
|
set.workspaces.push(workspace);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 13:18:06 -07:00
|
|
|
// Remove workspaces that prefer this output from other sets
|
2025-03-14 13:43:05 -07:00
|
|
|
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<_>>();
|
2024-10-11 13:18:06 -07:00
|
|
|
|
|
|
|
|
// Add `moved_workspaces` to set, and update output and index of workspaces
|
|
|
|
|
for workspace in &mut moved_workspaces {
|
2025-04-17 14:51:59 -07:00
|
|
|
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
|
|
|
|
|
workspace_state.move_workspace_to_group(set.group, workspace.handle);
|
2024-10-11 13:18:06 -07:00
|
|
|
}
|
|
|
|
|
set.workspaces.extend(moved_workspaces);
|
2024-10-11 13:26:11 -07:00
|
|
|
if set.workspaces.is_empty() {
|
|
|
|
|
set.add_empty_workspace(workspace_state);
|
|
|
|
|
}
|
2025-03-14 10:50:57 -07:00
|
|
|
set.update_workspace_idxs(workspace_state);
|
2024-10-11 13:18:06 -07:00
|
|
|
for (i, workspace) in set.workspaces.iter_mut().enumerate() {
|
2025-03-26 12:08:46 -07:00
|
|
|
workspace.set_output(output, false);
|
2025-03-13 12:50:02 -07:00
|
|
|
workspace.refresh();
|
2024-10-11 13:18:06 -07:00
|
|
|
if i == set.active {
|
|
|
|
|
workspace_state.add_workspace_state(&workspace.handle, WState::Active);
|
2023-11-20 19:11:02 +01:00
|
|
|
}
|
2023-10-25 19:40:26 +02:00
|
|
|
}
|
2024-10-11 13:18:06 -07: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);
|
|
|
|
|
}
|
2024-12-13 16:23:09 +01:00
|
|
|
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;
|
shell: On `output_remove`, focus moved workspace instead of empty one
On `output_remove`, if the output a workspace is moved to had no
non-empty workspaces (or did, but had an empty one active), the empty
workspace would remain active, and the workspace that was active on the
removed output is no longer visible.
Instead, change the active workspace to the one that was active on the
removed output.
This addresses another edge case where hotplug results in an empty
workspace, followed by other non-empty workspaces, and generally seems
like a better experience.
This could be further restricted by only applying if `new_set` is not on
the active output (to not mess with what the user is interacting with,
even it's an empty workspace) or only if the old `set` was the active
output, etc. But it seems good without further restriction.
2025-03-11 12:49:56 -07:00
|
|
|
for (i, mut workspace) in set.workspaces.into_iter().enumerate() {
|
2025-03-13 12:50:02 -07:00
|
|
|
if workspace.can_auto_remove(xdg_activation_state) {
|
2024-10-11 12:33:09 -07:00
|
|
|
workspace_state.remove_workspace(workspace.handle);
|
|
|
|
|
} else {
|
|
|
|
|
// update workspace protocol state
|
2025-04-17 14:51:59 -07:00
|
|
|
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
|
|
|
|
|
workspace_state.move_workspace_to_group(workspace_group, workspace.handle);
|
2024-10-11 12:33:09 -07:00
|
|
|
|
|
|
|
|
// update mapping
|
2025-03-26 12:08:46 -07:00
|
|
|
workspace.set_output(&new_output, false);
|
2025-03-13 12:50:02 -07:00
|
|
|
workspace.refresh();
|
2024-10-11 12:33:09 -07:00
|
|
|
new_set.workspaces.push(workspace);
|
shell: On `output_remove`, focus moved workspace instead of empty one
On `output_remove`, if the output a workspace is moved to had no
non-empty workspaces (or did, but had an empty one active), the empty
workspace would remain active, and the workspace that was active on the
removed output is no longer visible.
Instead, change the active workspace to the one that was active on the
removed output.
This addresses another edge case where hotplug results in an empty
workspace, followed by other non-empty workspaces, and generally seems
like a better experience.
This could be further restricted by only applying if `new_set` is not on
the active output (to not mess with what the user is interacting with,
even it's an empty workspace) or only if the old `set` was the active
output, etc. But it seems good without further restriction.
2025-03-11 12:49:56 -07:00
|
|
|
|
|
|
|
|
// 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,
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-10-11 12:33:09 -07:00
|
|
|
}
|
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.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);
|
|
|
|
|
|
2023-10-11 19:06:13 +02:00
|
|
|
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
|
|
|
}
|
2023-10-11 19:06:13 +02:00
|
|
|
|
2024-01-05 18:33:16 +00:00
|
|
|
self.refresh(workspace_state, xdg_activation_state)
|
2022-09-28 12:01:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-26 12:08:46 -07:00
|
|
|
// Move workspace from one output to another, explicitly by the user
|
2023-11-20 21:19:47 +01:00
|
|
|
fn migrate_workspace(
|
|
|
|
|
&mut self,
|
|
|
|
|
from: &Output,
|
|
|
|
|
to: &Output,
|
|
|
|
|
handle: &WorkspaceHandle,
|
|
|
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
|
|
|
|
) {
|
|
|
|
|
if !self.sets.contains_key(to) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-14 13:43:05 -07:00
|
|
|
if let Some(mut workspace) = self
|
|
|
|
|
.sets
|
|
|
|
|
.get_mut(from)
|
|
|
|
|
.and_then(|set| set.remove_workspace(workspace_state, handle))
|
|
|
|
|
{
|
2023-11-20 21:19:47 +01:00
|
|
|
let new_set = self.sets.get_mut(to).unwrap();
|
2025-04-17 14:51:59 -07:00
|
|
|
workspace_state.remove_workspace_state(&workspace.handle, WState::Active);
|
|
|
|
|
workspace_state.move_workspace_to_group(new_set.group, workspace.handle);
|
2025-03-26 12:08:46 -07:00
|
|
|
workspace.set_output(to, true);
|
2025-03-13 12:50:02 -07:00
|
|
|
workspace.refresh();
|
2025-03-14 12:40:51 -07:00
|
|
|
new_set.workspaces.insert(new_set.active + 1, workspace);
|
|
|
|
|
new_set.update_workspace_idxs(workspace_state);
|
2023-11-20 21:19:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 14:13:33 -08:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-11 19:06:13 +02:00
|
|
|
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,
|
2023-10-11 19:06:13 +02:00
|
|
|
) {
|
2023-09-07 13:28:08 -07:00
|
|
|
let old_mode = self.mode;
|
2024-02-08 14:25:18 -05:00
|
|
|
self.mode = config.cosmic_conf.workspaces.workspace_mode;
|
2024-06-07 19:51:47 +02:00
|
|
|
self.layout = config.cosmic_conf.workspaces.workspace_layout;
|
2023-09-07 13:28:08 -07:00
|
|
|
|
2023-10-11 19:06:13 +02:00
|
|
|
if self.sets.len() <= 1 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-07 13:28:08 -07:00
|
|
|
match (old_mode, self.mode) {
|
2023-10-11 19:06:13 +02:00
|
|
|
(WorkspaceMode::Global, WorkspaceMode::OutputBound) => {
|
2023-10-25 18:14:28 +02:00
|
|
|
// We basically just unlink the existing spaces, so nothing needs to be updated
|
2023-10-11 19:06:13 +02:00
|
|
|
}
|
|
|
|
|
(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,
|
2023-10-25 18:14:28 +02:00
|
|
|
&set.group,
|
2023-10-11 19:06:13 +02:00
|
|
|
false,
|
2024-02-08 14:25:18 -05:00
|
|
|
config.cosmic_conf.autotile,
|
2023-10-10 13:55:34 -04:00
|
|
|
self.theme.clone(),
|
2023-10-11 19:06:13 +02:00
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-10-25 18:14:28 +02:00
|
|
|
// Otherwise we are fine
|
2023-10-11 19:06:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
};
|
|
|
|
|
|
2024-01-05 18:33:16 +00:00
|
|
|
self.refresh(workspace_state, xdg_activation_state)
|
2023-10-11 19:06:13 +02:00
|
|
|
}
|
2023-10-25 19:40:26 +02:00
|
|
|
|
2023-12-20 20:32:10 +00:00
|
|
|
pub fn recalculate(&mut self) {
|
|
|
|
|
for set in self.sets.values_mut() {
|
2024-06-27 13:32:17 +02:00
|
|
|
set.sticky_layer.recalculate();
|
2023-12-20 20:32:10 +00:00
|
|
|
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 => {
|
2024-07-12 19:29:47 +02:00
|
|
|
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())
|
2025-01-31 14:13:33 -08:00
|
|
|
.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 {
|
2025-03-13 12:50:02 -07:00
|
|
|
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() {
|
2025-03-14 10:50:57 -07:00
|
|
|
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() {
|
2025-03-13 12:50:02 -07:00
|
|
|
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() {
|
2025-03-13 12:50:02 -07:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2025-01-06 19:23:06 +01: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
|
|
|
}
|
|
|
|
|
|
2025-01-06 19:23:06 +01:00
|
|
|
pub fn active_mut(&mut self, output: &Output) -> Option<&mut Workspace> {
|
|
|
|
|
self.sets
|
2023-10-24 17:40:52 +02:00
|
|
|
.get_mut(output)
|
|
|
|
|
.or(self.backup_set.as_mut())
|
2025-01-06 19:23:06 +01:00
|
|
|
.map(|set| &mut set.workspaces[set.active])
|
2022-09-28 12:01:29 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-22 20:19:11 +02:00
|
|
|
pub fn active_num(&self, output: &Output) -> (Option<usize>, usize) {
|
2023-10-24 17:40:52 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-12-07 19:31:36 +00: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
|
|
|
}
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-11-22 12:45:29 +01: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
|
|
|
|
2025-03-13 12:50:02 -07:00
|
|
|
pub fn set_theme(&mut self, theme: cosmic::Theme) {
|
2023-10-10 13:55:34 -04:00
|
|
|
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 {
|
2023-10-10 13:55:34 -04:00
|
|
|
w.tiling_layer.theme = theme.clone();
|
2023-12-07 19:41:28 +00:00
|
|
|
w.floating_layer.theme = theme.clone();
|
2023-10-10 13:55:34 -04:00
|
|
|
|
|
|
|
|
w.mapped().for_each(|m| {
|
|
|
|
|
m.update_theme(theme.clone());
|
2024-08-02 20:40:44 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 12:50:02 -07:00
|
|
|
self.force_redraw();
|
2024-08-02 20:40:44 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-13 12:50:02 -07: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| {
|
2023-10-10 13:55:34 -04:00
|
|
|
m.force_redraw();
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-13 12:50:02 -07:00
|
|
|
w.refresh();
|
2023-10-10 13:55:34 -04:00
|
|
|
w.dirty.store(true, Ordering::Relaxed);
|
|
|
|
|
w.recalculate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-08 14:25:18 -05:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
pub fn update_autotile_behavior<'a>(
|
2024-02-08 14:25:18 -05:00
|
|
|
&mut self,
|
|
|
|
|
behavior: TileBehavior,
|
|
|
|
|
guard: &mut WorkspaceUpdateGuard<'_, State>,
|
2024-04-05 13:53:35 +02:00
|
|
|
seats: impl Iterator<Item = &'a Seat<State>>,
|
2024-02-08 14:25:18 -05:00
|
|
|
) {
|
|
|
|
|
self.autotile_behavior = behavior;
|
|
|
|
|
self.apply_tile_change(guard, seats);
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
fn apply_tile_change<'a>(
|
2024-02-08 14:25:18 -05:00
|
|
|
&mut self,
|
|
|
|
|
guard: &mut WorkspaceUpdateGuard<'_, State>,
|
2024-04-05 13:53:35 +02:00
|
|
|
seats: impl Iterator<Item = &'a Seat<State>>,
|
2024-02-08 14:25:18 -05:00
|
|
|
) {
|
2024-04-05 13:53:35 +02:00
|
|
|
let seats = seats.cloned().collect::<Vec<_>>();
|
2024-02-09 10:49:44 -05:00
|
|
|
for (_, set) in &mut self.sets {
|
|
|
|
|
set.tiling_enabled = self.autotile;
|
2024-02-08 14:25:18 -05:00
|
|
|
|
2024-02-09 10:49:44 -05:00
|
|
|
if matches!(self.autotile_behavior, TileBehavior::Global) {
|
|
|
|
|
// must apply change to all workspaces now
|
2024-02-08 14:25:18 -05:00
|
|
|
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>(
|
2024-02-08 14:25:18 -05:00
|
|
|
&mut self,
|
|
|
|
|
autotile: bool,
|
|
|
|
|
guard: &mut WorkspaceUpdateGuard<'_, State>,
|
2024-04-05 13:53:35 +02:00
|
|
|
seats: impl Iterator<Item = &'a Seat<State>>,
|
2024-02-08 14:25:18 -05:00
|
|
|
) {
|
|
|
|
|
self.autotile = autotile;
|
|
|
|
|
self.apply_tile_change(guard, seats);
|
|
|
|
|
}
|
2025-01-31 14:13:33 -08:00
|
|
|
|
|
|
|
|
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) {
|
2025-05-20 17:41:27 +02:00
|
|
|
let mut shell = self.shell.write();
|
2025-03-13 12:50:02 -07:00
|
|
|
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);
|
2023-11-20 19:11:02 +01:00
|
|
|
self.refresh(); // fixes indicies of any moved workspaces
|
2022-04-13 22:59:14 +02:00
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
pub fn remove_output(&mut self, output: &Output) {
|
2025-05-20 17:41:27 +02:00
|
|
|
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
|
|
|
|
2023-11-20 21:19:47 +01:00
|
|
|
pub fn migrate_workspace(&mut self, from: &Output, to: &Output, handle: &WorkspaceHandle) {
|
|
|
|
|
if from == to {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-20 17:41:27 +02:00
|
|
|
let mut shell = self.shell.write();
|
2025-03-13 12:50:02 -07:00
|
|
|
shell
|
|
|
|
|
.workspaces
|
|
|
|
|
.migrate_workspace(from, to, handle, &mut self.workspace_state.update());
|
2024-04-10 15:49:08 +02:00
|
|
|
|
|
|
|
|
std::mem::drop(shell);
|
2023-11-20 21:19:47 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
pub fn update_config(&mut self) {
|
2025-05-20 17:41:27 +02:00
|
|
|
let mut shell = self.shell.write();
|
2025-02-13 21:08:31 +01:00
|
|
|
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;
|
2025-02-13 21:08:31 +01:00
|
|
|
|
|
|
|
|
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);
|
2025-02-13 21:08:31 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-07 19:51:47 +02:00
|
|
|
|
2023-10-11 19:06:13 +02:00
|
|
|
let mut workspace_state = self.workspace_state.update();
|
2025-02-13 21:08:31 +01:00
|
|
|
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) {
|
2025-03-13 12:50:02 -07:00
|
|
|
self.xdg_activation_state
|
|
|
|
|
.retain_tokens(|_, data| data.timestamp.elapsed() < ACTIVATION_TOKEN_EXPIRE_TIME);
|
2025-05-20 17:41:27 +02:00
|
|
|
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);
|
2024-04-19 14:57:17 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn on_commit(&mut self, surface: &WlSurface) {
|
2024-06-28 11:25:20 +02:00
|
|
|
{
|
2025-05-20 17:41:27 +02:00
|
|
|
let shell = self.shell.read();
|
2024-06-28 11:25:20 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-13 19:22:57 +02:00
|
|
|
|
|
|
|
|
data_device::on_commit(surface, seat);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let is_cursor_image = shell.seats.iter().any(|seat| {
|
|
|
|
|
seat.user_data()
|
|
|
|
|
.get::<Mutex<CursorImageStatus>>()
|
|
|
|
|
.map(|guard| {
|
|
|
|
|
matches!(*guard.lock().unwrap(), CursorImageStatus::Surface(ref cursor_surface) if cursor_surface == surface)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-06-28 11:25:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(mapped) = shell.element_for_surface(surface) {
|
|
|
|
|
mapped.on_commit(surface);
|
|
|
|
|
}
|
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();
|
|
|
|
|
|
2024-09-04 21:20:21 +03:00
|
|
|
let tiling_exceptions = layout::TilingExceptions::new(config.tiling_exceptions.iter());
|
2024-08-15 17:36:42 +03:00
|
|
|
|
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,
|
2025-02-27 18:14:35 +01:00
|
|
|
previous_workspace_idx: None,
|
2024-04-10 15:49:08 +02:00
|
|
|
|
|
|
|
|
theme,
|
2024-06-07 19:51:47 +02:00
|
|
|
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,
|
2025-01-23 15:45:00 +01:00
|
|
|
zoom_state: None,
|
2024-08-15 17:36:42 +03:00
|
|
|
tiling_exceptions,
|
2024-06-07 19:51:47 +02:00
|
|
|
|
|
|
|
|
#[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,
|
2024-03-07 13:14:53 -06:00
|
|
|
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) {
|
2023-07-31 17:25:09 +02:00
|
|
|
if matches!(
|
2024-07-15 16:30:54 +02:00
|
|
|
self.overview_mode.active_trigger(),
|
|
|
|
|
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
2023-07-31 17:25:09 +02:00
|
|
|
) {
|
2023-10-25 19:40:26 +02:00
|
|
|
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
2023-07-31 17:25:09 +02:00
|
|
|
}
|
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)?;
|
2024-03-07 13:14:53 -06:00
|
|
|
}
|
|
|
|
|
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>,
|
2024-03-07 13:14:53 -06: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!(
|
2024-07-15 16:30:54 +02:00
|
|
|
self.overview_mode.active_trigger(),
|
|
|
|
|
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
2024-03-07 13:14:53 -06:00
|
|
|
) {
|
|
|
|
|
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,
|
2024-03-07 13:14:53 -06:00
|
|
|
)?;
|
|
|
|
|
} else {
|
|
|
|
|
set.activate_previous(
|
|
|
|
|
WorkspaceDelta::new_gesture_end(
|
|
|
|
|
1.0 - delta.abs(),
|
|
|
|
|
velocity.abs(),
|
|
|
|
|
),
|
2024-04-10 15:49:08 +02:00
|
|
|
workspace_state,
|
2024-03-07 13:14:53 -06:00
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {} // 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,
|
2024-03-07 13:14:53 -06:00
|
|
|
)?;
|
|
|
|
|
} else {
|
|
|
|
|
set.activate_previous(
|
|
|
|
|
WorkspaceDelta::new_gesture_end(
|
|
|
|
|
1.0 - delta.abs(),
|
|
|
|
|
velocity.abs(),
|
|
|
|
|
),
|
2024-04-10 15:49:08 +02:00
|
|
|
workspace_state,
|
2024-03-07 13:14:53 -06:00
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {} // Do nothing
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-25 19:40:26 +02:00
|
|
|
}
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
2022-07-04 15:28:03 +02:00
|
|
|
}
|
2021-12-28 16:23:12 +01:00
|
|
|
}
|
|
|
|
|
|
2025-01-06 19:23:06 +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
|
|
|
|
2025-01-06 19:23:06 +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
|
|
|
}
|
2021-12-22 20:14:36 +01:00
|
|
|
|
2024-09-04 11:13:59 -05:00
|
|
|
/// get the parent output of the window which has keyboard focus (for a given seat)
|
2024-09-10 19:38:48 +02:00
|
|
|
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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-01-06 19:23:06 +01:00
|
|
|
let workspace = self.active_space(output).unwrap();
|
2024-09-10 19:38:48 +02:00
|
|
|
let is_mapped = workspace.mapped().any(|m| m == &elem);
|
|
|
|
|
|
|
|
|
|
is_sticky || is_mapped
|
|
|
|
|
})
|
|
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
KeyboardFocusTarget::Fullscreen(elem) => self
|
|
|
|
|
.outputs()
|
|
|
|
|
.find(|output| {
|
2025-01-06 19:23:06 +01:00
|
|
|
let workspace = self.active_space(&output).unwrap();
|
2024-09-10 19:38:48 +02:00
|
|
|
workspace.get_fullscreen() == Some(&elem)
|
|
|
|
|
})
|
|
|
|
|
.cloned(),
|
|
|
|
|
KeyboardFocusTarget::Group(WindowGroup { node, .. }) => self
|
|
|
|
|
.outputs()
|
|
|
|
|
.find(|output| {
|
|
|
|
|
self.workspaces
|
|
|
|
|
.active(&output)
|
2025-01-06 19:23:06 +01:00
|
|
|
.unwrap()
|
2024-09-10 19:38:48 +02:00
|
|
|
.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::Popup(_) => unreachable!(),
|
2024-09-04 11:13:59 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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::Fullscreen(surface) => self.element_for_surface(surface).cloned(),
|
|
|
|
|
KeyboardFocusTarget::Element(window) => Some(window).cloned(),
|
|
|
|
|
KeyboardFocusTarget::Popup(PopupKind::Xdg(popup)) => {
|
|
|
|
|
if let Some(parent) = popup.get_parent_surface() {
|
|
|
|
|
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) {
|
|
|
|
|
if let KeyboardFocusTarget::Group(_group) = focus_target {
|
|
|
|
|
//TODO: decide if we want close actions to apply to groups
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
if let Some(mapped) = self.focused_element(focus_target) {
|
|
|
|
|
mapped.send_close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-13 12:50:02 -07:00
|
|
|
pub fn refresh_active_space(&mut self, output: &Output) {
|
2025-01-06 19:23:06 +01:00
|
|
|
if let Some(w) = self.workspaces.active_mut(output) {
|
2025-03-13 12:50:02 -07:00
|
|
|
w.refresh()
|
2025-01-06 19:23:06 +01:00
|
|
|
}
|
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> {
|
2023-11-14 13:13:42 -08:00
|
|
|
if let Some(session_lock) = &self.session_lock {
|
2023-12-20 19:58:43 +00:00
|
|
|
return session_lock
|
2023-11-14 13:13:42 -08:00
|
|
|
.surfaces
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|(_, v)| v.wl_surface() == surface)
|
2023-12-20 19:58:43 +00:00
|
|
|
.map(|(k, _)| k);
|
2023-11-14 13:13:42 -08:00
|
|
|
}
|
|
|
|
|
|
2023-12-20 19:58:43 +00:00
|
|
|
self.outputs()
|
|
|
|
|
// layer map surface?
|
2023-06-22 21:30:45 +02:00
|
|
|
.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?
|
2023-06-22 21:30:45 +02:00
|
|
|
.or_else(|| {
|
2025-02-04 14:33:11 +01:00
|
|
|
self.pending_layers.iter().find_map(|pending| {
|
2023-11-14 17:30:11 +01:00
|
|
|
let mut found = false;
|
2025-02-04 14:33:11 +01:00
|
|
|
pending.surface.with_surfaces(|s, _| {
|
2023-11-14 17:30:11 +01:00
|
|
|
if s == surface {
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
2023-06-22 21:30:45 +02:00
|
|
|
});
|
2025-02-04 14:33:11 +01:00
|
|
|
found.then_some(&pending.output)
|
2023-06-22 21:30:45 +02:00
|
|
|
})
|
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| {
|
|
|
|
|
self.active_space(o)
|
2025-01-06 19:23:06 +01:00
|
|
|
.unwrap()
|
2023-12-20 19:58:43 +00:00
|
|
|
.mapped()
|
|
|
|
|
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|
|
|
|
|
})
|
|
|
|
|
})
|
2022-05-16 18:11:24 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-07 18:40:29 +01:00
|
|
|
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.mapped()
|
|
|
|
|
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|
2024-02-23 17:25:40 +01:00
|
|
|
|| w.minimized_windows
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|m| m.window.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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-21 12:44:40 +01:00
|
|
|
pub fn element_for_surface<S>(&self, surface: &S) -> Option<&CosmicMapped>
|
|
|
|
|
where
|
|
|
|
|
CosmicSurface: PartialEq<S>,
|
|
|
|
|
{
|
2023-12-20 20:04:51 +00:00
|
|
|
self.workspaces.sets.values().find_map(|set| {
|
2024-02-23 17:25:40 +01:00
|
|
|
set.minimized_windows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|w| &w.window)
|
|
|
|
|
.chain(set.sticky_layer.mapped())
|
2023-12-20 20:04:51 +00:00
|
|
|
.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))
|
2023-12-20 20:04:51 +00:00
|
|
|
})
|
|
|
|
|
})
|
2022-03-24 20:32:31 +01:00
|
|
|
}
|
2021-12-28 16:23:12 +01:00
|
|
|
|
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.window == mapped)
|
|
|
|
|
})
|
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.window == mapped)
|
|
|
|
|
})
|
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> {
|
2024-06-26 17:24:46 +02:00
|
|
|
self.workspaces.sets.keys()
|
2022-07-04 15:28:03 +02:00
|
|
|
}
|
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 => {
|
2025-01-30 16:48:39 +01:00
|
|
|
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 => {
|
2025-01-30 16:48:39 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-07 13:56:31 -08:00
|
|
|
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()
|
2022-07-04 15:28:03 +02:00
|
|
|
.fold(
|
2023-10-25 19:40:26 +02:00
|
|
|
Option::<Rectangle<i32, Global>>::None,
|
2022-07-04 15:28:03 +02:00
|
|
|
|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)
|
2022-07-04 15:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-12 20:01:37 +02:00
|
|
|
pub fn animations_going(&self) -> bool {
|
2024-03-28 12:34:46 +01:00
|
|
|
self.workspaces.sets.values().any(|set| {
|
|
|
|
|
set.previously_active
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|(_, delta)| delta.is_animating())
|
|
|
|
|
|| set.sticky_layer.animations_going()
|
2024-07-15 16:30:54 +02:00
|
|
|
}) || !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();
|
2023-12-20 20:21:43 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-08-11 18:15:22 +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>,
|
2023-08-11 18:15:22 +02:00
|
|
|
) {
|
2023-07-17 21:11:45 +02:00
|
|
|
if let Some(trigger) = enabled {
|
2024-07-15 16:30:54 +02:00
|
|
|
if !matches!(
|
|
|
|
|
self.overview_mode,
|
|
|
|
|
OverviewMode::Started(_, _) | OverviewMode::Active(_)
|
|
|
|
|
) {
|
2023-08-11 18:15:22 +02:00
|
|
|
if matches!(trigger, Trigger::KeyboardSwap(_, _)) {
|
2023-10-10 13:55:34 -04:00
|
|
|
self.swap_indicator = Some(swap_indicator(evlh, self.theme.clone()));
|
2023-08-11 18:15:22 +02:00
|
|
|
}
|
2023-07-17 21:11:45 +02:00
|
|
|
self.overview_mode = OverviewMode::Started(trigger, Instant::now());
|
2023-05-19 19:44:57 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-07-15 16:30:54 +02:00
|
|
|
if matches!(
|
|
|
|
|
self.overview_mode,
|
|
|
|
|
OverviewMode::Started(_, _) | OverviewMode::Active(_)
|
|
|
|
|
) {
|
2023-08-11 18:15:22 +02:00
|
|
|
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 {
|
2024-07-15 16:30:54 +02:00
|
|
|
(Duration::ZERO, self.overview_mode.active_trigger().cloned())
|
2023-08-11 18:15:22 +02:00
|
|
|
};
|
|
|
|
|
self.overview_mode =
|
|
|
|
|
OverviewMode::Ended(trigger, Instant::now() - reverse_duration);
|
2023-05-19 19:44:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
pub fn overview_mode(&self) -> (OverviewMode, Option<SwapIndicator>) {
|
2024-07-15 16:30:54 +02:00
|
|
|
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);
|
2023-05-19 19:44:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 18:15:22 +02:00
|
|
|
(self.overview_mode.clone(), self.swap_indicator.clone())
|
2023-05-19 19:44:57 +02:00
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2023-10-10 13:55:34 -04:00
|
|
|
self.resize_indicator = Some(resize_indicator(
|
|
|
|
|
direction,
|
|
|
|
|
config,
|
|
|
|
|
evlh,
|
|
|
|
|
self.theme.clone(),
|
|
|
|
|
));
|
2023-06-28 19:20:06 +02:00
|
|
|
} else {
|
2024-07-15 16:30:54 +02:00
|
|
|
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>) {
|
2024-07-15 16:30:54 +02:00
|
|
|
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())
|
2023-06-28 21:29:39 +02:00
|
|
|
}
|
|
|
|
|
|
2024-01-08 21:37:06 +00:00
|
|
|
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
|
2025-01-06 19:23:06 +01:00
|
|
|
.active_space(output)?
|
2024-01-08 21:37:06 +00:00
|
|
|
.floating_layer
|
|
|
|
|
.stacking_indicator(),
|
2025-01-06 19:23:06 +01:00
|
|
|
ManagedLayer::Tiling => self.active_space(output)?.tiling_layer.stacking_indicator(),
|
2024-01-08 21:37:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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()
|
2025-01-23 15:45:00 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
fn refresh(
|
|
|
|
|
&mut self,
|
|
|
|
|
xdg_activation_state: &XdgActivationState,
|
|
|
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
|
|
|
|
) {
|
2024-07-15 16:30:54 +02:00
|
|
|
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-07-15 16:30:54 +02:00
|
|
|
_ => {}
|
2024-04-05 13:53:35 +02:00
|
|
|
}
|
2024-07-15 16:30:54 +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-07-15 16:30:54 +02:00
|
|
|
_ => {}
|
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();
|
2021-12-22 20:14:36 +01:00
|
|
|
}
|
2022-09-28 12:01:29 +02:00
|
|
|
|
2023-01-27 19:51:23 +01:00
|
|
|
self.override_redirect_windows.retain(|or| or.alive());
|
2023-01-23 22:56:42 +01:00
|
|
|
self.override_redirect_windows
|
|
|
|
|
.iter()
|
2023-01-27 19:51:23 +01:00
|
|
|
.for_each(|or| or.refresh());
|
2023-01-23 18:25:01 +01:00
|
|
|
|
2025-02-04 14:33:11 +01:00
|
|
|
self.pending_layers
|
|
|
|
|
.retain(|pending| pending.surface.alive());
|
|
|
|
|
self.pending_windows
|
|
|
|
|
.retain(|pending| pending.surface.alive());
|
2024-03-28 13:10:28 +01:00
|
|
|
}
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-11 22:00:20 +02:00
|
|
|
pub fn remap_unfullscreened_window(
|
|
|
|
|
&mut self,
|
|
|
|
|
mapped: CosmicMapped,
|
|
|
|
|
current_workspace: &WorkspaceHandle,
|
|
|
|
|
previous_workspace: &WorkspaceHandle,
|
|
|
|
|
target_layer: ManagedLayer,
|
|
|
|
|
) {
|
2023-11-22 12:45:29 +01:00
|
|
|
if self
|
|
|
|
|
.workspaces
|
|
|
|
|
.space_for_handle(previous_workspace)
|
|
|
|
|
.is_none()
|
|
|
|
|
{
|
2023-10-11 22:00:20 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
2024-02-16 20:30:52 +01:00
|
|
|
let Some(workspace) = self.workspaces.space_for_handle_mut(¤t_workspace) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2023-10-11 22:00:20 +02:00
|
|
|
let _ = workspace.unmap(&mapped);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let new_workspace_output = self
|
2023-11-22 12:45:29 +01:00
|
|
|
.workspaces
|
2023-10-11 22:00:20 +02:00
|
|
|
.space_for_handle(&previous_workspace)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.output()
|
|
|
|
|
.clone();
|
|
|
|
|
for (window, _) in mapped.windows() {
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_enter_output(&window, &new_workspace_output);
|
|
|
|
|
toplevel_enter_workspace(&window, &previous_workspace);
|
2023-10-11 22:00:20 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-22 12:45:29 +01:00
|
|
|
let new_workspace = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.space_for_handle_mut(&previous_workspace)
|
|
|
|
|
.unwrap();
|
2023-10-11 22:00:20 +02:00
|
|
|
match target_layer {
|
2023-12-20 20:50:09 +00:00
|
|
|
ManagedLayer::Sticky => {
|
|
|
|
|
let output = new_workspace.output().clone();
|
|
|
|
|
self.workspaces
|
|
|
|
|
.sets
|
|
|
|
|
.get_mut(&output)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.sticky_layer
|
|
|
|
|
.map(mapped, None)
|
|
|
|
|
}
|
2024-01-26 18:47:59 +00:00
|
|
|
ManagedLayer::Tiling if new_workspace.tiling_enabled => {
|
2024-01-08 18:09:43 +00:00
|
|
|
new_workspace
|
|
|
|
|
.tiling_layer
|
|
|
|
|
.map(mapped, Option::<std::iter::Empty<_>>::None, None)
|
|
|
|
|
}
|
2024-01-26 18:47:59 +00:00
|
|
|
_ => new_workspace.floating_layer.map(mapped, None),
|
2023-10-11 22:00:20 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
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>,
|
2024-04-05 13:53:35 +02:00
|
|
|
evlh: &LoopHandle<'static, State>,
|
|
|
|
|
) -> Option<KeyboardFocusTarget> {
|
|
|
|
|
let pos = self
|
2022-07-04 16:00:29 +02:00
|
|
|
.pending_windows
|
|
|
|
|
.iter()
|
2025-02-04 14:33:11 +01:00
|
|
|
.position(|pending| &pending.surface == window)
|
2022-07-04 16:00:29 +02:00
|
|
|
.unwrap();
|
2025-02-04 14:33:11 +01:00
|
|
|
let PendingWindow {
|
|
|
|
|
surface: window,
|
|
|
|
|
seat,
|
|
|
|
|
fullscreen: output,
|
|
|
|
|
maximized: should_be_maximized,
|
|
|
|
|
} = self.pending_windows.remove(pos);
|
2022-09-28 12:01:29 +02:00
|
|
|
|
2024-02-21 13:24:56 -08: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
|
2024-02-21 13:24:56 -08:00
|
|
|
.sets
|
|
|
|
|
.values()
|
|
|
|
|
.any(|set| set.sticky_layer.mapped().any(|m| m == elem))
|
2023-12-20 20:39:51 +00:00
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
2024-02-21 13:24:56 -08:00
|
|
|
} else {
|
|
|
|
|
false
|
2023-12-20 20:39:51 +00:00
|
|
|
}
|
2024-02-21 13:24:56 -08:00
|
|
|
} else {
|
|
|
|
|
false
|
2023-12-20 20:39:51 +00:00
|
|
|
};
|
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-11 22:00:20 +02:00
|
|
|
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 {
|
2025-01-06 19:23:06 +01:00
|
|
|
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();
|
|
|
|
|
}
|
2023-10-11 22:00:20 +02:00
|
|
|
|
|
|
|
|
if let Some((mapped, layer, previous_workspace)) = workspace.remove_fullscreen() {
|
|
|
|
|
let old_handle = workspace.handle.clone();
|
2024-04-05 13:53:35 +02:00
|
|
|
let new_workspace_handle = self
|
2023-11-22 12:45:29 +01:00
|
|
|
.workspaces
|
2023-10-11 22:00:20 +02:00
|
|
|
.space_for_handle(&previous_workspace)
|
|
|
|
|
.is_some()
|
|
|
|
|
.then_some(previous_workspace)
|
|
|
|
|
.unwrap_or(old_handle);
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
self.remap_unfullscreened_window(mapped, &old_handle, &new_workspace_handle, layer);
|
2023-10-11 22:00:20 +02:00
|
|
|
};
|
|
|
|
|
|
2025-01-06 19:23:06 +01:00
|
|
|
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 {
|
2025-01-06 19:23:06 +01:00
|
|
|
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
|
|
|
|
2024-01-08 18:09:43 +00: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);
|
2024-08-14 21:56:20 +03:00
|
|
|
let floating_exception = layout::has_floating_exception(&self.tiling_exceptions, &window);
|
2024-01-08 18:09:43 +00:00
|
|
|
|
|
|
|
|
let maybe_focused = workspace.focus_stack.get(&seat).iter().next().cloned();
|
|
|
|
|
if let Some(focused) = maybe_focused {
|
2025-02-04 14:33:11 +01:00
|
|
|
if (focused.is_stack() && !is_dialog && !should_be_fullscreen && !should_be_maximized)
|
2024-01-08 18:09:43 +00:00
|
|
|
&& !(workspace.is_tiled(&focused) && floating_exception)
|
|
|
|
|
{
|
2025-02-26 15:31:06 +01:00
|
|
|
focused.stack_ref().unwrap().add_window(window, None, None);
|
2024-01-08 18:09:43 +00:00
|
|
|
if was_activated {
|
2024-04-10 15:49:08 +02:00
|
|
|
workspace_state.add_workspace_state(&workspace_handle, WState::Urgent);
|
2024-01-08 18:09:43 +00:00
|
|
|
}
|
2024-04-05 13:53:35 +02:00
|
|
|
return None;
|
2024-01-08 18:09:43 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 15:12:25 +01:00
|
|
|
let mapped = CosmicMapped::from(CosmicWindow::new(
|
|
|
|
|
window.clone(),
|
2024-04-05 13:53:35 +02:00
|
|
|
evlh.clone(),
|
2024-04-10 15:49:08 +02:00
|
|
|
self.theme.clone(),
|
2023-01-16 15:12:25 +01:00
|
|
|
));
|
2022-11-28 17:48:50 +01:00
|
|
|
#[cfg(feature = "debug")]
|
|
|
|
|
{
|
2024-06-07 19:51:47 +02:00
|
|
|
mapped.set_debug(self.debug_active);
|
2022-11-28 17:48:50 +01:00
|
|
|
}
|
2023-11-07 18:46:25 +01:00
|
|
|
|
|
|
|
|
let workspace_empty = workspace.mapped().next().is_none();
|
2024-01-08 18:09:43 +00:00
|
|
|
if is_dialog || floating_exception || !workspace.tiling_enabled {
|
2023-10-25 19:40:26 +02:00
|
|
|
workspace.floating_layer.map(mapped.clone(), None);
|
2022-07-04 15:28:03 +02:00
|
|
|
} 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()
|
|
|
|
|
{
|
2023-12-20 20:39:51 +00:00
|
|
|
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
|
2024-01-08 18:09:43 +00:00
|
|
|
.map(mapped.clone(), Some(focus_stack.iter()), None);
|
2023-10-11 22:00:20 +02:00
|
|
|
}
|
|
|
|
|
|
2023-12-20 20:39:51 +00:00
|
|
|
if !parent_is_sticky && should_be_fullscreen {
|
2024-04-10 15:49:08 +02:00
|
|
|
let from = minimize_rectangle(&output, &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
|
|
|
|
|
workspace.fullscreen_request(&mapped.active_window(), None, from, &seat);
|
2022-03-24 20:32:31 +01:00
|
|
|
}
|
2022-07-05 18:46:38 +02:00
|
|
|
|
2023-12-20 20:39:51 +00:00
|
|
|
if parent_is_sticky {
|
2024-04-05 13:53:35 +02:00
|
|
|
self.toggle_sticky(&seat, &mapped);
|
2023-12-20 20:39:51 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-04 14:33:11 +01:00
|
|
|
if !should_be_fullscreen && should_be_maximized {
|
2025-02-04 17:49:19 +01:00
|
|
|
self.maximize_request(&mapped, &seat, false);
|
2025-02-04 14:33:11 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
let new_target = if (workspace_output == seat.active_output()
|
|
|
|
|
&& active_handle == workspace_handle)
|
2023-12-20 20:39:51 +00:00
|
|
|
|| 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 || should_be_fullscreen {
|
|
|
|
|
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
|
|
|
|
2025-01-06 19:23:06 +01: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
|
2021-12-22 20:14:36 +01:00
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
pub fn map_override_redirect(&mut self, window: X11Surface) {
|
2023-01-23 22:56:42 +01:00
|
|
|
let geo = window.geometry();
|
2024-04-05 13:53:35 +02:00
|
|
|
for (output, overlap) in self.outputs().cloned().filter_map(|o| {
|
2023-10-25 19:24:51 +02:00
|
|
|
o.geometry()
|
|
|
|
|
.as_logical()
|
|
|
|
|
.intersection(geo)
|
|
|
|
|
.map(|overlap| (o, overlap))
|
|
|
|
|
}) {
|
2023-01-23 22:56:42 +01:00
|
|
|
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()
|
2025-02-04 14:33:11 +01:00
|
|
|
.position(|pending| &pending.surface == layer_surface)
|
2022-07-04 16:00:29 +02:00
|
|
|
.unwrap();
|
2025-02-04 14:33:11 +01:00
|
|
|
let pending = self.pending_layers.remove(pos);
|
2022-07-04 16:00:29 +02:00
|
|
|
|
2022-07-04 15:28:03 +02:00
|
|
|
let wants_focus = {
|
2025-02-04 14:33:11 +01:00
|
|
|
with_states(pending.surface.wl_surface(), |states| {
|
2024-06-07 18:58:33 +02:00
|
|
|
let mut state = states.cached_state.get::<LayerSurfaceCachedState>();
|
|
|
|
|
matches!(state.current().layer, Layer::Top | Layer::Overlay)
|
|
|
|
|
&& state.current().keyboard_interactivity != KeyboardInteractivity::None
|
2022-07-04 15:28:03 +02:00
|
|
|
})
|
|
|
|
|
};
|
2022-03-30 22:00:44 +02:00
|
|
|
|
2023-07-13 17:40:49 +02:00
|
|
|
{
|
2025-02-04 14:33:11 +01:00
|
|
|
let mut map = layer_map_for_output(&pending.output);
|
|
|
|
|
map.map_layer(&pending.surface).unwrap();
|
2023-07-13 17:40:49 +02:00
|
|
|
}
|
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();
|
2023-07-13 17:40:49 +02:00
|
|
|
}
|
2022-07-04 15:28:03 +02:00
|
|
|
|
2025-02-04 14:33:11 +01:00
|
|
|
wants_focus.then(|| pending.surface.into())
|
2022-03-30 23:08:35 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
2024-03-21 12:53:52 +01:00
|
|
|
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()))
|
|
|
|
|
});
|
2025-01-27 18:37:27 -08:00
|
|
|
let surface = if let Some((idx, mapped)) = sticky_res {
|
2024-03-21 12:53:52 +01:00
|
|
|
if mapped.is_stack() {
|
2025-01-27 18:37:27 -08:00
|
|
|
mapped.stack_ref().unwrap().remove_idx(idx)
|
2024-03-21 12:53:52 +01:00
|
|
|
} else {
|
|
|
|
|
set.sticky_layer.unmap(&mapped);
|
|
|
|
|
Some(mapped.active_window())
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(idx) = set
|
|
|
|
|
.minimized_windows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|w| &w.window)
|
|
|
|
|
.position(|w| w.windows().any(|(s, _)| &s == surface))
|
|
|
|
|
{
|
|
|
|
|
if set.minimized_windows.get(idx).unwrap().window.is_stack() {
|
|
|
|
|
let window = &mut set.minimized_windows.get_mut(idx).unwrap().window;
|
2025-01-27 18:37:27 -08:00
|
|
|
let stack = window.stack_ref().unwrap();
|
2024-03-21 12:53:52 +01:00
|
|
|
let idx = stack.surfaces().position(|s| &s == surface);
|
|
|
|
|
idx.and_then(|idx| stack.remove_idx(idx))
|
|
|
|
|
} else {
|
|
|
|
|
Some(set.minimized_windows.remove(idx).window.active_window())
|
|
|
|
|
}
|
2025-01-27 18:37:27 -08:00
|
|
|
} else if let Some((workspace, elem)) = set.workspaces.iter_mut().find_map(|w| {
|
2024-03-21 12:53:52 +01:00
|
|
|
w.element_for_surface(&surface)
|
|
|
|
|
.cloned()
|
|
|
|
|
.map(|elem| (w, elem))
|
|
|
|
|
}) {
|
|
|
|
|
if elem.is_stack() {
|
2025-01-27 18:37:27 -08:00
|
|
|
let stack = elem.stack_ref().unwrap();
|
2024-03-21 12:53:52 +01:00
|
|
|
let idx = stack.surfaces().position(|s| &s == surface);
|
|
|
|
|
idx.and_then(|idx| stack.remove_idx(idx))
|
|
|
|
|
} else {
|
|
|
|
|
workspace.unmap(&elem);
|
|
|
|
|
Some(elem.active_window())
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(surface) = surface {
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_info.remove_toplevel(&surface);
|
2025-02-04 14:33:11 +01:00
|
|
|
self.pending_windows.push(PendingWindow {
|
|
|
|
|
surface,
|
|
|
|
|
seat: seat.clone(),
|
|
|
|
|
fullscreen: None,
|
|
|
|
|
maximized: false,
|
|
|
|
|
});
|
2024-03-21 12:53:52 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
#[must_use]
|
2023-12-07 19:31:36 +00:00
|
|
|
pub fn move_window(
|
2024-04-05 13:53:35 +02:00
|
|
|
&mut self,
|
2023-12-06 15:13:48 -08:00
|
|
|
seat: Option<&Seat<State>>,
|
2023-12-07 19:31:36 +00:00
|
|
|
mapped: &CosmicMapped,
|
|
|
|
|
from: &WorkspaceHandle,
|
|
|
|
|
to: &WorkspaceHandle,
|
2023-01-24 19:22:00 +01:00
|
|
|
follow: bool,
|
2023-05-25 17:51:53 +02:00
|
|
|
direction: Option<Direction>,
|
2024-04-10 15:49:08 +02:00
|
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
2024-04-05 13:53:35 +02:00
|
|
|
) -> 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();
|
2021-12-22 20:14:36 +01:00
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
let from_workspace = self.workspaces.space_for_handle_mut(from).unwrap(); // checked above
|
2023-12-07 19:31:36 +00:00
|
|
|
let window_state = from_workspace.unmap(mapped)?;
|
|
|
|
|
let elements = from_workspace.mapped().cloned().collect::<Vec<_>>();
|
2022-07-04 16:00:29 +02:00
|
|
|
|
2022-11-14 11:54:22 +01:00
|
|
|
for (toplevel, _) in mapped.windows() {
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_leave_workspace(&toplevel, from);
|
2023-01-24 21:01:11 +01:00
|
|
|
if from_output != to_output {
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_leave_output(&toplevel, &from_output);
|
2023-01-24 21:01:11 +01:00
|
|
|
}
|
2022-11-14 11:54:22 +01:00
|
|
|
}
|
|
|
|
|
for mapped in elements.into_iter() {
|
2024-04-05 13:53:35 +02:00
|
|
|
self.update_reactive_popups(&mapped);
|
2022-11-14 11:54:22 +01:00
|
|
|
}
|
2023-01-24 19:22:00 +01:00
|
|
|
let new_pos = if follow {
|
2023-12-06 15:13:48 -08:00
|
|
|
if let Some(seat) = seat {
|
|
|
|
|
seat.set_active_output(&to_output);
|
|
|
|
|
}
|
2024-04-05 13:53:35 +02:00
|
|
|
self.workspaces
|
2023-12-07 19:31:36 +00:00
|
|
|
.idx_for_handle(&to_output, to)
|
2024-03-07 13:14:53 -06:00
|
|
|
.and_then(|to_idx| {
|
2024-04-10 15:49:08 +02:00
|
|
|
self.activate(
|
|
|
|
|
&to_output,
|
|
|
|
|
to_idx,
|
|
|
|
|
WorkspaceDelta::new_shortcut(),
|
|
|
|
|
workspace_state,
|
|
|
|
|
)
|
|
|
|
|
.unwrap()
|
2024-03-07 13:14:53 -06:00
|
|
|
})
|
2023-01-24 19:22:00 +01:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2022-11-22 10:10:08 +01:00
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
let any_seat = seat.unwrap_or(self.seats.last_active()).clone();
|
|
|
|
|
let mut to_workspace = self.workspaces.space_for_handle_mut(to).unwrap(); // checked above
|
2024-01-26 18:47:59 +00:00
|
|
|
if window_state.layer == ManagedLayer::Floating || !to_workspace.tiling_enabled {
|
2023-10-25 19:41:30 +02:00
|
|
|
to_workspace.floating_layer.map(mapped.clone(), None);
|
2022-11-14 11:54:22 +01:00
|
|
|
} else {
|
2025-03-26 18:24:33 +01:00
|
|
|
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));
|
2023-12-12 15:35:23 +00:00
|
|
|
to_workspace.tiling_layer.map(
|
|
|
|
|
mapped.clone(),
|
2023-12-06 15:13:48 -08:00
|
|
|
focus_stack.as_ref().map(|x| x.iter()),
|
2023-12-12 15:35:23 +00:00
|
|
|
direction,
|
|
|
|
|
);
|
2022-11-14 11:54:22 +01:00
|
|
|
}
|
2023-12-07 19:31:36 +00:00
|
|
|
|
2023-10-11 22:00:20 +02:00
|
|
|
let focus_target = if let Some(f) = window_state.was_fullscreen {
|
2023-10-24 15:58:53 +02:00
|
|
|
if to_workspace.fullscreen.is_some() {
|
|
|
|
|
if let Some((mapped, layer, previous_workspace)) = to_workspace.remove_fullscreen()
|
|
|
|
|
{
|
2023-12-07 19:31:36 +00:00
|
|
|
let old_handle = to.clone();
|
2024-04-05 13:53:35 +02:00
|
|
|
let new_workspace_handle = self
|
2023-11-22 12:45:29 +01:00
|
|
|
.workspaces
|
2023-10-24 15:58:53 +02:00
|
|
|
.space_for_handle(&previous_workspace)
|
|
|
|
|
.is_some()
|
|
|
|
|
.then_some(previous_workspace)
|
|
|
|
|
.unwrap_or(old_handle);
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
self.remap_unfullscreened_window(
|
2023-10-24 15:58:53 +02:00
|
|
|
mapped,
|
|
|
|
|
&old_handle,
|
|
|
|
|
&new_workspace_handle,
|
|
|
|
|
layer,
|
|
|
|
|
);
|
2024-04-05 13:53:35 +02:00
|
|
|
to_workspace = self.workspaces.space_for_handle_mut(to).unwrap();
|
2023-12-07 19:31:36 +00:00
|
|
|
// checked above
|
2023-10-24 15:58:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let from = minimize_rectangle(&to_output, &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
|
|
|
|
|
to_workspace.fullscreen_request(&mapped.active_window(), f.previously, from, &any_seat);
|
2023-10-24 15:58:53 +02:00
|
|
|
to_workspace
|
|
|
|
|
.fullscreen
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|f| KeyboardFocusTarget::from(f.surface.clone()))
|
|
|
|
|
.unwrap_or_else(|| KeyboardFocusTarget::from(mapped.clone()))
|
2023-10-25 19:41:30 +02:00
|
|
|
} else {
|
|
|
|
|
KeyboardFocusTarget::from(mapped.clone())
|
2023-09-20 18:57:58 +02:00
|
|
|
};
|
|
|
|
|
|
2023-12-07 19:31:36 +00:00
|
|
|
for mapped in to_workspace
|
|
|
|
|
.mapped()
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.into_iter()
|
|
|
|
|
{
|
2024-04-05 13:53:35 +02:00
|
|
|
self.update_reactive_popups(&mapped);
|
2023-12-07 19:31:36 +00:00
|
|
|
}
|
2022-11-14 11:54:22 +01:00
|
|
|
for (toplevel, _) in mapped.windows() {
|
2023-01-24 21:01:11 +01:00
|
|
|
if from_output != to_output {
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_enter_output(&toplevel, &to_output);
|
2023-01-24 21:01:11 +01:00
|
|
|
}
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_enter_workspace(&toplevel, to);
|
2022-07-04 16:00:29 +02:00
|
|
|
}
|
2022-11-14 11:54:22 +01:00
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
new_pos.map(|pos| (focus_target, pos))
|
2023-12-07 19:31:36 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
#[must_use]
|
2023-12-07 19:31:36 +00:00
|
|
|
pub fn move_current_window(
|
2024-04-05 13:53:35 +02:00
|
|
|
&mut self,
|
2023-12-07 19:31:36 +00:00
|
|
|
seat: &Seat<State>,
|
|
|
|
|
from_output: &Output,
|
|
|
|
|
to: (&Output, Option<usize>),
|
|
|
|
|
follow: bool,
|
|
|
|
|
direction: Option<Direction>,
|
2024-04-10 15:49:08 +02:00
|
|
|
workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
|
2024-04-05 13:53:35 +02:00
|
|
|
) -> Result<Option<(KeyboardFocusTarget, Point<i32, Global>)>, InvalidWorkspaceIndex> {
|
2023-12-07 19:31:36 +00:00
|
|
|
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);
|
2025-02-26 17:13:31 +01:00
|
|
|
let from_idx = self.workspaces.active_num(from_output).1;
|
2023-12-07 19:31:36 +00:00
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
if from_output == to_output && to_idx == self.workspaces.active_num(from_output).1 {
|
2023-12-07 19:31:36 +00:00
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 17:13:31 +01:00
|
|
|
if from_output == to_output
|
|
|
|
|
&& to_idx.checked_sub(1).is_some_and(|idx| idx == from_idx)
|
|
|
|
|
&& to_idx == self.workspaces.len(to_output) - 1
|
|
|
|
|
&& self
|
|
|
|
|
.workspaces
|
|
|
|
|
.get(from_idx, from_output)
|
|
|
|
|
.is_some_and(|w| w.mapped().count() == 1)
|
|
|
|
|
&& 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
|
2023-12-07 19:31:36 +00:00
|
|
|
.workspaces
|
|
|
|
|
.get(to_idx, to_output)
|
|
|
|
|
.map(|ws| ws.handle)
|
|
|
|
|
.ok_or(InvalidWorkspaceIndex)?;
|
|
|
|
|
|
2025-01-06 19:23:06 +01:00
|
|
|
let from_workspace = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.active_mut(from_output)
|
|
|
|
|
.ok_or(InvalidWorkspaceIndex)?;
|
2024-09-10 20:51:08 +02:00
|
|
|
let last_focused_window = from_workspace.focus_stack.get(seat).last().cloned();
|
|
|
|
|
let from = from_workspace.handle;
|
2023-12-07 19:31:36 +00:00
|
|
|
|
2024-09-10 20:51:08 +02:00
|
|
|
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
|
|
|
|
|
};
|
2023-12-07 19:31:36 +00:00
|
|
|
|
2024-09-10 20:51:08 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-26 18:24:08 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 20:51:08 +02:00
|
|
|
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,
|
2024-09-10 21:10:02 +02:00
|
|
|
focus_stack,
|
2024-09-10 20:51:08 +02:00
|
|
|
},
|
|
|
|
|
direction,
|
|
|
|
|
);
|
|
|
|
|
from_workspace.refresh_focus_stack();
|
|
|
|
|
to_workspace.refresh_focus_stack();
|
2025-03-26 18:23:42 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 20:51:08 +02:00
|
|
|
return Ok(res.zip(new_pos));
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-07 19:31:36 +00:00
|
|
|
|
2024-09-10 20:51:08 +02:00
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
if let Some(mapped) = last_focused_window {
|
|
|
|
|
Ok(self.move_window(
|
|
|
|
|
Some(seat),
|
|
|
|
|
&mapped,
|
|
|
|
|
&from,
|
|
|
|
|
&to,
|
|
|
|
|
follow,
|
|
|
|
|
direction,
|
|
|
|
|
workspace_state,
|
|
|
|
|
))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-09-28 12:01:29 +02:00
|
|
|
}
|
2022-07-04 15:28:03 +02:00
|
|
|
|
2022-09-28 12:01:29 +02:00
|
|
|
pub fn update_reactive_popups(&self, mapped: &CosmicMapped) {
|
|
|
|
|
if let Some(workspace) = self.space_for(mapped) {
|
2024-02-23 17:25:40 +01:00
|
|
|
if let Some(element_loc) = workspace
|
2023-10-25 19:24:51 +02:00
|
|
|
.element_geometry(mapped)
|
2024-02-23 17:25:40 +01:00
|
|
|
.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(),
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-01-16 15:12:25 +01:00
|
|
|
}
|
2022-03-24 20:32:31 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-18 20:23:41 +01:00
|
|
|
|
2023-12-07 19:53:41 +00:00
|
|
|
pub fn menu_request(
|
2024-04-10 15:49:08 +02:00
|
|
|
&self,
|
2023-12-07 19:53:41 +00:00
|
|
|
surface: &WlSurface,
|
|
|
|
|
seat: &Seat<State>,
|
|
|
|
|
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)) =
|
2025-02-13 21:05:36 +01:00
|
|
|
check_grab_preconditions(&seat, serial, Some(surface))
|
2024-04-10 15:49:08 +02:00
|
|
|
else {
|
2025-02-13 21:05:36 +01:00
|
|
|
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
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let mapped = self.element_for_surface(surface).cloned()?;
|
|
|
|
|
let (_, relative_loc) = mapped
|
|
|
|
|
.windows()
|
2024-05-13 14:16:21 -07:00
|
|
|
.find(|(w, _)| w.wl_surface().as_deref() == Some(surface))
|
2024-04-10 15:49:08 +02:00
|
|
|
.unwrap();
|
2023-12-20 20:19:42 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let (global_position, edge, is_tiled, is_stacked, is_sticky, tiling_enabled) =
|
|
|
|
|
if let Some(set) = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.sets
|
|
|
|
|
.values()
|
|
|
|
|
.find(|set| set.sticky_layer.mapped().any(|m| m == &mapped))
|
|
|
|
|
{
|
|
|
|
|
let output = set.output.clone();
|
|
|
|
|
let global_position = (set.sticky_layer.element_geometry(&mapped).unwrap().loc
|
|
|
|
|
+ relative_loc.as_local()
|
|
|
|
|
+ location.as_local())
|
|
|
|
|
.to_global(&output);
|
|
|
|
|
(
|
|
|
|
|
global_position,
|
|
|
|
|
ResizeEdge::all(),
|
|
|
|
|
false,
|
|
|
|
|
mapped.is_stack(),
|
|
|
|
|
true,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
} else if let Some(workspace) = self.space_for(&mapped) {
|
|
|
|
|
let output = seat.active_output();
|
|
|
|
|
|
|
|
|
|
let elem_geo = workspace.element_geometry(&mapped)?;
|
|
|
|
|
let global_position =
|
|
|
|
|
(elem_geo.loc + relative_loc.as_local() + location.as_local())
|
|
|
|
|
.to_global(&output);
|
|
|
|
|
let is_tiled = workspace.is_tiled(&mapped);
|
|
|
|
|
let edge = if is_tiled {
|
|
|
|
|
mapped
|
|
|
|
|
.tiling_node_id
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.clone()
|
|
|
|
|
.map(|node_id| {
|
|
|
|
|
TilingLayout::possible_resizes(workspace.tiling_layer.tree(), node_id)
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(ResizeEdge::empty())
|
|
|
|
|
} else {
|
|
|
|
|
ResizeEdge::all()
|
|
|
|
|
};
|
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,
|
2024-04-10 15:49:08 +02:00
|
|
|
edge,
|
|
|
|
|
is_tiled,
|
|
|
|
|
mapped.is_stack(),
|
|
|
|
|
false,
|
|
|
|
|
workspace.tiling_enabled,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let grab = MenuGrab::new(
|
2025-02-13 21:05:36 +01:00
|
|
|
GrabStartData::Pointer(start_data),
|
2024-04-10 15:49:08 +02:00
|
|
|
seat,
|
|
|
|
|
if target_stack || !is_stacked {
|
|
|
|
|
Box::new(window_items(
|
|
|
|
|
&mapped,
|
|
|
|
|
is_tiled,
|
|
|
|
|
is_stacked,
|
|
|
|
|
is_sticky,
|
|
|
|
|
tiling_enabled,
|
|
|
|
|
edge,
|
2024-04-03 16:02:27 +02:00
|
|
|
config,
|
2024-04-10 15:49:08 +02:00
|
|
|
)) as Box<dyn Iterator<Item = Item>>
|
|
|
|
|
} else {
|
|
|
|
|
let (tab, _) = mapped
|
|
|
|
|
.windows()
|
2024-05-13 14:16:21 -07:00
|
|
|
.find(|(s, _)| s.wl_surface().as_deref() == Some(surface))
|
2024-04-10 15:49:08 +02:00
|
|
|
.unwrap();
|
2024-04-03 16:02:27 +02:00
|
|
|
Box::new(tab_items(&mapped, &tab, is_tiled, config))
|
2024-04-10 15:49:08 +02:00
|
|
|
as Box<dyn Iterator<Item = Item>>
|
|
|
|
|
},
|
|
|
|
|
global_position,
|
2025-02-13 21:05:36 +01:00
|
|
|
MenuAlignment::CORNER,
|
2025-02-14 18:34:52 +01:00
|
|
|
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>>,
|
2023-12-07 19:49:53 +00:00
|
|
|
release: ReleaseMode,
|
2023-12-20 20:29:44 +00:00
|
|
|
move_out_of_stack: bool,
|
2024-04-10 15:49:08 +02:00
|
|
|
config: &Config,
|
|
|
|
|
evlh: &LoopHandle<'static, State>,
|
2024-08-19 16:01:04 +01:00
|
|
|
client_initiated: bool,
|
2024-04-10 15:49:08 +02:00
|
|
|
) -> Option<(MoveGrab, Focus)> {
|
2025-04-15 16:15:24 +02:00
|
|
|
if self.overview_mode().0.is_active() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 20:23:41 +01:00
|
|
|
let serial = serial.into();
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2025-02-13 21:05:36 +01:00
|
|
|
let mut start_data =
|
|
|
|
|
check_grab_preconditions(&seat, serial, client_initiated.then_some(surface))?;
|
2025-01-27 18:37:27 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let (window, _) = old_mapped
|
|
|
|
|
.windows()
|
2024-05-13 14:16:21 -07:00
|
|
|
.find(|(w, _)| w.wl_surface().as_deref() == Some(surface))
|
2024-04-10 15:49:08 +02:00
|
|
|
.unwrap();
|
2023-12-20 20:29:44 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2024-12-26 18:18:35 -08:00
|
|
|
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()
|
|
|
|
|
};
|
2023-12-20 20:29:44 +00:00
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
if workspace
|
|
|
|
|
.fullscreen
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|f| f.surface == window)
|
|
|
|
|
{
|
|
|
|
|
let _ = workspace.remove_fullscreen(); // We are moving this window, we don't need to send it back to it's original workspace
|
|
|
|
|
}
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let elem_geo = workspace.element_geometry(&old_mapped)?;
|
|
|
|
|
let mut initial_window_location = elem_geo.loc.to_global(workspace.output());
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-06-28 12:24:09 +02:00
|
|
|
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
|
2024-06-28 12:24:09 +02:00
|
|
|
workspace.unmaximize_request(&mapped)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
let layer = if mapped == old_mapped {
|
|
|
|
|
let was_floating = workspace.floating_layer.unmap(&mapped);
|
|
|
|
|
let was_tiled = workspace.tiling_layer.unmap_as_placeholder(&mapped);
|
2024-06-28 12:24:09 +02:00
|
|
|
assert!(was_floating.is_some() != was_tiled.is_some());
|
|
|
|
|
if was_floating.is_some_and(|size| size != elem_geo.size.as_logical()) {
|
|
|
|
|
new_size = was_floating;
|
|
|
|
|
}
|
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);
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-06-28 12:24:09 +02:00
|
|
|
// 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)
|
|
|
|
|
{
|
2024-06-28 12:24:09 +02:00
|
|
|
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
|
|
|
|
2024-06-28 12:24:09 +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,
|
|
|
|
|
);
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-06-28 12:24:09 +02:00
|
|
|
Some(new_size)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
if mapped == old_mapped {
|
2024-06-28 12:24:09 +02:00
|
|
|
if let Some(size) = sticky_layer.unmap(&mapped) {
|
|
|
|
|
if size != elem_geo.size.as_logical() {
|
|
|
|
|
new_size = Some(size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
(
|
|
|
|
|
initial_window_location,
|
|
|
|
|
ManagedLayer::Sticky,
|
2025-01-06 19:23:06 +01:00
|
|
|
self.active_space(&cursor_output).unwrap().handle,
|
2024-04-10 15:49:08 +02:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
toplevel_leave_workspace(&window, &workspace_handle);
|
|
|
|
|
toplevel_leave_output(&window, &cursor_output);
|
2023-12-20 20:29:44 +00:00
|
|
|
|
2024-04-10 15:49:08 +02:00
|
|
|
if move_out_of_stack {
|
2025-01-27 18:37:27 -08:00
|
|
|
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()
|
2025-03-13 12:50:02 -07:00
|
|
|
.refresh();
|
2024-04-10 15:49:08 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-17 18:57:21 +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,
|
2025-02-14 21:58:09 +11:00
|
|
|
config.cosmic_conf.edge_snap_threshold as f64,
|
2024-04-10 15:49:08 +02:00
|
|
|
layer,
|
|
|
|
|
release,
|
|
|
|
|
evlh.clone(),
|
|
|
|
|
);
|
2023-12-20 20:29:44 +00:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-09-04 11:13:59 -05: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>> {
|
|
|
|
|
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 {
|
2023-12-22 15:48:35 +00:00
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
};
|
2024-09-09 20:02:12 +02:00
|
|
|
let output = seat.active_output();
|
2025-01-06 19:23:06 +01:00
|
|
|
let workspace = self.active_space(&output).unwrap();
|
2024-09-04 11:13:59 -05:00
|
|
|
|
|
|
|
|
if workspace.fullscreen.is_some() {
|
|
|
|
|
return FocusResult::None;
|
|
|
|
|
}
|
2023-12-22 15:48:35 +00:00
|
|
|
|
2024-08-08 21:03:11 +02:00
|
|
|
if matches!(target, KeyboardFocusTarget::Fullscreen(_)) {
|
|
|
|
|
return FocusResult::None;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-09 20:02:12 +02:00
|
|
|
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];
|
2023-12-22 15:48:35 +00:00
|
|
|
|
|
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
};
|
2024-02-16 20:30:52 +01:00
|
|
|
sticky_layer
|
|
|
|
|
.space
|
|
|
|
|
.elements()
|
|
|
|
|
.chain(workspace.mapped())
|
2024-05-13 14:16:21 -07:00
|
|
|
.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),
|
2024-07-03 21:03:36 +02:00
|
|
|
KeyboardFocusTarget::Group { .. } => {
|
|
|
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
2024-07-15 16:30:54 +02:00
|
|
|
let swap_desc = match overview.active_trigger() {
|
|
|
|
|
Some(Trigger::KeyboardSwap(_, desc)) => Some(desc.clone()),
|
2024-07-03 21:03:36 +02:00
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return workspace.tiling_layer.next_focus(
|
|
|
|
|
direction,
|
|
|
|
|
seat,
|
|
|
|
|
focus_stack.iter(),
|
|
|
|
|
swap_desc,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-12-22 15:48:35 +00:00
|
|
|
_ => None,
|
2024-02-16 20:30:52 +01:00
|
|
|
})
|
|
|
|
|
.cloned() else {
|
|
|
|
|
return FocusResult::None;
|
2023-12-22 15:48:35 +00:00
|
|
|
};
|
|
|
|
|
|
2025-02-24 21:43:16 +01:00
|
|
|
if focused.handle_focus(seat, direction, None) {
|
2023-12-22 15:48:35 +00:00
|
|
|
return FocusResult::Handled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if workspace.is_tiled(&focused) {
|
2024-08-08 21:03:49 +02:00
|
|
|
if focused.is_maximized(false) {
|
|
|
|
|
return FocusResult::None;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-22 15:48:35 +00:00
|
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
2024-07-15 16:30:54 +02:00
|
|
|
let swap_desc = match overview.active_trigger() {
|
|
|
|
|
Some(Trigger::KeyboardSwap(_, desc)) => Some(desc.clone()),
|
2023-12-22 15:48:35 +00:00
|
|
|
_ => 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;
|
2023-12-22 15:48:35 +00:00
|
|
|
|
|
|
|
|
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(),
|
|
|
|
|
)
|
|
|
|
|
});
|
|
|
|
|
|
2023-12-22 15:48:35 +00:00
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
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;
|
2023-12-22 15:48:35 +00:00
|
|
|
if res.is_negative() {
|
|
|
|
|
res
|
|
|
|
|
} else {
|
|
|
|
|
i32::MIN
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
_ => return FocusResult::None,
|
2024-01-02 18:13:27 +00:00
|
|
|
}
|
|
|
|
|
.map(|(other, _)| other);
|
2023-12-22 15:48:35 +00:00
|
|
|
|
|
|
|
|
next.map(|elem| FocusResult::Some(KeyboardFocusTarget::Element(elem.clone())))
|
|
|
|
|
.unwrap_or(FocusResult::None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
#[must_use]
|
2024-09-04 11:13:59 -05:00
|
|
|
pub fn move_current_element<'a>(
|
|
|
|
|
&mut self,
|
|
|
|
|
direction: Direction,
|
|
|
|
|
seat: &Seat<State>,
|
|
|
|
|
) -> MoveResult {
|
2024-09-10 19:38:48 +02:00
|
|
|
let Some(output) = seat.focused_output() else {
|
|
|
|
|
return MoveResult::None;
|
|
|
|
|
};
|
2025-01-06 19:23:06 +01:00
|
|
|
let workspace = self.active_space(&output).unwrap();
|
2023-12-22 15:48:05 +00:00
|
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
|
|
|
|
let Some(last) = focus_stack.last().cloned() else {
|
2024-02-16 20:30:52 +01:00
|
|
|
return MoveResult::None;
|
2023-12-22 15:48:05 +00:00
|
|
|
};
|
|
|
|
|
let fullscreen = workspace.fullscreen.as_ref().map(|f| f.surface.clone());
|
|
|
|
|
|
2024-01-05 20:38:13 +00:00
|
|
|
if last
|
|
|
|
|
.maximized_state
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.as_ref()
|
|
|
|
|
.is_some_and(|state| state.original_layer == ManagedLayer::Tiling)
|
|
|
|
|
{
|
|
|
|
|
self.unmaximize_request(&last);
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-22 15:48:05 +00:00
|
|
|
if let Some(surface) = fullscreen {
|
|
|
|
|
MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(surface))
|
|
|
|
|
} else if let Some(set) = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.sets
|
|
|
|
|
.values_mut()
|
|
|
|
|
.find(|set| set.sticky_layer.mapped().any(|m| m == &last))
|
|
|
|
|
{
|
2024-01-05 20:38:13 +00:00
|
|
|
set.sticky_layer.move_current_element(
|
|
|
|
|
direction,
|
|
|
|
|
seat,
|
|
|
|
|
ManagedLayer::Sticky,
|
|
|
|
|
self.theme.clone(),
|
|
|
|
|
)
|
2023-12-22 15:48:05 +00:00
|
|
|
} else {
|
|
|
|
|
let theme = self.theme.clone();
|
2025-01-06 19:23:06 +01:00
|
|
|
let workspace = self.active_space_mut(&output).unwrap();
|
2023-12-22 15:48:05 +00:00
|
|
|
workspace
|
|
|
|
|
.floating_layer
|
2024-01-05 20:38:13 +00:00
|
|
|
.move_current_element(direction, seat, ManagedLayer::Floating, theme)
|
2023-12-22 15:48:05 +00:00
|
|
|
.or_else(|| workspace.tiling_layer.move_current_node(direction, seat))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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,
|
2025-02-14 21:58:09 +11:00
|
|
|
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),
|
|
|
|
|
)> {
|
2024-03-07 16:53:25 +01:00
|
|
|
if mapped.is_fullscreen(true) || mapped.is_maximized(true) {
|
2024-04-10 15:49:08 +02:00
|
|
|
return None;
|
2024-03-07 16:53:25 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-13 21:05:36 +01:00
|
|
|
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-05 16:15:47 -07:00
|
|
|
|
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-03-07 16:53:25 +01:00
|
|
|
|
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,
|
2025-02-14 21:58:09 +11:00
|
|
|
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(),
|
2024-03-07 16:54:19 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2024-02-23 17:25:40 +01:00
|
|
|
pub fn maximize_toggle(&mut self, window: &CosmicMapped, seat: &Seat<State>) {
|
2023-12-20 20:45:47 +00:00
|
|
|
if window.is_maximized(true) {
|
|
|
|
|
self.unmaximize_request(window);
|
|
|
|
|
} else {
|
2024-07-10 21:55:47 +02:00
|
|
|
if window.is_fullscreen(true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-04 17:49:19 +01:00
|
|
|
self.maximize_request(window, seat, true);
|
2023-12-20 20:45:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-23 17:25:40 +01:00
|
|
|
pub fn minimize_request(&mut self, mapped: &CosmicMapped) {
|
|
|
|
|
if let Some(set) = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.sets
|
|
|
|
|
.values_mut()
|
|
|
|
|
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
|
|
|
|
|
{
|
2024-04-10 15:49:08 +02:00
|
|
|
let to = minimize_rectangle(&set.output, &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
let (window, position) = set.sticky_layer.unmap_minimize(mapped, to).unwrap();
|
|
|
|
|
set.minimized_windows.push(MinimizedWindow {
|
|
|
|
|
window,
|
|
|
|
|
previous_state: MinimizedState::Sticky { position },
|
|
|
|
|
output_geo: set.output.geometry(),
|
|
|
|
|
fullscreen: None,
|
|
|
|
|
});
|
|
|
|
|
} else if let Some(workspace) = self.workspaces.sets.values_mut().find_map(|set| {
|
|
|
|
|
set.workspaces
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|workspace| workspace.mapped().any(|m| m == mapped))
|
|
|
|
|
}) {
|
2024-04-10 15:49:08 +02:00
|
|
|
let to = minimize_rectangle(workspace.output(), &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
if let Some(minimized) = workspace.minimize(&mapped, to) {
|
|
|
|
|
workspace.minimized_windows.push(minimized);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-08 20:28:59 +01:00
|
|
|
|
2024-02-23 17:25:40 +01:00
|
|
|
pub fn unminimize_request(&mut self, mapped: &CosmicMapped, seat: &Seat<State>) {
|
|
|
|
|
if let Some((set, window)) = self.workspaces.sets.values_mut().find_map(|set| {
|
|
|
|
|
set.minimized_windows
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|m| &m.window == mapped)
|
|
|
|
|
.map(|i| set.minimized_windows.swap_remove(i))
|
|
|
|
|
.map(|window| (set, window))
|
|
|
|
|
}) {
|
2024-04-10 15:49:08 +02:00
|
|
|
let from = minimize_rectangle(&set.output, &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
|
|
|
|
|
if let MinimizedState::Sticky { mut position } = window.previous_state {
|
|
|
|
|
let current_output_size = set.output.geometry().size.as_logical();
|
|
|
|
|
if current_output_size != window.output_geo.size.as_logical() {
|
|
|
|
|
position = Point::from((
|
|
|
|
|
(position.x as f64 / window.output_geo.size.w as f64
|
|
|
|
|
* current_output_size.w as f64)
|
|
|
|
|
.floor() as i32,
|
|
|
|
|
(position.y as f64 / window.output_geo.size.h as f64
|
|
|
|
|
* current_output_size.h as f64)
|
|
|
|
|
.floor() as i32,
|
|
|
|
|
))
|
|
|
|
|
};
|
|
|
|
|
set.sticky_layer
|
|
|
|
|
.remap_minimized(window.window, from, position);
|
|
|
|
|
} else {
|
|
|
|
|
unreachable!("None sticky window in WorkspaceSet minimized_windows");
|
|
|
|
|
}
|
|
|
|
|
} else if let Some((workspace, window)) = self.workspaces.spaces_mut().find_map(|w| {
|
|
|
|
|
w.minimized_windows
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|m| &m.window == mapped)
|
|
|
|
|
.map(|i| w.minimized_windows.swap_remove(i))
|
|
|
|
|
.map(|window| (w, window))
|
|
|
|
|
}) {
|
2024-04-10 15:49:08 +02:00
|
|
|
let from = minimize_rectangle(workspace.output(), &mapped.active_window());
|
2024-02-23 17:25:40 +01:00
|
|
|
|
|
|
|
|
workspace.unminimize(window, from, seat);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-08 20:28:59 +01:00
|
|
|
|
2025-02-04 17:49:19 +01:00
|
|
|
pub fn maximize_request(&mut self, mapped: &CosmicMapped, seat: &Seat<State>, animate: bool) {
|
2024-02-23 17:25:40 +01:00
|
|
|
self.unminimize_request(mapped, seat);
|
2023-12-23 22:49:56 +00:00
|
|
|
let (original_layer, floating_layer, original_geometry) = if let Some(set) = self
|
2023-12-20 20:45:47 +00:00
|
|
|
.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)
|
2023-12-20 20:45:47 +00:00
|
|
|
} else if let Some(workspace) = self.space_for_mut(&mapped) {
|
|
|
|
|
let layer = if workspace.is_floating(&mapped) {
|
|
|
|
|
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)
|
2023-12-20 20:45:47 +00:00
|
|
|
} 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,
|
2023-12-20 20:45:47 +00:00
|
|
|
});
|
|
|
|
|
std::mem::drop(state);
|
2025-02-04 17:49:19 +01:00
|
|
|
floating_layer.map_maximized(mapped.clone(), original_geometry, animate);
|
2023-12-20 20:45:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.window == mapped)
|
|
|
|
|
}) {
|
2023-12-20 20:45:47 +00: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.window == mapped)
|
|
|
|
|
{
|
|
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-12-20 20:45:47 +00:00
|
|
|
Some(state.original_geometry.size.as_logical())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(workspace) = self.space_for_mut(mapped) {
|
|
|
|
|
workspace.unmaximize_request(mapped)
|
|
|
|
|
} 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,
|
2025-02-14 21:58:09 +11:00
|
|
|
edge_snap_threshold: u32,
|
2024-08-19 16:01:04 +01:00
|
|
|
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();
|
2025-02-13 21:05:36 +01:00
|
|
|
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_fullscreen(true) || mapped.is_maximized(true) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2023-12-20 20:49:37 +00:00
|
|
|
|
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-05 16:15:47 -07:00
|
|
|
|
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,
|
2025-02-14 21:58:09 +11:00
|
|
|
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-03-07 16:54:19 +01:00
|
|
|
|
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) {
|
2024-09-04 11:13:59 -05:00
|
|
|
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);
|
2023-12-20 20:49:37 +00:00
|
|
|
|
|
|
|
|
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()
|
2023-12-20 20:49:37 +00:00
|
|
|
.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-12-20 20:49:37 +00:00
|
|
|
})
|
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-12-20 20:49:37 +00: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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-10-10 13:55:34 -04:00
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
#[must_use]
|
2024-07-03 21:04:28 +02:00
|
|
|
pub fn toggle_stacking(
|
|
|
|
|
&mut self,
|
|
|
|
|
seat: &Seat<State>,
|
|
|
|
|
window: &CosmicMapped,
|
|
|
|
|
) -> Option<KeyboardFocusTarget> {
|
2023-12-20 20:48:19 +00:00
|
|
|
if let Some(set) = self
|
|
|
|
|
.workspaces
|
|
|
|
|
.sets
|
|
|
|
|
.values_mut()
|
|
|
|
|
.find(|set| set.sticky_layer.mapped().any(|m| m == window))
|
|
|
|
|
{
|
2024-07-03 21:04:28 +02:00
|
|
|
let workspace = &mut set.workspaces[set.active];
|
|
|
|
|
set.sticky_layer
|
|
|
|
|
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
|
2023-12-20 20:48:19 +00:00
|
|
|
} else if let Some(workspace) = self.space_for_mut(window) {
|
2024-02-28 19:49:36 +01:00
|
|
|
if workspace.tiling_layer.mapped().any(|(m, _)| m == window) {
|
2024-07-03 21:04:28 +02:00
|
|
|
workspace
|
|
|
|
|
.tiling_layer
|
|
|
|
|
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
|
2023-12-20 20:48:19 +00:00
|
|
|
} else if workspace.floating_layer.mapped().any(|w| w == window) {
|
2024-07-03 21:04:28 +02:00
|
|
|
workspace
|
|
|
|
|
.floating_layer
|
|
|
|
|
.toggle_stacking(window, workspace.focus_stack.get_mut(seat))
|
2023-12-20 20:48:19 +00:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-05 13:53:35 +02:00
|
|
|
#[must_use]
|
2023-12-20 20:48:19 +00:00
|
|
|
pub fn toggle_stacking_focused(&mut self, seat: &Seat<State>) -> Option<KeyboardFocusTarget> {
|
2024-09-04 11:13:59 -05:00
|
|
|
let Some(focused_output) = seat.focused_output() else {
|
|
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
let set = self.workspaces.sets.get_mut(&focused_output).unwrap();
|
2023-12-20 20:48:19 +00:00
|
|
|
let workspace = &mut set.workspaces[set.active];
|
|
|
|
|
let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned();
|
|
|
|
|
if let Some(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) {
|
2023-12-20 20:48:19 +00:00
|
|
|
set.sticky_layer
|
|
|
|
|
.toggle_stacking_focused(seat, workspace.focus_stack.get_mut(seat))
|
2024-02-28 19:49:36 +01:00
|
|
|
} else if workspace.tiling_layer.mapped().any(|(m, _)| m == &window) {
|
2023-12-20 20:48:19 +00:00
|
|
|
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() {
|
2025-02-04 17:49:19 +01:00
|
|
|
self.maximize_request(mapped, seat, false);
|
2024-12-13 17:48:42 +01:00
|
|
|
}
|
2023-12-20 20:48:19 +00:00
|
|
|
}
|
2024-12-13 17:48:42 +01:00
|
|
|
|
|
|
|
|
res
|
2023-12-20 20:48:19 +00:00
|
|
|
} 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) {
|
|
|
|
|
if workspace.is_fullscreen(mapped) {
|
|
|
|
|
// we are making it sticky, we don't need to move it to it's previous workspace
|
|
|
|
|
let _ = workspace.remove_fullscreen();
|
|
|
|
|
}
|
|
|
|
|
let previous_layer = if workspace.is_tiled(mapped) {
|
|
|
|
|
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;
|
|
|
|
|
};
|
2023-12-20 20:19:42 +00:00
|
|
|
workspace.unmap(mapped);
|
|
|
|
|
|
|
|
|
|
*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
|
|
|
}
|
|
|
|
|
|
2025-01-06 08:10:49 -03: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.element_geometry(mapped).unwrap();
|
|
|
|
|
set.sticky_layer.unmap(mapped);
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2025-01-06 08:10:49 -03:00
|
|
|
let previous_layer = mapped
|
2023-12-20 20:19:42 +00:00
|
|
|
.previous_layer
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.take()
|
2025-01-06 08:10:49 -03:00
|
|
|
.unwrap_or(ManagedLayer::Floating);
|
2025-01-06 19:47:12 +01:00
|
|
|
|
2025-01-06 08:10:49 -03:00
|
|
|
match previous_layer {
|
2024-01-26 18:47:59 +00:00
|
|
|
ManagedLayer::Tiling if workspace.tiling_enabled => {
|
2023-12-20 20:19:42 +00:00
|
|
|
let focus_stack = workspace.focus_stack.get(seat);
|
2024-01-08 18:09:43 +00:00
|
|
|
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
|
|
|
}
|
2025-01-06 08:10:49 -03: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, 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>) {
|
2023-12-20 20:21:04 +00:00
|
|
|
let set = self.workspaces.sets.get_mut(&seat.active_output()).unwrap();
|
|
|
|
|
let workspace = &mut set.workspaces[set.active];
|
|
|
|
|
let maybe_window = workspace.focus_stack.get(seat).iter().next().cloned();
|
|
|
|
|
if let Some(mapped) = maybe_window {
|
2024-04-05 13:53:35 +02:00
|
|
|
self.toggle_sticky(seat, &mapped);
|
2023-12-20 20:21:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-28 13:58:51 +02:00
|
|
|
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);
|
2025-03-13 12:50:02 -07:00
|
|
|
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>,
|
|
|
|
|
) {
|
2023-10-10 13:55:34 -04:00
|
|
|
self.theme = theme.clone();
|
2024-04-10 15:49:08 +02:00
|
|
|
self.refresh(xdg_activation_state, workspace_state);
|
2025-03-13 12:50:02 -07:00
|
|
|
self.workspaces.set_theme(theme.clone());
|
2024-04-05 13:53:35 +02:00
|
|
|
}
|
2024-06-07 19:44:59 +02:00
|
|
|
|
2024-06-07 19:51:47 +02:00
|
|
|
pub fn theme(&self) -> &cosmic::Theme {
|
|
|
|
|
&self.theme
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 21:20:21 +03:00
|
|
|
pub fn update_tiling_exceptions<'a, I>(&mut self, exceptions: I)
|
|
|
|
|
where
|
2024-09-09 20:02:12 +02:00
|
|
|
I: Iterator<Item = &'a ApplicationException>,
|
2024-09-04 21:20:21 +03:00
|
|
|
{
|
2024-09-04 20:40:41 +03:00
|
|
|
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);
|
|
|
|
|
|
2025-01-06 19:23:06 +01:00
|
|
|
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().map(|m| &m.window))
|
|
|
|
|
.chain(set.workspaces.iter().flat_map(|w| {
|
|
|
|
|
w.mapped()
|
|
|
|
|
.chain(w.minimized_windows.iter().map(|m| &m.window))
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
}
|
2022-03-30 22:00:44 +02:00
|
|
|
}
|
2022-03-29 14:41:09 +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,
|
2022-07-04 15:28:03 +02:00
|
|
|
) {
|
2024-01-05 18:33:16 +00:00
|
|
|
state.set_workspace_name(handle, format!("{}", idx));
|
2025-03-14 10:40:33 -07:00
|
|
|
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>,
|
2025-02-13 21:05:36 +01:00
|
|
|
client_initiated: Option<&WlSurface>,
|
2024-04-04 19:17:03 -07:00
|
|
|
) -> Option<GrabStartData> {
|
2023-01-18 20:23:41 +01:00
|
|
|
use smithay::reexports::wayland_server::Resource;
|
|
|
|
|
|
|
|
|
|
let pointer = seat.get_pointer().unwrap();
|
2024-04-04 19:17:03 -07:00
|
|
|
let touch = seat.get_touch().unwrap();
|
2023-01-18 20:23:41 +01:00
|
|
|
|
2024-04-04 19:17:03 -07: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.)))),
|
2024-04-04 19:17:03 -07:00
|
|
|
button: 0x110,
|
|
|
|
|
location: pointer.current_location(),
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
};
|
2023-01-18 20:23:41 +01:00
|
|
|
|
2025-02-13 21:05:36 +01:00
|
|
|
if let Some(surface) = client_initiated {
|
2024-04-04 19:17:03 -07:00
|
|
|
// Check that this surface has a click or touch down grab.
|
2023-12-07 19:49:53 +00:00
|
|
|
if !match serial {
|
2024-04-04 19:17:03 -07:00
|
|
|
Some(serial) => pointer.has_grab(serial) || touch.has_grab(serial),
|
|
|
|
|
None => pointer.is_grabbed() | touch.is_grabbed(),
|
2023-12-07 19:49:53 +00:00
|
|
|
} {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2023-01-18 20:23:41 +01:00
|
|
|
|
2023-12-07 19:49:53 +00:00
|
|
|
// If the focus was for a different surface, ignore the request.
|
2024-04-04 19:17:03 -07:00
|
|
|
if start_data.focus().is_none()
|
|
|
|
|
|| !start_data.focus().unwrap().0.same_client_as(&surface.id())
|
2023-12-07 19:49:53 +00:00
|
|
|
{
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2023-01-18 20:23:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(start_data)
|
|
|
|
|
}
|