diff --git a/flutter/lib/common/widgets/remote_input.dart b/flutter/lib/common/widgets/remote_input.dart index 1e7523e54..ad08736c8 100644 --- a/flutter/lib/common/widgets/remote_input.dart +++ b/flutter/lib/common/widgets/remote_input.dart @@ -95,8 +95,9 @@ class _RawTouchGestureDetectorRegionState } if (handleTouch) { // Desktop or mobile "Touch mode" - ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - inputModel.tapDown(MouseButtons.left); + if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) { + inputModel.tapDown(MouseButtons.left); + } } } @@ -105,8 +106,9 @@ class _RawTouchGestureDetectorRegionState return; } if (handleTouch) { - ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy); - inputModel.tapUp(MouseButtons.left); + if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) { + inputModel.tapUp(MouseButtons.left); + } } } @@ -134,6 +136,9 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } + if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) { + return; + } inputModel.tap(MouseButtons.left); inputModel.tap(MouseButtons.left); } @@ -222,6 +227,9 @@ class _RawTouchGestureDetectorRegionState return; } if (handleTouch) { + if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { + return; + } if (isDesktop) { ffi.cursorModel.trySetRemoteWindowCoords(); } @@ -244,6 +252,9 @@ class _RawTouchGestureDetectorRegionState if (lastDeviceKind != PointerDeviceKind.touch) { return; } + if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) { + return; + } ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch); } diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index ca7b909e8..7d449f84b 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -679,6 +679,7 @@ class _KeyHelpToolsState extends State { var _fn = false; var _pin = false; final _keyboardVisibilityController = KeyboardVisibilityController(); + final _key = GlobalKey(); InputModel get inputModel => gFFI.inputModel; @@ -703,6 +704,24 @@ class _KeyHelpToolsState extends State { onPressed: onPressed); } + @override + void initState() { + super.initState(); + } + + _updateRect() { + RenderObject? renderObject = _key.currentContext?.findRenderObject(); + if (renderObject == null) { + return; + } + if (renderObject is RenderBox) { + final size = renderObject.size; + Offset pos = renderObject.localToGlobal(Offset.zero); + gFFI.cursorModel.keyHelpToolsRect = + Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height); + } + } + @override Widget build(BuildContext context) { final hasModifierOn = inputModel.ctrl || @@ -711,6 +730,7 @@ class _KeyHelpToolsState extends State { inputModel.command; if (!_pin && !hasModifierOn && !widget.requestShow) { + gFFI.cursorModel.keyHelpToolsRect = null; return Offstage(); } final size = MediaQuery.of(context).size; @@ -821,7 +841,12 @@ class _KeyHelpToolsState extends State { }), ]; final space = size.width > 320 ? 4.0 : 2.0; + // 500 ms is long enough for this widget to be built! + Future.delayed(Duration(milliseconds: 500), () { + _updateRect(); + }); return Container( + key: _key, color: Color(0xAA000000), padding: EdgeInsets.only( top: _keyboardVisibilityController.isVisible ? 24 : 4, bottom: 8), diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index c7b3debea..bdbad349f 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -1800,6 +1800,32 @@ class CursorModel with ChangeNotifier { String peerId = ''; WeakReference parent; + // Only for mobile, touch mode + // To block touch event above the KeyHelpTools + // + // A better way is to not listen events from the KeyHelpTools. + // But we're now using a Container(child: Stack(...)) to wrap the KeyHelpTools, + // and the listener is on the Container. + Rect? _keyHelpToolsRect; + bool _lastIsBlocked = false; + + set keyHelpToolsRect(Rect? r) { + _keyHelpToolsRect = r; + if (r == null) { + _lastIsBlocked = false; + } else { + // `lastIsBlocked` is only used in common/widgets/remote_input.dart -> _RawTouchGestureDetectorRegionState -> onDoubleTap() + // Because onDoubleTap() doesn't have the `event` parameter, we can't get the touch event's position. + // + // Block the touch event is safe here. + // `lastIsBlocked` is only used in onDoubleTap() to block the touch event from the KeyHelpTools. + // `lastIsBlocked` will be set when the cursor is moving or touch somewhere else. + _lastIsBlocked = true; + } + } + + get lastIsBlocked => _lastIsBlocked; + ui.Image? get image => _image; CursorData? get cache => _cache; @@ -1844,9 +1870,10 @@ class CursorModel with ChangeNotifier { return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale); } + get keyboardHeight => MediaQueryData.fromWindow(ui.window).viewInsets.bottom; + double adjustForKeyboard() { final m = MediaQueryData.fromWindow(ui.window); - var keyboardHeight = m.viewInsets.bottom; final size = m.size; if (keyboardHeight < 100) return 0; final s = parent.target?.canvasModel.scale ?? 1.0; @@ -1855,9 +1882,29 @@ class CursorModel with ChangeNotifier { return h - thresh; } + // mobile Soft keyboard, block touch event from the KeyHelpTools + shouldBlock(double x, double y) { + if (!(parent.target?.ffiModel.touchMode ?? false)) { + return false; + } + if (_keyHelpToolsRect == null) { + return false; + } + if (isPointInRect(Offset(x, y), _keyHelpToolsRect!)) { + return true; + } + return false; + } + move(double x, double y) { + if (shouldBlock(x, y)) { + _lastIsBlocked = true; + return false; + } + _lastIsBlocked = false; moveLocal(x, y); parent.target?.inputModel.moveMouse(_x, _y); + return true; } moveLocal(double x, double y) {