atagen

monolith

public
Star0
Watch0

Commit 9529672263b7

tarball

atagen <boss@atagen.co>2026-10-19 09:47 UTC

5 files changed+89-18
input: send relative pointer motion with unaccelerated deltas

Games that read raw mouse movement had only absolute positions to go on, so camera look broke at screen edges. pointer_motion now takes a Motion that says how the pointer moved. libinput deltas pass through as Relative, absolute devices and the nested host derive a delta from their last position, and warps send none so mouse-follows-focus never reads as a flick.
Modified file: crates/monolith/src/drm.rs+5 additions−3 deletions
@@ -4,6 +4,7 @@
44
55 use crate::backend::{Backend, logical_area};
66 use crate::devices::Devices;
7+use crate::input::Motion;
78 use crate::scene::Draw;
89 use crate::state::Services;
910 use crate::state::{State, now_ns};
@@ -1148,8 +1149,9 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
11481149 state.key(event.key_code(), event.state(), time);
11491150 }
11501151 InputEvent::PointerMotion { event } => {
1151- let pos = clamp(state, state.input.pointer_pos + event.delta());
1152- state.pointer_to(pos, event.time());
1152+ let (delta, unaccel) = (event.delta(), event.delta_unaccel());
1153+ let pos = clamp(state, state.input.pointer_pos + delta);
1154+ state.pointer_motion(pos, Motion::Relative { delta, unaccel }, event.time());
11531155 state.pointer_frame();
11541156 state.dirty.cursor = true;
11551157 }
@@ -1166,7 +1168,7 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
11661168 };
11671169 let p = event.position_transformed((area.w as i32, area.h as i32).into());
11681170 let pos = clamp(state, Point::from((area.x + p.x, area.y + p.y)));
1169- state.pointer_to(pos, event.time());
1171+ state.pointer_motion(pos, Motion::Absolute, event.time());
11701172 state.pointer_frame();
11711173 state.dirty.cursor = true;
11721174 }
Modified file: crates/monolith/src/globals.rs+3 additions
@@ -22,6 +22,7 @@ use smithay::wayland::image_capture_source::{
2222 use smithay::wayland::image_copy_capture::ImageCopyCaptureState;
2323 use smithay::wayland::output::OutputManagerState;
2424 use smithay::wayland::presentation::PresentationState;
25+use smithay::wayland::relative_pointer::RelativePointerManagerState;
2526 use smithay::wayland::selection::data_device::DataDeviceState;
2627 use smithay::wayland::selection::ext_data_control::DataControlState as ExtDataControlState;
2728 use smithay::wayland::selection::primary_selection::PrimarySelectionState;
@@ -70,6 +71,7 @@ pub struct Protocols {
7071 _single_pixel_buffer: SinglePixelBufferState,
7172 _fractional_scale: FractionalScaleManagerState,
7273 _presentation: PresentationState,
74+ _relative_pointer: RelativePointerManagerState,
7375 }
7476
7577 impl Protocols {
@@ -134,6 +136,7 @@ impl Protocols {
134136 _single_pixel_buffer: SinglePixelBufferState::new::<State>(dh),
135137 _fractional_scale: FractionalScaleManagerState::new::<State>(dh),
136138 _presentation: PresentationState::new::<State>(dh, Monotonic::ID as u32),
139+ _relative_pointer: RelativePointerManagerState::new::<State>(dh),
137140 }
138141 }
139142 }
Modified file: crates/monolith/src/input.rs+60 additions−7 deletions
@@ -7,15 +7,41 @@ use monolith_layout::{Axis, Dir, Layout, MIN_FLOAT_SIZE, Rect, WindowId, clamp_f
77 use smithay::backend::input::{ButtonState, InputTime, KeyState};
88 use smithay::backend::session::Session;
99 use smithay::input::keyboard::{FilterResult, Keycode, xkb};
10-use smithay::input::pointer::{AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, MotionEvent};
10+use smithay::input::pointer::{
11+ AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, MotionEvent, RelativeMotionEvent,
12+};
1113 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
1214 use smithay::reexports::wayland_server::Resource;
15+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1316 use smithay::utils::{Logical, Point, SERIAL_COUNTER};
1417 use smithay::wayland::shell::wlr_layer::Layer;
1518 use smithay::xwayland::xwm::ResizeEdge;
1619
20+/// How the pointer got to where a motion wants it.
21+pub enum Motion {
22+ Relative {
23+ delta: Point<f64, Logical>,
24+ unaccel: Point<f64, Logical>,
25+ },
26+ Absolute,
27+ Warp,
28+}
29+
1730 impl State {
18- pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) {
31+ pub fn pointer_motion(&mut self, wanted: Point<f64, Logical>, motion: Motion, time: InputTime) {
32+ let (delta, unaccel) = match motion {
33+ Motion::Relative { delta, unaccel } => {
34+ self.input.absolute = None;
35+ (delta, unaccel)
36+ }
37+ Motion::Absolute => {
38+ let from = self.input.absolute.replace(wanted);
39+ let delta = wanted - from.unwrap_or(self.input.pointer_pos);
40+ (delta, delta)
41+ }
42+ Motion::Warp => Default::default(),
43+ };
44+ let pos = wanted;
1945 self.input.pointer_pos = pos;
2046 self.follow_pointer_scale();
2147 self.activity();
@@ -32,6 +58,7 @@ impl State {
3258 if std::mem::replace(&mut self.input.hover, hover) != hover {
3359 self.dirty.cursor = true;
3460 }
61+ self.relative_motion(under.clone(), delta, unaccel, time);
3562 pointer.motion(
3663 self,
3764 under,
@@ -48,6 +75,36 @@ impl State {
4875 }
4976 }
5077
78+ fn relative_motion(
79+ &mut self,
80+ under: Option<(WlSurface, Point<f64, Logical>)>,
81+ delta: Point<f64, Logical>,
82+ unaccel: Point<f64, Logical>,
83+ time: InputTime,
84+ ) {
85+ if delta == Point::default() && unaccel == Point::default() {
86+ return;
87+ }
88+ if let Some(pointer) = self.input.seat.get_pointer() {
89+ let event = RelativeMotionEvent {
90+ delta,
91+ delta_unaccel: unaccel,
92+ time,
93+ };
94+ pointer.relative_motion(self, under, &event);
95+ }
96+ }
97+
98+ /// Puts the pointer somewhere without telling clients it moved, which a
99+ /// relative motion would, and without letting focus follow it.
100+ pub fn warp_pointer(&mut self, pos: Point<f64, Logical>, time: InputTime) {
101+ self.input.warping = true;
102+ self.pointer_motion(pos, Motion::Warp, time);
103+ self.input.warping = false;
104+ self.pointer_frame();
105+ self.dirty.cursor = true;
106+ }
107+
51108 pub fn pointer_button(&mut self, button: u32, pressed: ButtonState, time: InputTime) {
52109 self.activity();
53110 if pressed == ButtonState::Released {
@@ -246,14 +303,10 @@ impl State {
246303 let Some((_, r, _)) = focused.and_then(|id| self.wm.get(id)?.placed) else {
247304 return;
248305 };
249- self.input.warping = true;
250- self.pointer_to(
306+ self.warp_pointer(
251307 Point::from((r.x + r.w / 2.0, r.y + r.h / 2.0)),
252308 InputTime::now(),
253309 );
254- self.input.warping = false;
255- self.pointer_frame();
256- self.dirty.cursor = true;
257310 }
258311 }
259312
Modified file: crates/monolith/src/nested/input.rs+17 additions−8 deletions
@@ -1,13 +1,22 @@
11 //! The host seat, forwarded into monolith's own.
22
3+use crate::input::Motion;
34 use crate::state::State;
45 use smithay::backend::input::{Axis, ButtonState, InputTime, KeyState};
56 use smithay::input::keyboard::Keycode;
67 use smithay::input::pointer::AxisFrame;
7-use smithay::utils::Point;
8+use smithay::utils::{Logical, Point};
89 use wayland_client::protocol::{wl_keyboard, wl_pointer, wl_seat};
910 use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};
1011
12+fn host_position(state: &State, x: f64, y: f64) -> Point<f64, Logical> {
13+ let scale = state
14+ .backend
15+ .nested()
16+ .map_or(1.0, |h| h.output.current_scale().fractional_scale());
17+ Point::from((x / scale, y / scale))
18+}
19+
1120 impl Dispatch<wl_seat::WlSeat, ()> for State {
1221 fn event(
1322 _: &mut Self,
@@ -46,18 +55,18 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
4655 surface_x,
4756 surface_y,
4857 ..
58+ } => {
59+ let pos = host_position(state, surface_x, surface_y);
60+ state.input.absolute = Some(pos);
61+ state.pointer_motion(pos, Motion::Absolute, time);
4962 }
50- | wl_pointer::Event::Motion {
63+ wl_pointer::Event::Motion {
5164 surface_x,
5265 surface_y,
5366 ..
5467 } => {
55- let scale = state
56- .backend
57- .nested()
58- .map_or(1.0, |h| h.output.current_scale().fractional_scale());
59- let pos = Point::from((surface_x / scale, surface_y / scale));
60- state.pointer_to(pos, time);
68+ let pos = host_position(state, surface_x, surface_y);
69+ state.pointer_motion(pos, Motion::Absolute, time);
6170 }
6271 wl_pointer::Event::Button {
6372 button,
Modified file: crates/monolith/src/state.rs+4 additions
@@ -65,6 +65,9 @@ pub struct Input {
6565 pub hover: Option<crate::input::Edges>,
6666 /// The output the pointer was last on, whose scale cursor and drag icon surfaces follow.
6767 pub pointer_output: Option<String>,
68+ /// Where the last absolute device event put the pointer, so the next
69+ /// one yields a relative delta even while the pointer is held in place.
70+ pub absolute: Option<Point<f64, Logical>>,
6871 }
6972
7073 /// What needs doing after this batch of events.
@@ -198,6 +201,7 @@ impl State {
198201 dnd_icon: None,
199202 hover: None,
200203 pointer_output: None,
204+ absolute: None,
201205 },
202206 dirty: Dirty {
203207 redraw: true,