diff --git i/input-capture/src/layer_shell.rs w/input-capture/src/layer_shell.rs index 698c584..7d81d37 100644 --- i/input-capture/src/layer_shell.rs +++ w/input-capture/src/layer_shell.rs @@ -70,6 +70,21 @@ use super::{ error::{LayerShellCaptureCreationError, WaylandBindError}, }; +/// Multiplier applied to forwarded pointer motion, from `LAN_MOUSE_POINTER_SCALE`. +/// +/// Defaults to 1.0. Useful when the two machines differ in logical pixel density, so the +/// same logical delta covers a different physical distance on each screen. +fn pointer_scale() -> f64 { + static SCALE: std::sync::OnceLock = std::sync::OnceLock::new(); + *SCALE.get_or_init(|| { + env::var("LAN_MOUSE_POINTER_SCALE") + .ok() + .and_then(|v| v.parse::().ok()) + .filter(|v| v.is_finite() && *v > 0.0) + .unwrap_or(1.0) + }) +} + struct Globals { compositor: wl_compositor::WlCompositor, pointer_constraints: ZwpPointerConstraintsV1, @@ -887,13 +902,19 @@ impl Dispatch for State { if let zwp_relative_pointer_v1::Event::RelativeMotion { utime_hi, utime_lo, - dx_unaccel: dx, - dy_unaccel: dy, + dx, + dy, .. } = event { if let Some(window) = &app.focused { let time = ((((utime_hi as u64) << 32) | utime_lo as u64) / 1000) as u32; + // Forward the ACCELERATED deltas. The unaccelerated ones bypass the sending + // compositor's pointer-acceleration curve, and the receiving end emulates via + // wlr-virtual-pointer, which is not a libinput device and so applies no curve + // of its own -- the acceleration would be lost entirely in transit. + let scale = pointer_scale(); + let (dx, dy) = (dx * scale, dy * scale); app.pending_events.push_back(( window.pos, CaptureEvent::Input(Event::Pointer(PointerEvent::Motion { time, dx, dy })),