diff --git a/src/input/mod.rs b/src/input/mod.rs index 813937bd..4e700b0a 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -16,7 +16,7 @@ use crate::{ target::{KeyboardFocusTarget, PointerFocusTarget}, Stage, }, - grabs::{ReleaseMode, ResizeEdge}, + grabs::{ReleaseMode, ResizeEdge, UngrabOnPointerUp}, layout::{ floating::ResizeGrabMarker, tiling::{NodeDesc, TilingLayout}, @@ -31,7 +31,7 @@ use crate::{ }; use calloop::{ timer::{TimeoutAction, Timer}, - RegistrationToken, + LoopHandle, RegistrationToken, }; use cosmic_comp_config::workspace::WorkspaceLayout; use cosmic_settings_config::shortcuts; @@ -58,7 +58,8 @@ use smithay::{ }, output::Output, reexports::{ - input::Device as InputDevice, wayland_server::protocol::wl_shm::Format as ShmFormat, + input::Device as InputDevice, + wayland_server::protocol::{wl_shm::Format as ShmFormat, wl_surface::WlSurface}, }, utils::{Point, Rectangle, Serial, SERIAL_COUNTER}, wayland::{ @@ -677,6 +678,66 @@ impl State { } } + fn window_menu( + seat: Seat, + evlh: &LoopHandle, + surface: WlSurface, + serial: Serial, + ) { + evlh.insert_idle(move |state| { + let shell = state.common.shell.write().unwrap(); + + let grab = if let Some(mapped) = + shell.element_for_surface(&surface).cloned() + { + let position = if let Some((output, set)) = + shell.workspaces.sets.iter().find(|(_, set)| { + set.sticky_layer + .mapped() + .any(|m| m == &mapped) + }) { + set.sticky_layer + .element_geometry(&mapped) + .unwrap() + .loc + .to_global(output) + } else if let Some(workspace) = + shell.space_for(&mapped) + { + let Some(elem_geo) = + workspace.element_geometry(&mapped) + else { + return; + }; + elem_geo.loc.to_global(&workspace.output) + } else { + return; + }; + let cursor = seat + .get_pointer() + .unwrap() + .current_location() + .to_i32_round(); + + shell.menu_request( + &surface, + &seat, + serial, + cursor - position.as_logical(), + false, + &state.common.config, + &state.common.event_loop_handle, + false, + ) + } else { + None + }; + drop(shell); + + dispatch_grab(grab, seat, serial, state); + }); + } + if let Some(mouse_button) = mouse_button { match mouse_button { smithay::backend::input::MouseButton::Left => { @@ -696,63 +757,104 @@ impl State { &state.common.xdg_activation_state, false, ); + drop(shell); + + seat_clone + .user_data() + .get_or_insert(UngrabOnPointerUp::new) + .set(true); dispatch_grab( res, seat_clone, serial, state, ); }, ); } - smithay::backend::input::MouseButton::Right => { + smithay::backend::input::MouseButton::Middle => { supress_button(); - self.common.event_loop_handle.insert_idle( - move |state| { - let mut shell = - state.common.shell.write().unwrap(); - let Some(target_elem) = - shell.element_for_surface(&surface) - else { - return; - }; - let Some(geom) = - shell.space_for(target_elem).and_then( - |f| f.element_geometry(target_elem), - ) - else { - return; - }; - let geom = geom.to_f64(); - let center = - geom.loc + geom.size.downscale(2.0); - let offset = center.to_global(&output) - - global_position; - let edge = match ( - offset.x > 0.0, - offset.y > 0.0, - ) { - (true, true) => ResizeEdge::TOP_LEFT, - (false, true) => ResizeEdge::TOP_RIGHT, - (true, false) => { - ResizeEdge::BOTTOM_LEFT - } - (false, false) => { - ResizeEdge::BOTTOM_RIGHT - } - }; - let res = shell.resize_request( - &surface, - &seat_clone, - serial, - edge, - false, - ); - drop(shell); - dispatch_grab( - res, seat_clone, serial, state, - ); - }, + window_menu( + seat_clone, + &self.common.event_loop_handle, + surface, + serial, ); } + smithay::backend::input::MouseButton::Right => { + supress_button(); + if seat + .get_keyboard() + .unwrap() + .modifier_state() + .shift + { + window_menu( + seat_clone, + &self.common.event_loop_handle, + surface, + serial, + ); + } else { + self.common.event_loop_handle.insert_idle( + move |state| { + let mut shell = + state.common.shell.write().unwrap(); + let Some(target_elem) = + shell.element_for_surface(&surface) + else { + return; + }; + let Some(geom) = shell + .space_for(target_elem) + .and_then(|f| { + f.element_geometry(target_elem) + }) + else { + return; + }; + let geom = geom.to_f64(); + let center = + geom.loc + geom.size.downscale(2.0); + let offset = center.to_global(&output) + - global_position; + let edge = match ( + offset.x > 0.0, + offset.y > 0.0, + ) { + (true, true) => { + ResizeEdge::TOP_LEFT + } + (false, true) => { + ResizeEdge::TOP_RIGHT + } + (true, false) => { + ResizeEdge::BOTTOM_LEFT + } + (false, false) => { + ResizeEdge::BOTTOM_RIGHT + } + }; + let res = shell.resize_request( + &surface, + &seat_clone, + serial, + edge, + false, + ); + drop(shell); + + seat_clone + .user_data() + .get_or_insert( + UngrabOnPointerUp::new, + ) + .set(true); + dispatch_grab( + res, seat_clone, serial, state, + ); + }, + ); + } + } _ => {} } } @@ -787,7 +889,12 @@ impl State { ); ptr.frame(self); } else if event.state() == ButtonState::Released { - ptr.unset_grab(self, serial, event.time_msec()) + if let Some(ungrab) = seat.user_data().get::() { + if ungrab.get() { + ungrab.set(false); + ptr.unset_grab(self, serial, event.time_msec()) + } + } } } InputEvent::PointerAxis { event, .. } => { diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index e14b8a58..a8349f85 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -872,6 +872,7 @@ impl Program for CosmicStackInternal { true, &state.common.config, &state.common.event_loop_handle, + true, ); std::mem::drop(shell); @@ -914,6 +915,7 @@ impl Program for CosmicStackInternal { false, &state.common.config, &state.common.event_loop_handle, + true, ); std::mem::drop(shell); diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 57b52578..d368779d 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -515,6 +515,7 @@ impl Program for CosmicWindowInternal { false, &state.common.config, &state.common.event_loop_handle, + true, ); std::mem::drop(shell); diff --git a/src/shell/grabs/menu/mod.rs b/src/shell/grabs/menu/mod.rs index c1ca0c2b..8e7187fb 100644 --- a/src/shell/grabs/menu/mod.rs +++ b/src/shell/grabs/menu/mod.rs @@ -161,7 +161,10 @@ impl Item { } } -/// Menu that comes up when right-clicking an application header bar +/// Menu that comes up when: +/// - Right-clicking an application header bar +/// - Super + Middle-clicking a window +/// - Super + Shift + Right-clicking a window pub struct ContextMenu { items: Vec, selected: AtomicBool, diff --git a/src/shell/grabs/mod.rs b/src/shell/grabs/mod.rs index def21cfe..cafaeff2 100644 --- a/src/shell/grabs/mod.rs +++ b/src/shell/grabs/mod.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use cosmic_settings_config::shortcuts; use smithay::{ input::{ @@ -67,6 +69,22 @@ pub enum ReleaseMode { NoMouseButtons, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UngrabOnPointerUp(Cell); +impl UngrabOnPointerUp { + pub fn new() -> Self { + Self(Cell::new(false)) + } + + pub fn get(&self) -> bool { + self.0.get() + } + + pub fn set(&self, ungrab: bool) { + self.0.set(ungrab); + } +} + mod menu; pub use self::menu::*; mod moving; diff --git a/src/shell/mod.rs b/src/shell/mod.rs index c9f17634..f19e12d7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2564,10 +2564,11 @@ impl Shell { target_stack: bool, config: &Config, evlh: &LoopHandle<'static, State>, + client_initiated: bool, ) -> Option<(MenuGrab, Focus)> { let serial = serial.into(); let Some(GrabStartData::Pointer(start_data)) = - check_grab_preconditions(&seat, surface, serial, true) + check_grab_preconditions(&seat, surface, serial, client_initiated) else { return None; }; diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 7f19913c..23cd4c10 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -439,6 +439,7 @@ impl XdgShellHandler for State { false, &self.common.config, &self.common.event_loop_handle, + true, ); if let Some((grab, focus)) = res { std::mem::drop(shell);