diff --git a/clients/session/src/console.rs b/clients/session/src/console.rs index 5f6b6b81..df4677f8 100644 --- a/clients/session/src/console.rs +++ b/clients/session/src/console.rs @@ -158,6 +158,7 @@ pub fn run(target: Option<&str>) -> u8 { v => v, }, touch_mode: settings_at_start.touch_mode(), + invert_scroll: settings_at_start.invert_scroll, json_status, on_connected: Some(Box::new(move |fingerprint: [u8; 32]| { let fp_hex = trust::hex(&fingerprint); diff --git a/clients/session/src/main.rs b/clients/session/src/main.rs index d2855de7..14a39f89 100644 --- a/clients/session/src/main.rs +++ b/clients/session/src/main.rs @@ -373,6 +373,7 @@ mod session_main { v => v, }, touch_mode: settings.touch_mode(), + invert_scroll: settings.invert_scroll, json_status: true, on_connected: Some(Box::new(|fingerprint: [u8; 32]| { // This host's card carries the accent bar in the desktop client now. diff --git a/clients/windows/src/app/settings.rs b/clients/windows/src/app/settings.rs index ccf2697b..30479813 100644 --- a/clients/windows/src/app/settings.rs +++ b/clients/windows/src/app/settings.rs @@ -380,6 +380,10 @@ pub(crate) fn settings_page( let touch_combo = setting_combo(ctx, "Touch input", touch_names, touch_i, |s, i| { s.touch_mode = TOUCH_MODES[i].0.to_string(); }); + let invert_scroll_toggle = + setting_toggle(ctx, "Invert scroll direction", s.invert_scroll, |s, on| { + s.invert_scroll = on + }); let shortcuts_toggle = setting_toggle( ctx, "Capture system shortcuts (Alt+Tab, Win, \u{2026})", @@ -523,11 +527,17 @@ pub(crate) fn settings_page( ); out.extend(group( Some("Keyboard & mouse"), - vec![described( - shortcuts_toggle, - "Alt+Tab, the Windows key and friends reach the host while the stream has \ - input captured. Off, they act on this machine instead.", - )], + vec![ + described( + shortcuts_toggle, + "Alt+Tab, the Windows key and friends reach the host while the stream \ + has input captured. Off, they act on this machine instead.", + ), + described( + invert_scroll_toggle, + "Reverses the wheel and trackpad scroll direction sent to the host.", + ), + ], None, )); ("Input", out) diff --git a/crates/pf-client-core/src/trust.rs b/crates/pf-client-core/src/trust.rs index d6a2a3c8..79f9900d 100644 --- a/crates/pf-client-core/src/trust.rs +++ b/crates/pf-client-core/src/trust.rs @@ -533,6 +533,10 @@ pub struct Settings { /// wait only adds a delay. #[serde(default = "default_true")] pub auto_wake: bool, + /// Reverse the wheel/trackpad scroll direction sent to the host (the Apple client's + /// "Invert scroll direction"). Default off = the host scrolls the way this machine does. + #[serde(default)] + pub invert_scroll: bool, /// Match-window resolution policy (design/midstream-resolution-resize.md D1): the /// stream mode follows the session window — the connect asks for the window's pixel /// size and a mid-session resize renegotiates the host's virtual display + encoder @@ -622,6 +626,7 @@ impl Default for Settings { fullscreen_on_stream: true, library_enabled: false, auto_wake: true, + invert_scroll: false, match_window: false, last_window_w: 0, last_window_h: 0, diff --git a/crates/pf-presenter/src/input.rs b/crates/pf-presenter/src/input.rs index 6de45c0e..c67cb6c3 100644 --- a/crates/pf-presenter/src/input.rs +++ b/crates/pf-presenter/src/input.rs @@ -53,6 +53,8 @@ pub struct Capture { /// The touchscreen input model for this session, and — for trackpad/pointer — the /// gesture state machine finger events feed. touch_mode: TouchMode, + /// Reverse the scroll direction sent to the host ([`Settings::invert_scroll`]). + invert_scroll: bool, gestures: Gestures, } @@ -68,7 +70,11 @@ fn send(connector: &NativeClient, kind: InputKind, code: u32, x: i32, y: i32, fl } impl Capture { - pub fn new(connector: Arc, touch_mode: TouchMode) -> Capture { + pub fn new( + connector: Arc, + touch_mode: TouchMode, + invert_scroll: bool, + ) -> Capture { Capture { connector, captured: false, @@ -79,6 +85,7 @@ impl Capture { scroll_acc: (0.0, 0.0), touch_slots: HashMap::new(), touch_mode, + invert_scroll, gestures: Gestures::new(touch_mode == TouchMode::Trackpad), } } @@ -194,9 +201,10 @@ impl Capture { return; } self.flush_motion(); // scroll happens at the latest cursor position + let sign = if self.invert_scroll { -1.0 } else { 1.0 }; let (mut ax, mut ay) = self.scroll_acc; - ay += f64::from(dy) * 120.0; - ax += f64::from(dx) * 120.0; + ay += f64::from(dy) * 120.0 * sign; + ax += f64::from(dx) * 120.0 * sign; let vy = ay.trunc() as i32; if vy != 0 { ay -= f64::from(vy); diff --git a/crates/pf-presenter/src/run.rs b/crates/pf-presenter/src/run.rs index 582955b5..2f73da14 100644 --- a/crates/pf-presenter/src/run.rs +++ b/crates/pf-presenter/src/run.rs @@ -48,6 +48,8 @@ pub struct SessionOpts { /// `Pointer` (absolute cursor), or `Touch` (real multi-touch passthrough). Latched per /// session — a mouse-only client leaves this at the default and never sees a finger. pub touch_mode: TouchMode, + /// Reverse the scroll direction sent to the host ([`Settings::invert_scroll`]). + pub invert_scroll: bool, /// Emit the `{"ready":true}` stdout line after the first presented frame. pub json_status: bool, /// Called once on `Connected` with the host's fingerprint (trust persistence is the @@ -811,7 +813,7 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result .ok(); gamepad.attach(c.clone()); st.clock_offset = Some(c.clock_offset_shared()); - let mut cap = Capture::new(c.clone(), opts.touch_mode); + let mut cap = Capture::new(c.clone(), opts.touch_mode, opts.invert_scroll); cap.engage(); // capture engages when the stream starts (ui_stream parity) apply_capture(&mut window, &mouse, true); st.capture = Some(cap);