fix(remote_input): pannability-aware two-finger scale on mobile

Replace the stateful isNotTouchBasedDevice() guard at the top of
onTwoFingerScaleUpdate with the new isPanScrollUseful() overflow predicate
on mobile. Two-finger touch only pans the local canvas when remote
content actually overflows the viewport; otherwise the gesture is a
no-op (the trackpad case is already short-circuited at the recognizer
level — gestures.dart).

Specifically:
- Desktop path: still gated by isNotTouchBasedDevice() (preserves existing
  behaviour where a desktop trackpad pinch claims the recognizer).
- Mobile path: drops the lastDeviceKind-based guard entirely (the
  recognizer no longer claims trackpad pan-zoom on mobile, so any callback
  here is real touch by construction). Pans canvas only when
  isPanScrollUseful() is true.

This is the 'pannability-aware routing' half of the #15209 fix; the
recognizer-level half landed in 087c1b702.

flutter test (gestures + canvas + input_modifier_utils) -> 20/20 pass.

Refs: rustdesk/rustdesk#15209
Plan: docs/superpowers/plans/2026-06-19-trackpad-scroll-15209.md task 3.
This commit is contained in:
Naman Jain 2026-06-19 02:27:39 -07:00
commit b0688a23ee

View file

@ -458,7 +458,13 @@ class _RawTouchGestureDetectorRegionState
}
onTwoFingerScaleUpdate(ScaleUpdateDetails d) async {
if (isNotTouchBasedDevice()) {
// On mobile, only operate on real touch `CustomTouchGestureRecognizer`
// refuses to claim trackpad pan-zoom on mobile (gestures.dart), so any
// call here on iOS/Android is a real two-finger touch. The historical
// `isNotTouchBasedDevice()` guard was stateful (lastDeviceKind) and gave
// the wrong answer after the first incidental screen touch, breaking
// trackpad scroll until restart. See rustdesk/rustdesk#15209.
if ((isDesktop || isWebDesktop) && isNotTouchBasedDevice()) {
return;
}
@ -484,11 +490,16 @@ class _RawTouchGestureDetectorRegionState
.toJson()));
}
} else {
// mobile
// mobile: real two-finger touch.
ffi.canvasModel.updateScale(d.scale / _scale, d.focalPoint);
_scale = d.scale;
ffi.canvasModel.panX(d.focalPointDelta.dx);
ffi.canvasModel.panY(d.focalPointDelta.dy);
// Only pan the local canvas when the remote view actually overflows
// the viewport otherwise the pan moves nothing but black bars and
// surprises the user. (#15209)
if (ffi.canvasModel.isPanScrollUseful()) {
ffi.canvasModel.panX(d.focalPointDelta.dx);
ffi.canvasModel.panY(d.focalPointDelta.dy);
}
}
}