diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 13f23fef..78f7b5a2 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -1,6 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::state::Common; +use crate::{ + state::Common, + shell::grabs::{ + SeatMoveGrabState, + MoveGrabRenderElement, + }, +}; #[cfg(feature = "debug")] use crate::{ debug::{debug_ui, fps_ui, log_ui, EguiFrame}, @@ -42,6 +48,7 @@ smithay::custom_elements! { pub CustomElem<=Gles2Renderer>; SurfaceTree=SurfaceTree, PointerElement=PointerElement::, + MoveGrabRenderElement=MoveGrabRenderElement, #[cfg(feature = "debug")] EguiFrame=EguiFrame, } @@ -242,6 +249,12 @@ where .shell .space_relative_output_geometry(pointer.current_location().to_i32_round(), output); + if let Some(grab) = seat.user_data().get::().unwrap().borrow() + .as_ref().and_then(|state| state.render(seat, output)) + { + custom_elements.push(grab); + } + if let Some(cursor) = cursor::draw_cursor( renderer.as_gles2(), seat, diff --git a/src/input/mod.rs b/src/input/mod.rs index b7c52909..705fec15 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,6 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only -use crate::{config::Action, shell::Workspace, utils::prelude::*}; +use crate::{ + config::Action, + shell::{ + Workspace, + grabs::SeatMoveGrabState, + }, + utils::prelude::*, +}; use smithay::{ backend::input::{Device, DeviceCapability, InputBackend, InputEvent, KeyState}, desktop::{layer_map_for_output, Kind, WindowSurfaceType}, @@ -97,6 +104,7 @@ pub fn add_seat(dh: &DisplayHandle, name: String) -> Seat { userdata.insert_if_missing(SeatId::default); userdata.insert_if_missing(Devices::default); userdata.insert_if_missing(SupressedKeys::default); + userdata.insert_if_missing(SeatMoveGrabState::default); userdata.insert_if_missing(|| RefCell::new(CursorImageStatus::Default)); seat } diff --git a/src/shell/grabs.rs b/src/shell/grabs.rs new file mode 100644 index 00000000..d1ed8e9b --- /dev/null +++ b/src/shell/grabs.rs @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::utils::prelude::*; +use super::Shell; + +use smithay::{ + backend::renderer::{Renderer, ImportAll}, + desktop::{Kind, Window, draw_window, space::{RenderElement, SpaceOutputTuple}}, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState, + wayland_server::DisplayHandle, + }, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale}, + wayland::{ + seat::{ + AxisFrame, ButtonEvent, MotionEvent, PointerGrab, PointerGrabStartData, + PointerInnerHandle, + }, + seat::{Seat, Focus}, + output::Output, + Serial, + }, +}; +use cosmic_protocols::workspace::v1::server::zcosmic_workspace_handle_v1::State as WState; +use std::cell::RefCell; + +impl Shell { + pub fn move_request( + &mut self, + window: &Window, + seat: &Seat, + serial: Serial, + start_data: PointerGrabStartData, + ) { + // TODO touch grab + if let Some(pointer) = seat.get_pointer() { + let workspace = self.space_for_surface_mut(window.toplevel().wl_surface()).unwrap(); + if workspace.fullscreen.values().any(|w| w == window) { + return; + } + + let pos = pointer.current_location(); + let output = workspace.space.outputs_for_window(&window) + .into_iter() + .find(|o| o.geometry().contains(pos.to_i32_round())) + .unwrap(); + let mut initial_window_location = workspace.space.window_location(&window).unwrap(); + + let output = match &window.toplevel() { + Kind::Xdg(surface) => { + // If surface is maximized then unmaximize it + let current_state = surface.current_state(); + if current_state.states.contains(XdgState::Maximized) { + workspace.floating_layer.unmaximize_request(&mut workspace.space, window); + let new_size = surface.with_pending_state(|state| state.size); + let ratio = pos.x / output.geometry().size.w as f64; + + initial_window_location = new_size.map(|size| ( + pos.x - (size.w as f64 * ratio), + pos.y, + ).into()).unwrap_or_else(|| pos).to_i32_round(); + } + + output + } + }; + + let was_tiled = if workspace.tiling_layer.windows.contains(&window) { + workspace + .tiling_layer + .unmap_window(&mut workspace.space, &window); + true + } else { + workspace + .floating_layer + .unmap_window(&mut workspace.space, &window); + false + }; + + let workspace_handle = workspace.handle; + let workspace_is_empty = workspace.space.windows().next().is_none(); + + if workspace_is_empty { + self.workspace_state.update().add_workspace_state(&workspace_handle, WState::Hidden); + } + self.toplevel_info_state + .toplevel_leave_workspace(&window, &workspace_handle); + self.toplevel_info_state + .toplevel_leave_output(&window, &output); + + + let state = MoveGrabState { + window: window.clone(), + was_tiled, + initial_cursor_location: pointer.current_location(), + initial_window_location, + }; + let grab = MoveSurfaceGrab::new(start_data, window.clone(), seat); + + *seat.user_data().get::().unwrap().borrow_mut() = Some(state); + pointer.set_grab(grab, serial, Focus::Clear); + } + } + + fn drop_move( + &mut self, + dh: &DisplayHandle, + seat: &Seat, + + output: &Output, + ) { + if let Some(move_state) = seat.user_data().get::().unwrap().borrow_mut().take() { + let pointer = seat.get_pointer().unwrap(); + let window = move_state.window; + + if window.alive() { + let delta = pointer.current_location() - move_state.initial_cursor_location; + let window_location = (move_state.initial_window_location.to_f64() + delta).to_i32_round(); + let surface = window.toplevel().wl_surface().clone(); + + let workspace_handle = self.active_space(output).handle; + self.workspace_state + .update() + .remove_workspace_state(&workspace_handle, WState::Hidden); + self.toplevel_info_state + .toplevel_enter_workspace(&window, &workspace_handle); + self.toplevel_info_state + .toplevel_enter_output(&window, &output); + + let workspace = self.active_space_mut(output); + if move_state.was_tiled { + let focus_stack = workspace.focus_stack(&seat); + workspace.tiling_layer.map_window( + &mut workspace.space, + window, + &seat, + focus_stack.iter(), + ); + } else { + workspace + .floating_layer + .map_window(&mut workspace.space, window, &seat, window_location); + } + + self.set_focus(dh, Some(&surface), &seat, None); + + for window in self.active_space(output).space.windows() { + self.update_reactive_popups(window); + } + } + } + } +} + +pub type SeatMoveGrabState = RefCell>; + +pub struct MoveGrabState { + window: Window, + was_tiled: bool, + initial_cursor_location: Point, + initial_window_location: Point, +} + +pub struct MoveGrabRenderElement { + seat_id: usize, + window: Window, + window_location: Point, +} + +impl RenderElement for MoveGrabRenderElement +where + R: Renderer + ImportAll, + ::TextureId: 'static, +{ + fn id(&self) -> usize { + self.seat_id + } + + fn location(&self, scale: impl Into>) -> Point { + (self.window_location - self.window.geometry().loc.to_f64()).to_physical(scale) + } + + fn geometry(&self, scale: impl Into>) -> Rectangle { + let scale = scale.into(); + self.window.physical_bbox_with_popups(RenderElement::::location(self, scale), scale) + } + + fn accumulated_damage( + &self, + scale: impl Into>, + for_values: Option>, + ) -> Vec> { + let scale = scale.into(); + self.window.accumulated_damage(RenderElement::::location(self, scale), scale, for_values.map(|t| (t.0, t.1))) + } + + fn opaque_regions( + &self, + scale: impl Into>, + ) -> Option>> { + let scale = scale.into(); + self.window.opaque_regions(RenderElement::::location(self, scale), scale) + } + + fn draw( + &self, + renderer: &mut R, + frame: &mut ::Frame, + scale: impl Into>, + position: Point, + damage: &[Rectangle], + log: &slog::Logger, + ) -> Result<(), ::Error> { + draw_window(renderer, frame, &self.window, scale, position, damage, log) + } +} + +impl MoveGrabState { + pub fn render(&self, seat: &Seat, output: &Output) -> Option + where + I: From + { + let cursor_at = seat.get_pointer().unwrap().current_location(); + if !output.geometry().contains(cursor_at.to_i32_round()) { + return None; + } + + let delta = cursor_at - self.initial_cursor_location; + let window_location = self.initial_window_location.to_f64() + delta - output.geometry().loc.to_f64(); + Some(I::from(MoveGrabRenderElement { + seat_id: seat.id(), + window: self.window.clone(), + window_location, + })) + } +} + +pub struct MoveSurfaceGrab { + window: Window, + start_data: PointerGrabStartData, + seat: Seat, +} + +impl PointerGrab for MoveSurfaceGrab { + fn motion( + &mut self, + state: &mut State, + dh: &DisplayHandle, + handle: &mut PointerInnerHandle<'_, State>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus + handle.motion(event.location, None, event.serial, event.time); + if !self.window.alive() { + self.ungrab(dh, state, handle, event.serial, event.time); + } + } + + fn button( + &mut self, + state: &mut State, + dh: &DisplayHandle, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(event.button, event.state, event.serial, event.time); + if handle.current_pressed().is_empty() { + self.ungrab(dh, state, handle, event.serial, event.time); + } + } + + fn axis( + &mut self, + _state: &mut State, + _dh: &DisplayHandle, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(details); + } + + fn start_data(&self) -> &PointerGrabStartData { + &self.start_data + } +} + +impl MoveSurfaceGrab { + pub fn new( + start_data: PointerGrabStartData, + window: Window, + seat: &Seat, + ) -> MoveSurfaceGrab { + MoveSurfaceGrab { + window, + start_data, + seat: seat.clone(), + } + } + + fn ungrab( + &mut self, + dh: &DisplayHandle, + state: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + serial: Serial, + time: u32, + ) { + // No more buttons are pressed, release the grab. + let output = active_output(&self.seat, &state.common); + let dh = dh.clone(); + let seat = self.seat.clone(); + + state.common.event_loop_handle.insert_idle(move |data| { + data.state.common.shell.drop_move(&dh, &seat, &output); + }); + handle.unset_grab(serial, time); + } +} diff --git a/src/shell/layout/floating/grabs.rs b/src/shell/layout/floating/grabs.rs index 34b50691..608d00da 100644 --- a/src/shell/layout/floating/grabs.rs +++ b/src/shell/layout/floating/grabs.rs @@ -19,81 +19,6 @@ use smithay::{ }; use std::{cell::RefCell, convert::TryFrom, sync::Mutex}; -pub struct MoveSurfaceGrab { - start_data: PointerGrabStartData, - window: Window, - initial_window_location: Point, - delta: Point, -} - -impl PointerGrab for MoveSurfaceGrab { - fn motion( - &mut self, - data: &mut State, - _dh: &DisplayHandle, - handle: &mut PointerInnerHandle<'_, State>, - event: &MotionEvent, - ) { - // While the grab is active, no client has pointer focus - handle.motion(event.location, None, event.serial, event.time); - self.delta = event.location - self.start_data.location; - - if let Some(workspace) = data - .common - .shell - .space_for_surface_mut(self.window.toplevel().wl_surface()) - { - let new_location = (self.initial_window_location.to_f64() + self.delta).to_i32_round(); - workspace - .space - .map_window(&self.window, new_location, super::FLOATING_INDEX, true); - } - } - - fn button( - &mut self, - _data: &mut State, - _dh: &DisplayHandle, - handle: &mut PointerInnerHandle<'_, State>, - event: &ButtonEvent, - ) { - handle.button(event.button, event.state, event.serial, event.time); - if handle.current_pressed().is_empty() { - // No more buttons are pressed, release the grab. - handle.unset_grab(event.serial, event.time); - } - } - - fn axis( - &mut self, - _data: &mut State, - _dh: &DisplayHandle, - handle: &mut PointerInnerHandle<'_, State>, - details: AxisFrame, - ) { - handle.axis(details) - } - - fn start_data(&self) -> &PointerGrabStartData { - &self.start_data - } -} - -impl MoveSurfaceGrab { - pub fn new( - start_data: PointerGrabStartData, - window: Window, - initial_window_location: Point, - ) -> MoveSurfaceGrab { - MoveSurfaceGrab { - start_data, - window, - initial_window_location, - delta: (0.0, 0.0).into(), - } - } -} - bitflags::bitflags! { struct ResizeEdge: u32 { const NONE = 0; diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 824c0e24..b4c455d9 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -5,7 +5,7 @@ use smithay::{ reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{ ResizeEdge, State as XdgState, }, - utils::{IsAlive, Rectangle, Logical}, + utils::{IsAlive, Rectangle, Point, Logical}, wayland::{ compositor::with_states, output::Output, @@ -40,9 +40,9 @@ impl FloatingLayout { Default::default() } - pub fn map_window(&mut self, space: &mut Space, window: Window, seat: &Seat) { + pub fn map_window(&mut self, space: &mut Space, window: Window, seat: &Seat, position: impl Into>>) { if let Some(output) = super::output_from_seat(Some(seat), space) { - self.map_window_internal(space, window, &output); + self.map_window_internal(space, window, &output, position.into()); } else { self.pending_windows.push(window); } @@ -52,13 +52,13 @@ impl FloatingLayout { self.pending_windows.retain(|w| w.toplevel().alive()); if let Some(output) = super::output_from_seat(None, space) { for window in std::mem::take(&mut self.pending_windows).into_iter() { - self.map_window_internal(space, window, &output); + self.map_window_internal(space, window, &output, None); } } // TODO make sure all windows are still visible on any output or move them } - fn map_window_internal(&mut self, space: &mut Space, window: Window, output: &Output) { + fn map_window_internal(&mut self, space: &mut Space, window: Window, output: &Output, position: Option>) { let last_geometry = window.user_data().get::().map(|u| u.lock().unwrap().last_geometry); let mut win_geo = window.geometry(); @@ -112,7 +112,7 @@ impl FloatingLayout { } } - let position = last_geometry.map(|g| g.loc).unwrap_or_else(|| ( + let position = position.or_else(|| last_geometry.map(|g| g.loc)).unwrap_or_else(|| ( geometry.loc.x + (geometry.size.w / 2) - (win_geo.size.w / 2) + win_geo.loc.x, geometry.loc.y + (geometry.size.h / 2) - (win_geo.size.h / 2) + win_geo.loc.y, ).into()); @@ -209,49 +209,6 @@ impl FloatingLayout { } } - pub fn move_request( - &mut self, - space: &mut Space, - window: &Window, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - ) { - if let Some(pointer) = seat.get_pointer() { - let mut initial_window_location = space.window_location(&window).unwrap(); - - #[allow(irrefutable_let_patterns)] - if let Kind::Xdg(surface) = &window.toplevel() { - // If surface is maximized then unmaximize it - let current_state = surface.current_state(); - if current_state.states.contains(XdgState::Maximized) { - surface.with_pending_state(|state| { - state.states.unset(XdgState::Maximized); - state.size = None; - }); - - surface.send_configure(); - - // TODO: The mouse location should be mapped to a new window size - // For example, you could: - // 1) transform mouse pointer position from compositor space to window space (location relative) - // 2) divide the x coordinate by width of the window to get the percentage - // - 0.0 would be on the far left of the window - // - 0.5 would be in middle of the window - // - 1.0 would be on the far right of the window - // 3) multiply the percentage by new window width - // 4) by doing that, drag will look a lot more natural - let pos = pointer.current_location(); - initial_window_location = (pos.x as i32, pos.y as i32).into(); - } - } - - let grab = MoveSurfaceGrab::new(start_data, window.clone(), initial_window_location); - - pointer.set_grab(grab, serial, Focus::Clear); - } - } - pub fn resize_request( &mut self, space: &mut Space, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 66dbb280..2a348b6b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -35,6 +35,7 @@ use crate::{ pub const MAX_WORKSPACES: usize = 10; pub mod focus; pub mod layout; +pub mod grabs; mod workspace; pub use self::workspace::*; @@ -561,7 +562,7 @@ impl Shell { if layout::should_be_floating(&window) { workspace .floating_layer - .map_window(&mut workspace.space, window, &seat); + .map_window(&mut workspace.space, window, &seat, None); } else { let focus_stack = workspace.focus_stack(&seat); workspace.tiling_layer.map_window( @@ -627,6 +628,7 @@ impl Shell { let maybe_window = workspace.focus_stack(seat).last(); if let Some(window) = maybe_window { + let mut workspace_state = self.workspace_state.update(); workspace .floating_layer .unmap_window(&mut workspace.space, &window); @@ -635,15 +637,19 @@ impl Shell { .unmap_window(&mut workspace.space, &window); self.toplevel_info_state .toplevel_leave_workspace(&window, &workspace.handle); + if workspace.space.windows().next().is_none() { + workspace_state.add_workspace_state(&workspace.handle, WState::Hidden); + } let new_workspace = &mut self.spaces[idx]; + workspace_state.remove_workspace_state(&new_workspace.handle, WState::Hidden); self.toplevel_info_state .toplevel_enter_workspace(&window, &new_workspace.handle); let focus_stack = new_workspace.focus_stack(&seat); if layout::should_be_floating(&window) { new_workspace .floating_layer - .map_window(&mut new_workspace.space, window, &seat); + .map_window(&mut new_workspace.space, window, &seat, None); } else { new_workspace.tiling_layer.map_window( &mut new_workspace.space, diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index fc2f9933..bcf91b9b 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -80,22 +80,6 @@ impl Workspace { } } - pub fn move_request( - &mut self, - window: &Window, - seat: &Seat, - serial: Serial, - start_data: PointerGrabStartData, - ) { - if self.fullscreen.values().any(|w| w == window) { - return; - } - if self.floating_layer.windows.contains(window) { - self.floating_layer - .move_request(&mut self.space, window, seat, serial, start_data) - } - } - pub fn resize_request( &mut self, window: &Window, @@ -189,7 +173,7 @@ impl Workspace { if self.tiling_enabled { for window in self.tiling_layer.windows.clone().into_iter() { self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer.map_window(&mut self.space, window, seat); + self.floating_layer.map_window(&mut self.space, window, seat, None); } self.tiling_enabled = false; } else { @@ -207,7 +191,7 @@ impl Workspace { if let Some(window) = self.focus_stack(seat).iter().next().cloned() { if self.tiling_layer.windows.contains(&window) { self.tiling_layer.unmap_window(&mut self.space, &window); - self.floating_layer.map_window(&mut self.space, window, seat); + self.floating_layer.map_window(&mut self.space, window, seat, None); } else if self.floating_layer.windows.contains(&window) { let focus_stack = self.focus_stack(seat); self.floating_layer.unmap_window(&mut self.space, &window); diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 70934c7c..1b21f7b6 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -181,7 +181,7 @@ impl XdgShellHandler for State { .unwrap() .clone(); - workspace.move_request(&window, &seat, serial, start_data); + self.common.shell.move_request(&window, &seat, serial, start_data); } }