diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index ac39f084..11557e2a 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1,7 +1,10 @@ use super::{CosmicMapped, CosmicSurface, CosmicWindow}; use crate::{ shell::{ - focus::FocusDirection, grabs::MoveGrab, layout::tiling::NodeDesc, Direction, Shell, Trigger, + focus::FocusDirection, + grabs::{MoveGrab, ReleaseMode}, + layout::tiling::NodeDesc, + Direction, Shell, Trigger, }, state::State, utils::iced::{IcedElement, Program}, @@ -650,7 +653,13 @@ impl Program for CosmicStackInternal { .wl_surface() { loop_handle.insert_idle(move |state| { - Shell::move_request(state, &surface, &seat, serial); + Shell::move_request( + state, + &surface, + &seat, + serial, + ReleaseMode::NoMouseButtons, + ); }); } } @@ -1113,6 +1122,7 @@ impl PointerTarget for CosmicStack { pos.to_i32_round() - Point::from((elem_geo.size.w / 2, 24)), indicator_thickness, was_tiled, + ReleaseMode::NoMouseButtons, ); if grab.is_tiling_grab() { data.common.shell.set_overview_mode( diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index e56322c4..20d47cce 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -1,5 +1,5 @@ use crate::{ - shell::Shell, + shell::{grabs::ReleaseMode, Shell}, state::State, utils::{ iced::{IcedElement, Program}, @@ -255,7 +255,13 @@ impl Program for CosmicWindowInternal { if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { if let Some(surface) = self.window.wl_surface() { loop_handle.insert_idle(move |state| { - Shell::move_request(state, &surface, &seat, serial); + Shell::move_request( + state, + &surface, + &seat, + serial, + ReleaseMode::NoMouseButtons, + ); }); } } diff --git a/src/shell/grabs/mod.rs b/src/shell/grabs/mod.rs index 92776337..582b98c5 100644 --- a/src/shell/grabs/mod.rs +++ b/src/shell/grabs/mod.rs @@ -17,6 +17,11 @@ use super::{ layout::{floating::ResizeSurfaceGrab, tiling::ResizeForkGrab}, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReleaseMode { + Click, + NoMouseButtons, +} mod moving; pub use self::moving::*; diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 78b778f6..e29ff43c 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -19,9 +19,12 @@ use crate::{ use cosmic::theme::CosmicTheme; use smithay::{ - backend::renderer::{ - element::{utils::RescaleRenderElement, AsRenderElements, RenderElement}, - ImportAll, ImportMem, Renderer, + backend::{ + input::ButtonState, + renderer::{ + element::{utils::RescaleRenderElement, AsRenderElements, RenderElement}, + ImportAll, ImportMem, Renderer, + }, }, desktop::space::SpaceElement, input::{ @@ -46,6 +49,8 @@ use std::{ time::{Duration, Instant}, }; +use super::ReleaseMode; + pub type SeatMoveGrabState = RefCell>; const RESCALE_ANIMATION_DURATION: f64 = 150.0; @@ -217,6 +222,7 @@ pub struct MoveGrab { cursor_output: Output, window_outputs: HashSet, tiling: bool, + release: ReleaseMode, } impl PointerGrab for MoveGrab { @@ -334,8 +340,17 @@ impl PointerGrab for MoveGrab { event: &ButtonEvent, ) { handle.button(state, event); - if handle.current_pressed().is_empty() { - self.ungrab(state, handle, event.serial, event.time); + match self.release { + ReleaseMode::NoMouseButtons => { + if handle.current_pressed().is_empty() { + self.ungrab(state, handle, event.serial, event.time); + } + } + ReleaseMode::Click => { + if event.state == ButtonState::Pressed { + self.ungrab(state, handle, event.serial, event.time); + } + } } } @@ -438,6 +453,7 @@ impl MoveGrab { initial_window_location: Point, indicator_thickness: u8, was_tiled: bool, + release: ReleaseMode, ) -> MoveGrab { let output = seat.active_output(); let mut outputs = HashSet::new(); @@ -473,6 +489,7 @@ impl MoveGrab { window_outputs: outputs, cursor_output: output, tiling: was_tiled, + release, } } diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 0bf281cc..bda485be 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -4,11 +4,15 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::{ shell::{ - element::CosmicMapped, focus::target::PointerFocusTarget, grabs::ResizeEdge, CosmicSurface, + element::CosmicMapped, + focus::target::PointerFocusTarget, + grabs::{ReleaseMode, ResizeEdge}, + CosmicSurface, }, utils::prelude::*, }; use smithay::{ + backend::input::ButtonState, desktop::space::SpaceElement, input::{ pointer::{ @@ -20,7 +24,7 @@ use smithay::{ }, Seat, }, - utils::{IsAlive, Logical, Point, Rectangle, Size}, + utils::{IsAlive, Logical, Point, Rectangle, Serial, Size}, }; /// Information about the resize operation. @@ -50,6 +54,7 @@ pub struct ResizeSurfaceGrab { edges: ResizeEdge, initial_window_size: Size, last_window_size: Size, + release: ReleaseMode, } impl PointerGrab for ResizeSurfaceGrab { @@ -140,36 +145,16 @@ impl PointerGrab for ResizeSurfaceGrab { event: &ButtonEvent, ) { handle.button(data, event); - if handle.current_pressed().is_empty() { - // No more buttons are pressed, release the grab. - self.seat - .user_data() - .get::() - .unwrap() - .0 - .store(false, Ordering::SeqCst); - handle.unset_grab(data, event.serial, event.time, true); - - // If toplevel is dead, we can't resize it, so we return early. - if !self.window.alive() { - return; + match self.release { + ReleaseMode::NoMouseButtons => { + if handle.current_pressed().is_empty() { + self.ungrab(data, handle, event.serial, event.time); + } } - - self.window.set_resizing(false); - self.window.set_geometry(Rectangle::from_loc_and_size( - match self.window.active_window() { - CosmicSurface::X11(s) => s.geometry().loc.as_global(), - _ => (0, 0).into(), - }, - self.last_window_size.as_global(), - )); - self.window.configure(); - - let mut resize_state = self.window.resize_state.lock().unwrap(); - if let Some(ResizeState::Resizing(resize_data)) = *resize_state { - *resize_state = Some(ResizeState::WaitingForCommit(resize_data)); - } else { - panic!("invalid resize state: {:?}", resize_state); + ReleaseMode::Click => { + if event.state == ButtonState::Pressed { + self.ungrab(data, handle, event.serial, event.time); + } } } } @@ -280,6 +265,7 @@ impl ResizeSurfaceGrab { initial_window_location: Point, initial_window_size: Size, seat: &Seat, + release: ReleaseMode, ) -> ResizeSurfaceGrab { let resize_state = ResizeState::Resizing(ResizeData { edges, @@ -300,6 +286,7 @@ impl ResizeSurfaceGrab { edges, initial_window_size, last_window_size: initial_window_size, + release, } } @@ -375,4 +362,43 @@ impl ResizeSurfaceGrab { } } } + + fn ungrab( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + serial: Serial, + time: u32, + ) { + // No more buttons are pressed, release the grab. + self.seat + .user_data() + .get::() + .unwrap() + .0 + .store(false, Ordering::SeqCst); + handle.unset_grab(data, serial, time, true); + + // If toplevel is dead, we can't resize it, so we return early. + if !self.window.alive() { + return; + } + + self.window.set_resizing(false); + self.window.set_geometry(Rectangle::from_loc_and_size( + match self.window.active_window() { + CosmicSurface::X11(s) => s.geometry().loc.as_global(), + _ => (0, 0).into(), + }, + self.last_window_size.as_global(), + )); + self.window.configure(); + + let mut resize_state = self.window.resize_state.lock().unwrap(); + if let Some(ResizeState::Resizing(resize_data)) = *resize_state { + *resize_state = Some(ResizeState::WaitingForCommit(resize_data)); + } else { + panic!("invalid resize state: {:?}", resize_state); + } + } } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 56311c70..2560aaa5 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -32,7 +32,7 @@ use crate::{ CosmicMapped, CosmicMappedRenderElement, CosmicWindow, }, focus::{target::KeyboardFocusTarget, FocusDirection, FocusStackMut}, - grabs::ResizeEdge, + grabs::{ReleaseMode, ResizeEdge}, CosmicSurface, Direction, FocusResult, MoveResult, ResizeDirection, ResizeMode, }, state::State, @@ -459,6 +459,7 @@ impl FloatingLayout { seat: &Seat, start_data: PointerGrabStartData, edges: ResizeEdge, + release: ReleaseMode, ) -> Option { if seat.get_pointer().is_some() { let location = self.space.element_location(&mapped).unwrap().as_local(); @@ -472,6 +473,7 @@ impl FloatingLayout { location, size, seat, + release, )) } else { None diff --git a/src/shell/layout/tiling/grabs/resize.rs b/src/shell/layout/tiling/grabs/resize.rs index aaf7c933..72ae459f 100644 --- a/src/shell/layout/tiling/grabs/resize.rs +++ b/src/shell/layout/tiling/grabs/resize.rs @@ -2,7 +2,7 @@ use crate::{ backend::render::cursor::{CursorShape, CursorState}, - shell::{focus::target::PointerFocusTarget, layout::Orientation}, + shell::{focus::target::PointerFocusTarget, grabs::ReleaseMode, layout::Orientation}, utils::prelude::*, }; use id_tree::{NodeId, Tree}; @@ -74,20 +74,19 @@ impl PointerTarget for ResizeForkTarget { let location = pointer.current_location(); pointer.set_grab( state, - ResizeForkGrab { - start_data: PointerGrabStartData { + ResizeForkGrab::new( + PointerGrabStartData { focus: None, button, location, }, - old_tree: None, - accumulated_delta: 0.0, - last_loc: location, + location.as_global(), node, - output, left_up_idx, orientation, - }, + output, + ReleaseMode::NoMouseButtons, + ), serial, Focus::Clear, ) @@ -117,13 +116,38 @@ impl PointerTarget for ResizeForkTarget { pub struct ResizeForkGrab { start_data: PointerGrabStartData, - last_loc: Point, + last_loc: Point, old_tree: Option>, accumulated_delta: f64, node: NodeId, output: WeakOutput, left_up_idx: usize, orientation: Orientation, + release: ReleaseMode, +} + +impl ResizeForkGrab { + pub fn new( + start_data: PointerGrabStartData, + pointer_loc: Point, + node: NodeId, + idx: usize, + orientation: Orientation, + output: WeakOutput, + release: ReleaseMode, + ) -> ResizeForkGrab { + ResizeForkGrab { + start_data, + last_loc: pointer_loc, + old_tree: None, + accumulated_delta: 0.0, + node, + output, + left_up_idx: idx, + orientation, + release, + } + } } impl PointerGrab for ResizeForkGrab { @@ -137,7 +161,7 @@ impl PointerGrab for ResizeForkGrab { // While the grab is active, no client has pointer focus handle.motion(data, None, event); - let delta = event.location - self.last_loc; + let delta = event.location - self.last_loc.as_logical(); if let Some(output) = self.output.upgrade() { let tiling_layer = &mut data.common.shell.active_space_mut(&output).tiling_layer; @@ -234,7 +258,7 @@ impl PointerGrab for ResizeForkGrab { _ => unreachable!(), } - self.last_loc = event.location; + self.last_loc = event.location.as_global(); let blocker = TilingLayout::update_positions(&output, tree, gaps); tiling_layer.pending_blockers.extend(blocker); } else { @@ -261,9 +285,17 @@ impl PointerGrab for ResizeForkGrab { event: &ButtonEvent, ) { handle.button(data, event); - if handle.current_pressed().is_empty() { - // No more buttons are pressed, release the grab. - handle.unset_grab(data, event.serial, event.time, true); + match self.release { + ReleaseMode::NoMouseButtons => { + if handle.current_pressed().is_empty() { + handle.unset_grab(data, event.serial, event.time, true); + } + } + ReleaseMode::Click => { + if event.state == ButtonState::Pressed { + handle.unset_grab(data, event.serial, event.time, true); + } + } } } diff --git a/src/shell/mod.rs b/src/shell/mod.rs index af002618..aa1806b7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1883,9 +1883,10 @@ impl Shell { surface: &WlSurface, seat: &Seat, serial: impl Into>, + release: ReleaseMode, ) { let serial = serial.into(); - if let Some(start_data) = check_grab_preconditions(&seat, surface, serial) { + if let Some(start_data) = check_grab_preconditions(&seat, surface, serial, release) { if let Some(mapped) = state.common.shell.element_for_wl_surface(surface).cloned() { if let Some(workspace) = state.common.shell.space_for_mut(&mapped) { let output = seat.active_output(); @@ -1901,6 +1902,7 @@ impl Shell { &output, start_data, active_hint as u8, + release, ) { let handle = workspace.handle; state @@ -1939,7 +1941,9 @@ impl Shell { edges: ResizeEdge, ) { let serial = serial.into(); - if let Some(start_data) = check_grab_preconditions(&seat, surface, serial) { + if let Some(start_data) = + check_grab_preconditions(&seat, surface, serial, ReleaseMode::NoMouseButtons) + { if let Some(mapped) = state.common.shell.element_for_wl_surface(surface).cloned() { if let Some(workspace) = state.common.shell.space_for_mut(&mapped) { if let Some(grab) = workspace.resize_request(&mapped, &seat, start_data, edges) @@ -2028,32 +2032,41 @@ pub fn check_grab_preconditions( seat: &Seat, surface: &WlSurface, serial: Option, + release: ReleaseMode, ) -> Option> { use smithay::reexports::wayland_server::Resource; // TODO: touch resize. let pointer = seat.get_pointer().unwrap(); - // Check that this surface has a click grab. - if !match serial { - Some(serial) => pointer.has_grab(serial), - None => pointer.is_grabbed(), - } { - return None; - } + let start_data = pointer + .grab_start_data() + .unwrap_or_else(|| PointerGrabStartData { + focus: pointer.current_focus().map(|f| (f, Point::from((0, 0)))), + button: 0x110, + location: pointer.current_location(), + }); - let start_data = pointer.grab_start_data().unwrap(); + if release == ReleaseMode::NoMouseButtons { + // Check that this surface has a click grab. + if !match serial { + Some(serial) => pointer.has_grab(serial), + None => pointer.is_grabbed(), + } { + return None; + } - // If the focus was for a different surface, ignore the request. - if start_data.focus.is_none() - || !start_data - .focus - .as_ref() - .unwrap() - .0 - .same_client_as(&surface.id()) - { - return None; + // If the focus was for a different surface, ignore the request. + if start_data.focus.is_none() + || !start_data + .focus + .as_ref() + .unwrap() + .0 + .same_client_as(&surface.id()) + { + return None; + } } Some(start_data) diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index f7291556..6e1932f3 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -71,7 +71,7 @@ use super::{ target::{KeyboardFocusTarget, PointerFocusTarget, WindowGroup}, FocusDirection, FocusStack, FocusStackMut, }, - grabs::{ResizeEdge, ResizeGrab}, + grabs::{ReleaseMode, ResizeEdge, ResizeGrab}, layout::tiling::{Data, NodeDesc}, CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode, }; @@ -640,7 +640,13 @@ impl Workspace { if self.floating_layer.mapped().any(|m| m == mapped) { self.floating_layer - .resize_request(mapped, seat, start_data.clone(), edges) + .resize_request( + mapped, + seat, + start_data.clone(), + edges, + ReleaseMode::NoMouseButtons, + ) .map(Into::into) } else { None @@ -678,6 +684,7 @@ impl Workspace { output: &Output, start_data: PointerGrabStartData, indicator_thickness: u8, + release: ReleaseMode, ) -> Option { let pointer = seat.get_pointer().unwrap(); let pos = pointer.current_location().as_global(); @@ -720,6 +727,7 @@ impl Workspace { initial_window_location, indicator_thickness, was_tiled.is_some(), + release, )) } diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 70b1bbec..d72fa567 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - shell::{element::CosmicWindow, CosmicMapped, CosmicSurface, ManagedLayer}, + shell::{element::CosmicWindow, grabs::ReleaseMode, CosmicMapped, CosmicSurface, ManagedLayer}, utils::prelude::*, wayland::protocols::screencopy::SessionType, }; @@ -139,7 +139,13 @@ impl XdgShellHandler for State { fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { let seat = Seat::from_resource(&seat).unwrap(); - Shell::move_request(self, surface.wl_surface(), &seat, serial) + Shell::move_request( + self, + surface.wl_surface(), + &seat, + serial, + ReleaseMode::NoMouseButtons, + ) } fn resize_request( diff --git a/src/xwayland.rs b/src/xwayland.rs index 7eea8693..3d2015b2 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -2,7 +2,7 @@ use std::{ffi::OsString, os::unix::io::OwnedFd}; use crate::{ backend::render::cursor::{load_cursor_theme, Cursor, CursorShape}, - shell::{focus::target::KeyboardFocusTarget, CosmicSurface, Shell}, + shell::{focus::target::KeyboardFocusTarget, grabs::ReleaseMode, CosmicSurface, Shell}, state::State, utils::prelude::*, wayland::{ @@ -410,7 +410,7 @@ impl XwmHandler for State { fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) { if let Some(wl_surface) = window.wl_surface() { let seat = self.common.last_active_seat().clone(); - Shell::move_request(self, &wl_surface, &seat, None) + Shell::move_request(self, &wl_surface, &seat, None, ReleaseMode::NoMouseButtons) } }