aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-05-11 14:01:48 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-05-11 14:02:37 +0400
commitbc29256b9d95f265c8f6508e7949c57497835430 (patch)
treee8d7dd9c3a43e383fdc9f5fb6850f31e3e3078e6 /src
parentbeba87354a1fd30a95eaaf6c98eec72797e4baa7 (diff)
downloadniri-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.rs2
-rw-r--r--src/input/mod.rs47
-rw-r--r--src/input/resize_grab.rs2
-rw-r--r--src/input/view_offset_grab.rs190
-rw-r--r--src/layout/mod.rs57
-rw-r--r--src/layout/workspace.rs28
-rw-r--r--src/niri.rs6
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();