diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-11 14:01:48 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-11 14:02:37 +0400 |
| commit | bc29256b9d95f265c8f6508e7949c57497835430 (patch) | |
| tree | e8d7dd9c3a43e383fdc9f5fb6850f31e3e3078e6 /src | |
| parent | beba87354a1fd30a95eaaf6c98eec72797e4baa7 (diff) | |
| download | niri-bc29256b9d95f265c8f6508e7949c57497835430.tar.gz niri-bc29256b9d95f265c8f6508e7949c57497835430.tar.bz2 niri-bc29256b9d95f265c8f6508e7949c57497835430.zip | |
Implement Mod+MMB view offset gesture
Diffstat (limited to 'src')
| -rw-r--r-- | src/handlers/xdg_shell.rs | 2 | ||||
| -rw-r--r-- | src/input/mod.rs | 47 | ||||
| -rw-r--r-- | src/input/resize_grab.rs | 2 | ||||
| -rw-r--r-- | src/input/view_offset_grab.rs | 190 | ||||
| -rw-r--r-- | src/layout/mod.rs | 57 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 28 | ||||
| -rw-r--r-- | src/niri.rs | 6 |
7 files changed, 298 insertions, 34 deletions
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 6f36fe8d..1b8194aa 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -128,7 +128,7 @@ impl XdgShellHandler for State { } pointer.set_grab(self, grab, serial, Focus::Clear); - self.niri.interactive_resize_ongoing = true; + self.niri.pointer_grab_ongoing = true; } fn reposition_request( diff --git a/src/input/mod.rs b/src/input/mod.rs index 69650a12..8206890c 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -17,10 +17,10 @@ use smithay::backend::input::{ use smithay::backend::libinput::LibinputInputBackend; use smithay::input::keyboard::{keysyms, FilterResult, Keysym, ModifiersState}; use smithay::input::pointer::{ - AxisFrame, ButtonEvent, CursorImageStatus, Focus, GestureHoldBeginEvent, GestureHoldEndEvent, - GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, - GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, - MotionEvent, RelativeMotionEvent, + AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, Focus, GestureHoldBeginEvent, + GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, + GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, + GrabStartData as PointerGrabStartData, MotionEvent, RelativeMotionEvent, }; use smithay::input::touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent}; use smithay::utils::{Logical, Point, SERIAL_COUNTER}; @@ -28,6 +28,7 @@ use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerCons use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; use self::resize_grab::ResizeGrab; +use self::view_offset_grab::ViewOffsetGrab; use crate::niri::State; use crate::ui::screenshot_ui::ScreenshotUi; use crate::utils::spawning::spawn; @@ -36,6 +37,7 @@ use crate::utils::{center, get_monotonic_time, ResizeEdge}; pub mod resize_grab; pub mod scroll_tracker; pub mod swipe_tracker; +pub mod view_offset_grab; pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400); @@ -1144,7 +1146,7 @@ impl State { }; let grab = ResizeGrab::new(start_data, window.clone()); pointer.set_grab(self, grab, serial, Focus::Clear); - self.niri.interactive_resize_ongoing = true; + self.niri.pointer_grab_ongoing = true; self.niri.cursor_manager.set_cursor_image( CursorImageStatus::Named(edges.cursor_icon()), ); @@ -1163,6 +1165,32 @@ impl State { // FIXME: granular. self.niri.queue_redraw_all(); } + + if event.button() == Some(MouseButton::Middle) && !pointer.is_grabbed() { + let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); + let mod_down = match self.backend.mod_key() { + CompositorMod::Super => mods.logo, + CompositorMod::Alt => mods.alt, + }; + if mod_down { + if let Some(output) = self.niri.output_under_cursor() { + self.niri.layout.view_offset_gesture_begin(&output, false); + + let location = pointer.current_location(); + let start_data = PointerGrabStartData { + focus: None, + button: event.button_code(), + location, + }; + let grab = ViewOffsetGrab::new(start_data); + pointer.set_grab(self, grab, serial, Focus::Clear); + self.niri.pointer_grab_ongoing = true; + self.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll)); + } + } + } }; self.update_pointer_focus(); @@ -1593,7 +1621,7 @@ impl State { if let Some(output) = self.niri.output_under_cursor() { if cx.abs() > cy.abs() { - self.niri.layout.view_offset_gesture_begin(&output); + self.niri.layout.view_offset_gesture_begin(&output, true); } else { self.niri.layout.workspace_switch_gesture_begin(&output); } @@ -1618,7 +1646,7 @@ impl State { let res = self .niri .layout - .view_offset_gesture_update(delta_x, timestamp); + .view_offset_gesture_update(delta_x, timestamp, true); if let Some(output) = res { if let Some(output) = output { self.niri.queue_redraw(&output); @@ -1659,7 +1687,10 @@ impl State { handled = true; } - let res = self.niri.layout.view_offset_gesture_end(event.cancelled()); + let res = self + .niri + .layout + .view_offset_gesture_end(event.cancelled(), Some(true)); if let Some(output) = res { self.niri.queue_redraw(&output); handled = true; diff --git a/src/input/resize_grab.rs b/src/input/resize_grab.rs index 8c761558..38483ca6 100644 --- a/src/input/resize_grab.rs +++ b/src/input/resize_grab.rs @@ -22,7 +22,7 @@ impl ResizeGrab { fn on_ungrab(&mut self, state: &mut State) { state.niri.layout.interactive_resize_end(&self.window); - state.niri.interactive_resize_ongoing = false; + state.niri.pointer_grab_ongoing = false; state .niri .cursor_manager diff --git a/src/input/view_offset_grab.rs b/src/input/view_offset_grab.rs new file mode 100644 index 00000000..4e2d2785 --- /dev/null +++ b/src/input/view_offset_grab.rs @@ -0,0 +1,190 @@ +use std::time::Duration; + +use smithay::input::pointer::{ + AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, + GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, +}; +use smithay::input::SeatHandler; +use smithay::utils::{Logical, Point}; + +use crate::niri::State; + +pub struct ViewOffsetGrab { + start_data: PointerGrabStartData<State>, + last_location: Point<f64, Logical>, +} + +impl ViewOffsetGrab { + pub fn new(start_data: PointerGrabStartData<State>) -> Self { + Self { + last_location: start_data.location, + start_data, + } + } + + fn on_ungrab(&mut self, state: &mut State) { + let res = state + .niri + .layout + .view_offset_gesture_end(false, Some(false)); + if let Some(output) = res { + state.niri.queue_redraw(&output); + } + + state.niri.pointer_grab_ongoing = false; + state + .niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + } +} + +impl PointerGrab<State> for ViewOffsetGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus. + handle.motion(data, None, event); + + let timestamp = Duration::from_millis(u64::from(event.time)); + let delta = event.location - self.last_location; + self.last_location = event.location; + + let res = data + .niri + .layout + .view_offset_gesture_update(-delta.x, timestamp, false); + if let Some(output) = res { + if let Some(output) = output { + data.niri.queue_redraw(&output); + } + } else { + // The resize is no longer ongoing. + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<i32, Logical>)>, + event: &RelativeMotionEvent, + ) { + // While the grab is active, no client has pointer focus. + handle.relative_motion(data, None, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + if handle.current_pressed().is_empty() { + // No more buttons are pressed, release the grab. + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details); + } + + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &PointerGrabStartData<State> { + &self.start_data + } + + fn unset(&mut self, data: &mut State) { + self.on_ungrab(data); + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 0cb8e493..1442dfd0 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1732,7 +1732,7 @@ impl<W: LayoutElement> Layout<W> { None } - pub fn view_offset_gesture_begin(&mut self, output: &Output) { + pub fn view_offset_gesture_begin(&mut self, output: &Output, is_touchpad: bool) { let monitors = match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => monitors, MonitorSet::NoOutputs { .. } => unreachable!(), @@ -1742,11 +1742,11 @@ impl<W: LayoutElement> Layout<W> { for (idx, ws) in monitor.workspaces.iter_mut().enumerate() { // Cancel the gesture on other workspaces. if &monitor.output != output || idx != monitor.active_workspace_idx { - ws.view_offset_gesture_end(true); + ws.view_offset_gesture_end(true, None); continue; } - ws.view_offset_gesture_begin(); + ws.view_offset_gesture_begin(is_touchpad); } } } @@ -1755,6 +1755,7 @@ impl<W: LayoutElement> Layout<W> { &mut self, delta_x: f64, timestamp: Duration, + is_touchpad: bool, ) -> Option<Option<Output>> { let monitors = match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => monitors, @@ -1763,7 +1764,9 @@ impl<W: LayoutElement> Layout<W> { for monitor in monitors { for ws in &mut monitor.workspaces { - if let Some(refresh) = ws.view_offset_gesture_update(delta_x, timestamp) { + if let Some(refresh) = + ws.view_offset_gesture_update(delta_x, timestamp, is_touchpad) + { if refresh { return Some(Some(monitor.output.clone())); } else { @@ -1776,7 +1779,11 @@ impl<W: LayoutElement> Layout<W> { None } - pub fn view_offset_gesture_end(&mut self, cancelled: bool) -> Option<Output> { + pub fn view_offset_gesture_end( + &mut self, + cancelled: bool, + is_touchpad: Option<bool>, + ) -> Option<Output> { let monitors = match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => monitors, MonitorSet::NoOutputs { .. } => return None, @@ -1784,7 +1791,7 @@ impl<W: LayoutElement> Layout<W> { for monitor in monitors { for ws in &mut monitor.workspaces { - if ws.view_offset_gesture_end(cancelled) { + if ws.view_offset_gesture_end(cancelled, is_touchpad) { return Some(monitor.output.clone()); } } @@ -2029,7 +2036,7 @@ impl<W: LayoutElement> Layout<W> { // Cancel the view offset gesture after workspace switches, moves, etc. if ws_idx != mon.active_workspace_idx { - ws.view_offset_gesture_end(false); + ws.view_offset_gesture_end(false, None); } } } @@ -2037,7 +2044,7 @@ impl<W: LayoutElement> Layout<W> { MonitorSet::NoOutputs { workspaces, .. } => { for ws in workspaces { ws.refresh(false); - ws.view_offset_gesture_end(false); + ws.view_offset_gesture_end(false, None); } } } @@ -2351,13 +2358,17 @@ mod tests { ViewOffsetGestureBegin { #[proptest(strategy = "1..=5usize")] output_idx: usize, + is_touchpad: bool, }, ViewOffsetGestureUpdate { #[proptest(strategy = "arbitrary_view_offset_gesture_delta()")] delta: f64, timestamp: Duration, + is_touchpad: bool, + }, + ViewOffsetGestureEnd { + is_touchpad: Option<bool>, }, - ViewOffsetGestureEnd, WorkspaceSwitchGestureBegin { #[proptest(strategy = "1..=5usize")] output_idx: usize, @@ -2619,20 +2630,27 @@ mod tests { layout.move_workspace_to_output(&output); } - Op::ViewOffsetGestureBegin { output_idx: id } => { + Op::ViewOffsetGestureBegin { + output_idx: id, + is_touchpad: normalize, + } => { let name = format!("output{id}"); let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { return; }; - layout.view_offset_gesture_begin(&output); + layout.view_offset_gesture_begin(&output, normalize); } - Op::ViewOffsetGestureUpdate { delta, timestamp } => { - layout.view_offset_gesture_update(delta, timestamp); + Op::ViewOffsetGestureUpdate { + delta, + timestamp, + is_touchpad, + } => { + layout.view_offset_gesture_update(delta, timestamp, is_touchpad); } - Op::ViewOffsetGestureEnd => { + Op::ViewOffsetGestureEnd { is_touchpad } => { // We don't handle cancels in this gesture. - layout.view_offset_gesture_end(false); + layout.view_offset_gesture_end(false, is_touchpad); } Op::WorkspaceSwitchGestureBegin { output_idx: id } => { let name = format!("output{id}"); @@ -3377,8 +3395,13 @@ mod tests { min_max_size: Default::default(), }, Op::FullscreenWindow(1), - Op::ViewOffsetGestureBegin { output_idx: 1 }, - Op::ViewOffsetGestureEnd, + Op::ViewOffsetGestureBegin { + output_idx: 1, + is_touchpad: true, + }, + Op::ViewOffsetGestureEnd { + is_touchpad: Some(true), + }, ]; check_ops(&ops); diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 77f10919..6005d448 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -129,6 +129,8 @@ struct ViewGesture { delta_from_tracker: f64, // The view offset we'll use if needed for activate_prev_column_on_removal. static_view_offset: i32, + /// Whether the gesture is controlled by the touchpad. + is_touchpad: bool, } #[derive(Debug)] @@ -2131,7 +2133,7 @@ impl<W: LayoutElement> Workspace<W> { rv } - pub fn view_offset_gesture_begin(&mut self) { + pub fn view_offset_gesture_begin(&mut self, is_touchpad: bool) { if self.columns.is_empty() { return; } @@ -2141,6 +2143,7 @@ impl<W: LayoutElement> Workspace<W> { tracker: SwipeTracker::new(), delta_from_tracker: self.view_offset as f64, static_view_offset: self.static_view_offset(), + is_touchpad, }; self.view_offset_adj = Some(ViewOffsetAdjustment::Gesture(gesture)); } @@ -2149,14 +2152,23 @@ impl<W: LayoutElement> Workspace<W> { &mut self, delta_x: f64, timestamp: Duration, + is_touchpad: bool, ) -> Option<bool> { let Some(ViewOffsetAdjustment::Gesture(gesture)) = &mut self.view_offset_adj else { return None; }; + if gesture.is_touchpad != is_touchpad { + return None; + } + gesture.tracker.push(delta_x, timestamp); - let norm_factor = self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT; + let norm_factor = if gesture.is_touchpad { + self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT + } else { + 1. + }; let pos = gesture.tracker.pos() * norm_factor; let view_offset = pos + gesture.delta_from_tracker; gesture.current_view_offset = view_offset; @@ -2164,17 +2176,25 @@ impl<W: LayoutElement> Workspace<W> { Some(true) } - pub fn view_offset_gesture_end(&mut self, _cancelled: bool) -> bool { + pub fn view_offset_gesture_end(&mut self, _cancelled: bool, is_touchpad: Option<bool>) -> bool { let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj else { return false; }; + if is_touchpad.map_or(false, |x| gesture.is_touchpad != x) { + return false; + } + // We do not handle cancelling, just like GNOME Shell doesn't. For this gesture, proper // cancelling would require keeping track of the original active column, and then updating // it in all the right places (adding columns, removing columns, etc.) -- quite a bit of // effort and bug potential. - let norm_factor = self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT; + let norm_factor = if gesture.is_touchpad { + self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT + } else { + 1. + }; let velocity = gesture.tracker.velocity() * norm_factor; let pos = gesture.tracker.pos() * norm_factor; let current_view_offset = pos + gesture.delta_from_tracker; diff --git a/src/niri.rs b/src/niri.rs index 8e907aac..6605cc79 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -233,7 +233,7 @@ pub struct Niri { /// various tooltips from sticking around. pub pointer_hidden: bool, // FIXME: this should be able to be removed once PointerFocus takes grabs into accound. - pub interactive_resize_ongoing: bool, + pub pointer_grab_ongoing: bool, pub tablet_cursor_location: Option<Point<f64, Logical>>, pub gesture_swipe_3f_cumulative: Option<(f64, f64)>, pub vertical_wheel_tracker: ScrollTracker, @@ -1504,7 +1504,7 @@ impl Niri { dnd_icon: None, pointer_focus: PointerFocus::default(), pointer_hidden: false, - interactive_resize_ongoing: false, + pointer_grab_ongoing: false, tablet_cursor_location: None, gesture_swipe_3f_cumulative: None, vertical_wheel_tracker: ScrollTracker::new(120), @@ -3667,7 +3667,7 @@ impl Niri { let Some((surface, surface_loc)) = &new_under.surface else { return; }; - if self.interactive_resize_ongoing { + if self.pointer_grab_ongoing { return; } let pointer = &self.seat.get_pointer().unwrap(); |
