feat(client/windows): invert scroll direction

The Apple client's toggle, wired through the shared session presenter:
Settings::invert_scroll -> SessionOpts -> Capture, applied at the single
seam where wheel deltas enter (before accumulation, so the fractional
remainders stay consistent with what was actually sent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 17:29:07 +02:00
parent 7a1809547b
commit 51f7ff1b3f
6 changed files with 36 additions and 9 deletions
+5
View File
@@ -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,
+11 -3
View File
@@ -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<NativeClient>, touch_mode: TouchMode) -> Capture {
pub fn new(
connector: Arc<NativeClient>,
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);
+3 -1
View File
@@ -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<Option<Outcome>
.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);