From 4709a1d684b243b5fb0eb2045b396a2459d49956 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 20 Sep 2023 18:57:58 +0200 Subject: [PATCH] shell: Have Move-shortcut for floating layer and fullscreen windows --- src/config/key_bindings.rs | 4 +- src/input/mod.rs | 9 +-- src/shell/element/mod.rs | 6 +- src/shell/element/stack.rs | 5 +- src/shell/focus/mod.rs | 18 ++++-- src/shell/layout/floating/mod.rs | 68 +++++++++++++++++++-- src/shell/layout/tiling/mod.rs | 35 ++--------- src/shell/mod.rs | 40 +++++++++++- src/shell/workspace.rs | 87 ++++++++++++++++++++++++--- src/wayland/handlers/xdg_shell/mod.rs | 2 +- src/xwayland.rs | 2 +- 11 files changed, 207 insertions(+), 69 deletions(-) diff --git a/src/config/key_bindings.rs b/src/config/key_bindings.rs index 269a1ddb..4ad89f6a 100644 --- a/src/config/key_bindings.rs +++ b/src/config/key_bindings.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::shell::{ - focus::FocusDirection, grabs::ResizeEdge, layout::tiling::Direction, ResizeDirection, -}; +use crate::shell::{focus::FocusDirection, grabs::ResizeEdge, Direction, ResizeDirection}; use serde::Deserialize; use smithay::{ backend::input::KeyState, diff --git a/src/input/mod.rs b/src/input/mod.rs index 971400e2..9af4c60d 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -6,8 +6,9 @@ use crate::{ shell::{ focus::{target::PointerFocusTarget, FocusDirection}, grabs::{ResizeEdge, SeatMoveGrabState}, - layout::tiling::{Direction, MoveResult, SwapWindowGrab, TilingLayout}, - FocusResult, OverviewMode, ResizeDirection, ResizeMode, Trigger, Workspace, + layout::tiling::{SwapWindowGrab, TilingLayout}, + Direction, FocusResult, MoveResult, OverviewMode, ResizeDirection, ResizeMode, Trigger, + Workspace, }, state::Common, utils::prelude::*, @@ -1373,7 +1374,7 @@ impl State { return; // TODO, is this what we want? How do we indicate the switch? } - match workspace.tiling_layer.move_current_node(direction, seat) { + match workspace.move_current_element(direction, seat) { MoveResult::MoveFurther(_move_further) => { match (direction, self.common.config.static_conf.workspace_layout) { (Direction::Left, WorkspaceLayout::Horizontal) @@ -1417,7 +1418,7 @@ impl State { MoveResult::ShiftFocus(shift) => { Common::set_focus(self, Some(&shift), seat, None); } - MoveResult::Done => { + _ => { if let Some(focused_window) = workspace.focus_stack.get(seat).last() { if workspace.is_tiled(focused_window) { self.common.shell.set_overview_mode( diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 6fb86121..f649ea76 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -73,10 +73,8 @@ use tracing::debug; use super::{ focus::FocusDirection, - layout::{ - floating::ResizeState, - tiling::{Direction, NodeDesc}, - }, + layout::{floating::ResizeState, tiling::NodeDesc}, + Direction, }; space_elements! { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 782584af..913c2b4e 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,10 +1,7 @@ use super::{CosmicMapped, CosmicSurface, CosmicWindow}; use crate::{ shell::{ - focus::FocusDirection, - grabs::MoveGrab, - layout::tiling::{Direction, NodeDesc}, - Shell, Trigger, + focus::FocusDirection, grabs::MoveGrab, layout::tiling::NodeDesc, Direction, Shell, Trigger, }, state::State, utils::iced::{IcedElement, Program}, diff --git a/src/shell/focus/mod.rs b/src/shell/focus/mod.rs index b47f4ed0..02a5596b 100644 --- a/src/shell/focus/mod.rs +++ b/src/shell/focus/mod.rs @@ -97,12 +97,22 @@ impl Shell { serial: Option, ) { // update FocusStack and notify layouts about new focus (if any window) - if let Some(KeyboardFocusTarget::Element(mapped)) = target { - if let Some(workspace) = state.common.shell.space_for_mut(mapped) { + let element = match target { + Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()), + Some(KeyboardFocusTarget::Fullscreen(window)) => state + .common + .shell + .element_for_surface(&window.surface()) + .cloned(), + _ => None, + }; + + if let Some(mapped) = element { + if let Some(workspace) = state.common.shell.space_for_mut(&mapped) { let mut focus_stack = workspace.focus_stack.get_mut(active_seat); - if Some(mapped) != focus_stack.last() { + if Some(&mapped) != focus_stack.last() { trace!(?mapped, "Focusing window."); - focus_stack.append(mapped); + focus_stack.append(&mapped); // also remove popup grabs, if we are switching focus if let Some(mut popup_grab) = active_seat .user_data() diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 237c0aa2..24094dca 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -13,18 +13,18 @@ use smithay::{ }; use std::collections::HashMap; -use crate::shell::focus::FocusDirection; -use crate::shell::FocusResult; use crate::{ backend::render::{element::AsGlowRenderer, IndicatorShader, Key, Usage}, shell::{ element::{ - resize_indicator::ResizeIndicator, stack::CosmicStackRenderElement, - window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement, + resize_indicator::ResizeIndicator, + stack::{CosmicStackRenderElement, MoveResult as StackMoveResult}, + window::CosmicWindowRenderElement, + CosmicMapped, CosmicMappedRenderElement, CosmicWindow, }, - focus::target::KeyboardFocusTarget, + focus::{target::KeyboardFocusTarget, FocusDirection}, grabs::ResizeEdge, - CosmicSurface, ResizeDirection, ResizeMode, + CosmicSurface, Direction, FocusResult, MoveResult, ResizeDirection, ResizeMode, }, state::State, utils::prelude::*, @@ -423,6 +423,62 @@ impl FloatingLayout { .unwrap_or(FocusResult::None) } + pub fn move_current_element<'a>( + &mut self, + direction: Direction, + seat: &Seat, + ) -> MoveResult { + let Some(target) = seat.get_keyboard().unwrap().current_focus() else { + return MoveResult::None + }; + + let Some(focused) = (match target { + KeyboardFocusTarget::Popup(popup) => { + let Some(toplevel_surface) = (match popup { + PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), + }) else { + return MoveResult::None + }; + self.space.elements().find(|elem| elem.wl_surface().as_ref() == Some(&toplevel_surface)) + }, + KeyboardFocusTarget::Element(elem) => self.space.elements().find(|e| *e == &elem), + _ => None, + }) else { + return MoveResult::None + }; + + match focused.handle_move(direction) { + StackMoveResult::Handled => return MoveResult::Done, + StackMoveResult::MoveOut(surface, loop_handle) => { + let mapped: CosmicMapped = CosmicWindow::new(surface, loop_handle).into(); + let output = seat.active_output(); + let pos = self.space.element_geometry(focused).unwrap().loc + + match direction { + Direction::Up => Point::from((5, -10)), + Direction::Down => Point::from((5, 10)), + Direction::Left => Point::from((-10, 5)), + Direction::Right => Point::from((10, 5)), + }; + let position = self + .space + .output_geometry(&output) + .unwrap() + .overlaps({ + let mut geo = mapped.geometry(); + geo.loc += pos; + geo + }) + .then_some(pos); + + self.map_internal(mapped.clone(), &output, position); + return MoveResult::ShiftFocus(KeyboardFocusTarget::Element(mapped)); + } + StackMoveResult::Default => {} + }; + + MoveResult::MoveFurther(KeyboardFocusTarget::Element(focused.clone())) + } + pub fn mapped(&self) -> impl Iterator { self.space.elements().rev() } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 5178b511..cfa48157 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -22,8 +22,8 @@ use crate::{ }, grabs::ResizeEdge, layout::Orientation, - CosmicSurface, FocusResult, OutputNotMapped, OverviewMode, ResizeDirection, ResizeMode, - Trigger, + CosmicSurface, Direction, FocusResult, MoveResult, OutputNotMapped, OverviewMode, + ResizeDirection, ResizeMode, Trigger, }, utils::{prelude::*, tween::EaseRectangle}, wayland::{ @@ -112,33 +112,6 @@ pub struct NodeDesc { pub stack_window: Option, } -#[derive(Debug, serde::Deserialize, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Left, - Right, - Up, - Down, -} - -impl std::ops::Not for Direction { - type Output = Self; - fn not(self) -> Self::Output { - match self { - Direction::Left => Direction::Right, - Direction::Right => Direction::Left, - Direction::Up => Direction::Down, - Direction::Down => Direction::Up, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum MoveResult { - Done, - MoveFurther(KeyboardFocusTarget), - ShiftFocus(KeyboardFocusTarget), -} - #[derive(Debug, Clone, PartialEq)] enum TargetZone { Initial, @@ -1454,12 +1427,12 @@ impl TilingLayout { let mut tree = queue.trees.back().unwrap().0.copy_clone(); let Some(target) = seat.get_keyboard().unwrap().current_focus() else { - return MoveResult::Done; + return MoveResult::None; }; let Some((node_id, data)) = TilingLayout::currently_focused_node(&mut tree, &seat.active_output(), target) else { - return MoveResult::Done; + return MoveResult::None; }; // stacks may handle movement internally diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 52ced80a..396d54e9 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -66,7 +66,7 @@ use self::{ grabs::ResizeEdge, layout::{ floating::{FloatingLayout, ResizeState}, - tiling::{Direction, NodeDesc, TilingLayout}, + tiling::{NodeDesc, TilingLayout}, }, }; @@ -1500,13 +1500,47 @@ impl Shell { .get_mut(to_idx, to_output) .unwrap(); // checked above let focus_stack = to_workspace.focus_stack.get(&seat); - if window_state == ManagedState::Floating { + if window_state.layer == ManagedLayer::Floating { to_workspace.floating_layer.map(mapped.clone(), &seat, None); } else { to_workspace .tiling_layer .map(mapped.clone(), &seat, focus_stack.iter(), direction); } + let focus_target = match window_state.was_fullscreen { + Some(true) => { + to_workspace.fullscreen_request( + &mapped.active_window(), + &to_output, + state.common.event_loop_handle.clone(), + ); + KeyboardFocusTarget::from( + to_workspace + .fullscreen + .get(to_output) + .unwrap() + .window + .clone(), + ) + } + Some(false) => { + to_workspace.maximize_request( + &mapped.active_window(), + &to_output, + state.common.event_loop_handle.clone(), + ); + KeyboardFocusTarget::from( + to_workspace + .fullscreen + .get(to_output) + .unwrap() + .window + .clone(), + ) + } + None => KeyboardFocusTarget::from(mapped.clone()), + }; + for (toplevel, _) in mapped.windows() { if from_output != to_output { state @@ -1531,7 +1565,7 @@ impl Shell { } if follow { - Common::set_focus(state, Some(&KeyboardFocusTarget::from(mapped)), &seat, None); + Common::set_focus(state, Some(&focus_target), &seat, None); } Ok(new_pos) } diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 26dfb098..2e781ad0 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -128,11 +128,36 @@ impl IsAlive for FullscreenSurface { pub struct FocusStacks(HashMap, IndexSet>); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ManagedState { +pub struct ManagedState { + pub layer: ManagedLayer, + pub was_fullscreen: Option, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ManagedLayer { Tiling, Floating, } +#[derive(Debug, serde::Deserialize, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Left, + Right, + Up, + Down, +} + +impl std::ops::Not for Direction { + type Output = Self; + fn not(self) -> Self::Output { + match self { + Direction::Left => Direction::Right, + Direction::Right => Direction::Left, + Direction::Up => Direction::Down, + Direction::Down => Direction::Up, + } + } +} + #[derive(Debug, Clone, PartialEq)] pub enum FocusResult { None, @@ -152,6 +177,26 @@ impl FocusResult { } } +#[derive(Debug, Clone, PartialEq)] +pub enum MoveResult { + None, + Done, + MoveFurther(KeyboardFocusTarget), + ShiftFocus(KeyboardFocusTarget), +} + +impl MoveResult { + pub fn or_else(self, f: F) -> MoveResult + where + F: FnOnce() -> MoveResult, + { + match self { + MoveResult::None => f(), + x => x, + } + } +} + impl Workspace { pub fn new(handle: WorkspaceHandle, tiling_enabled: bool, gaps: (u8, u8)) -> Workspace { Workspace { @@ -279,7 +324,9 @@ impl Workspace { assert!(was_floating != was_tiling); } - if mapped.is_maximized(true) || mapped.is_fullscreen(true) { + let was_maximized = mapped.is_maximized(true); + let was_fullscreen = mapped.is_fullscreen(true); + if was_maximized || was_fullscreen { self.unmaximize_request(&mapped.active_window()); } @@ -288,9 +335,15 @@ impl Workspace { .values_mut() .for_each(|set| set.retain(|m| m != mapped)); if was_floating { - Some(ManagedState::Floating) + Some(ManagedState { + layer: ManagedLayer::Floating, + was_fullscreen: (was_fullscreen || was_maximized).then_some(was_fullscreen), + }) } else if was_tiling { - Some(ManagedState::Tiling) + Some(ManagedState { + layer: ManagedLayer::Tiling, + was_fullscreen: (was_fullscreen || was_maximized).then_some(was_fullscreen), + }) } else { None } @@ -389,14 +442,14 @@ impl Workspace { window.set_maximized(true); self.set_fullscreen(window, output, false, evlh) } + pub fn unmaximize_request(&mut self, window: &CosmicSurface) -> Option> { if self .fullscreen .values() .any(|f| &f.window.surface() == window) { - self.unfullscreen_request(window); - self.floating_layer.unmaximize_request(window) + self.unfullscreen_request(window) } else { None } @@ -476,7 +529,7 @@ impl Workspace { ); } - pub fn unfullscreen_request(&mut self, window: &CosmicSurface) { + pub fn unfullscreen_request(&mut self, window: &CosmicSurface) -> Option> { if let Some((output, f)) = self .fullscreen .iter_mut() @@ -485,7 +538,7 @@ impl Workspace { f.window.output_leave(output); window.set_maximized(false); window.set_fullscreen(false); - self.floating_layer.unmaximize_request(window); + let result = self.floating_layer.unmaximize_request(window); self.floating_layer.refresh(); self.tiling_layer.recalculate(output); self.tiling_layer.refresh(); @@ -520,6 +573,10 @@ impl Workspace { old_signal.store(true, Ordering::SeqCst); } } + + result + } else { + None } } @@ -833,6 +890,20 @@ impl Workspace { }) } + pub fn move_current_element<'a>( + &mut self, + direction: Direction, + seat: &Seat, + ) -> MoveResult { + if let Some(f) = self.fullscreen.get(&seat.active_output()) { + MoveResult::MoveFurther(KeyboardFocusTarget::Fullscreen(f.window.clone())) + } else { + self.floating_layer + .move_current_element(direction, seat) + .or_else(|| self.tiling_layer.move_current_node(direction, seat)) + } + } + pub fn render_output<'a, R>( &self, renderer: &mut R, diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 056e3729..ec0ec87f 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -227,7 +227,7 @@ impl XdgShellHandler for State { .windows() .find(|(w, _)| w.wl_surface().as_ref() == Some(surface.wl_surface())) .unwrap(); - workspace.unfullscreen_request(&window) + workspace.unfullscreen_request(&window); } } } diff --git a/src/xwayland.rs b/src/xwayland.rs index b43bb488..6fe62b27 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -460,7 +460,7 @@ impl XwmHandler for Data { { if let Some(workspace) = self.state.common.shell.space_for_mut(&mapped) { let (window, _) = mapped.windows().find(|(w, _)| w == &surface).unwrap(); - workspace.unfullscreen_request(&window) + workspace.unfullscreen_request(&window); } } }