Skip to content

Touchpad scroll on chromebooks doesn't work on stable (1.7.8+hotfix.2) #36118

Closed
flutter-team-archive/engine
#11420

Description

@wreppun

Summary

Two finger trackpad scroll used to work, and now it doesn't. I did a (sloooow) binary search through the commits between stable releases. The commit which broke trackpad scrolling is 7f969d0. Unfortunately, that was a pretty complex PR (well, at least for my flutter skills), and I wasn't able to figure out what exactly went wrong.

Steps to Reproduce:

I used a slightly modified version of the counter example to make a scrollable list.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: ListView(
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            SizedBox(height: 16),
            ...List.generate(_counter, (index) {
              return Padding(
                padding: EdgeInsets.symmetric(vertical: 8),
                child: Text('#$index'),
              );
            }),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
  1. Run the app on a chromebook. I tested on an ASUS C202, a Lenovo e300, and one more (I don't have the model at hand, I'll add it when I can look) an HP Chromebook x360
  2. Click the + button until you have a list that goes past the bottom of the window
  3. Try to scroll with two fingers on the trackpad

Expected Result

The app scrolls

Actual Result

The app doesn't scroll

Logs

I didn't see anything in the verbose logs

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, v1.6.1-pre.56, on Mac OS X 10.14.5 18F132, locale en-US)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[!] iOS toolchain - develop for iOS devices (Xcode 10.2.1)
    ✗ Verify that all connected devices have been paired with this computer in Xcode.
      If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.
      To update with Brew, run:
        brew update
        brew uninstall --ignore-dependencies libimobiledevice
        brew uninstall --ignore-dependencies usbmuxd
        brew install --HEAD usbmuxd
        brew unlink usbmuxd
        brew link usbmuxd
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.36.1)
[!] Connected device

Activity

  1. dkwingsmt commented on Jul 13, 2019

    @dkwingsmt
    Contributor

    Thank you very much for the minimal example. I'll take a look next Monday.

  2. added
    f: gesturesflutter/packages/flutter/gestures repository.
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Jul 13, 2019
  3. self-assigned this
    on Jul 13, 2019
  4. Skylled commented on Jul 16, 2019

    @Skylled

    Running into this issue as well. Verified that plugging in a physical mouse and using the mouse wheel still scrolls the list as expected.

  5. dkwingsmt commented on Jul 16, 2019

    @dkwingsmt
    Contributor

    Initial investigation: two-finger scrolling on a trackpad triggers scrolling by sending PointerMoveEvent with buttons: 0, which becomes rejected due to this PR.

    We can either make an exception that allows move events to have buttons: 0, or see if we can send them using other routes, such as reinterpret them as scroll events.

  6. dkwingsmt commented on Jul 16, 2019

    @dkwingsmt
    Contributor

    +cc @stuartmorgan I think you implemented trackpad scrolling right? Is it possible or desirable to reinterpret them as scroll events?
    (Another choice is to add button: 1 to move events sent by trackpad scrolling)

  7. stuartmorgan-g commented on Jul 16, 2019

    @stuartmorgan-g
    Contributor

    two-finger scrolling on a trackpad triggers scrolling by sending PointerMoveEvent

    I'd have to dig more, but that doesn't sound right. I believe I designed it to use hover events for non-gesture pointer events, with the intention of using down/move/up for gestural pointer events (pinch-zoom, improved trackpad scroll handling, etc.)

    We can either make an exception that allows move events to have buttons: 0

    We should allow pointer signals to have buttons: 0

  8. dkwingsmt commented on Jul 16, 2019

    @dkwingsmt
    Contributor

    @stuartmorgan PointerSignalEvent is allowed to have buttons: 0, but PointerMoveEvent is not.

    This is how touchpad scrolling works before the PR:

    • A PointerMoveEvent is constructed with buttons: 0 and varying position
    • The move event is dispatched to GestureDetector and DragGestureRecognizer
    • The move event triggers DragGestureRecognizer.onUpdate, which updates Scrollable.

    Also it seems to me that PointerMoveEvents are received without the leading PointerDownEvent.

  9. stuartmorgan-g commented on Jul 16, 2019

    @stuartmorgan-g
    Contributor

    I'm confused; the initial description here is talking about two-finger scrolling, which I would expect to be using the pointer signal codepath (Edit: specifically, I would have thought that flutter-team-archive/engine#8149 would be the codepath used for two-finger scrolling of an Anrdoid app on ChromeOS). That wouldn't use gesture recognizers.

    Could you link to the specific code that's creating the events you are talking about?

  10. dkwingsmt commented on Jul 17, 2019

    @dkwingsmt
    Contributor

    @stuartmorgan I'm still investigating, but here is what I observed: I added the following printing to PointerEventConverter.expand

          print('${datum.change}, ${datum.signalKind}, ${datum.buttons}, ${datum.physicalX}, ${datum.physicalY}');

    and when I do a two-finger scrolling, this is what is logged:

    D/ArcInputCompatHandler( 9969): Scrolling Gesture started at (826.98047, 300.61328) will apply compatibility=(mUseMouseScrollCompat=false, mUseMouseTouchScreenEmulation=false)
    I/flutter ( 9969): PointerChange.down, PointerSignalKind.none, 0, 826.98046875, 300.61328125
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 336.5953063964844
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 506.6507873535156
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 653.0632934570312
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 1184.8023681640625
    D/ViewRootImpl[MainActivity]( 9969): updatePointerIcon called with position out of bounds
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 1461.354736328125
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 2192.589111328125
    I/flutter ( 9969): PointerChange.move, PointerSignalKind.none, 0, 826.98046875, 2391.46337890625
    D/ArcInputCompatHandler( 9969): Scrolling Gesture stopped at (826.98047, 2391.4634) with delta=(0.0, 2090.85) has applied compatibility=(mUseMouseScrollCompat=false, mUseMouseTouchScreenEmulation=false)
    I/flutter ( 9969): PointerChange.up, PointerSignalKind.none, 0, 826.98046875, 2391.46337890625
    

    And these events used to be the ones that triggered the change of Scrollable.position. Now they're blocked because they have buttons: 0.

  11. stuartmorgan-g commented on Jul 17, 2019

    @stuartmorgan-g
    Contributor

    I don't have experience with ARC, but it sounds like this is https://developer.android.com/topic/arc/input-compatibility which seems like something we should be disabling for Flutter given that we have scroll handling in the Android code.

  12. Skylled commented on Jul 17, 2019

    @Skylled

    Please correct me if I'm wrong, but it might be wiser for Flutter to respect ARC's input, at least for scrolling, as Chrome OS has a setting that allows you to flip the touchpad scrolling direction.
    https://support.google.com/chromebook/answer/1047367#settings

    If Flutter worked with the raw touchpad input, this setting would be ignored, which would be unexpected behavior for users and a regression from previous Flutter versions. I just verified with an older build of my app that touchpad scrolling flips as expected when that setting is toggled.

  13. stuartmorgan-g commented on Jul 17, 2019

    @stuartmorgan-g
    Contributor

    If Flutter worked with the raw touchpad input

    That is not the suggestion. Android is capable of receiving and handling scroll events, and the PR I linked above uses those to generate pointer signal events; the preferred outcome if it's possible (which I think is likely) is for ChromeOS to deliver two-finger scrolls as scroll events, rather than a series of synthetic touch-and-drag events.

    In that model I would imagine that ChromeOS would apply directional setting preferences prior to delivering them as scroll events. (As does, for instance, macOS, which also has such a setting.)

  14. dkwingsmt commented on Jul 17, 2019

    @dkwingsmt
    Contributor

    @Skylled brought about a very good point. I flipped the option for scrolling direction, and scrolled on trackpad in the same direction as before (downwards), and got the log as follows:

    DI/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1438.5025634765625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1501.1087646484375
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1562.3900146484375
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1662.427490234375
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1704.7861328125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1740.9752197265625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1792.7330322265625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1810.910400390625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1824.160400390625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1844.2010498046875
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1852.9791259765625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1861.3846435546875
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1876.7049560546875
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1883.6612548828125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1889.3753662109375
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1894.6754150390625
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1898.774658203125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1905.31689453125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1907.5528564453125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1909.2091064453125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1910.0372314453125
    I/flutter (11020): PointerChange.move, PointerSignalKind.none, 0, 201.51953125, -1910.8653564453125
    D/ArcInputCompatHandler(11020): Scrolling Gesture stopped at (1092.5195, -1715.8654) with delta=(0.0, -2098.0137) has applied compatibility=(mUseMouseScrollCompat=false, mUseMouseTouchScreenEmulation=false)
    

    It now reports PointerMoveEvent with decreasing Y, instead of increasing Y.

  15. stuartmorgan-g commented on Jul 17, 2019

    @stuartmorgan-g
    Contributor

    Again, there's no reason to believe the same reversal would not happen if we can switch to a mode where two-finger scroll is delivered as scroll events rather than synthesized touches.

  16. stuartmorgan-g commented on Jul 17, 2019

    @stuartmorgan-g
    Contributor

    Looking at the ARC page again, I didn't read far enough down:
    https://developer.android.com/topic/arc/input-compatibility#mouse_and_touchpad

    Since scroll wheels work as expected, it sounds like we aren't in compatibility mode as I thought from the initial description. And it sounds like there's no way to have the OS deliver these the same way as scroll wheel events, so we'd need to do the transformation manually.

    Based on the description in that section, my recommendation would be to update the Android embedding to detect two-finger scroll (using getSource or getButtonState per the docs) and send them as discrete pointer signal events (using computed deltas) instead of passing the synthetic touches through as is currently done. You can leave a TODO to update to gestural scrolling (which that input stream will be a better match for) once #23604 is implemented.

  17. dkwingsmt commented on Jul 19, 2019

    @dkwingsmt
    Contributor

    @stuartmorgan I tried to implement the feature that converts touchpad scrolling to scroll events. Despite other difficulties, I found out that one of the differences of mouse scrolling and touch scrolling is that

    • For mouse scrolling, the list stops scrolling as soon as the scroll ends.
    • For touch scrolling, the end velocity of the scroll keeps the list scrolling for a short additional time.

    Trackpad scrolling actually follows the latter form in native apps. Therefore it should take the route of touch scrolling instead of mouse scrolling.

  18. stuartmorgan-g commented on Jul 19, 2019

    @stuartmorgan-g
    Contributor

    Yes, and gestural signal scrolling will do that (and did, in fact, in my early implementation). Thus the suggestion of a TODO to upgrade the Android implementation from discrete signal scrolling to gestural signal scrolling later.

    Hacking the framework's handling of buttons just to avoid that temporary, minor difference when running Android apps on ChromeOS seems like a poor tradeoff to me, since it'll be harder to undo the button changes later (since other things may start depending on that without anyone realizing it).

    If you really feel that it's a major issue, then you could implement signal gestures (#23604) first, and then use that here now, but implementing discrete scrolling first would give most of the functionality back quickly.

  19. stuartmorgan-g commented on Jul 19, 2019

    @stuartmorgan-g
    Contributor

    Hacking the framework's handling of buttons

    I forgot about the other route you suggested: adding a primary button to the synthetic events. That would be a simple but localized temporary fix that doesn't have the concern of affecting framework code. Doing that as the short-term fix instead of using discrete pointer signals SGTM.

    (The main drawback is that those events could potentially trigger gesture detectors other than scroll, which is not ideal, but apparently that's the way it worked before your button validation change so it would be no worse than it was.)

  20. dkwingsmt commented on Jul 19, 2019

    @dkwingsmt
    Contributor

    @stuartmorgan What do you think if Scrollables use a special private version of DragGestureRecognizer that also allows buttonless-mouse events, as a short-term fix?

  21. stuartmorgan-g commented on Jul 19, 2019

    @stuartmorgan-g
    Contributor

    Personally I think localizing any workaround to the Android embedding is far preferable to doing anything in the framework to handle this one unusual event source. Any form of the latter is going to be harder to unwind because it'll be very difficult to know if anything else has started relying on it.

  22. wreppun commented on Aug 6, 2019

    @wreppun
    Author

    @dkwingsmt, thanks for the work on this!

    Just curious -- is the plan to wait for #23604 to land? I'm trying to get a release ready for a few chromebook schools by September -- any thoughts on how I could get a short term fix implemented?

  23. stuartmorgan-g commented on Aug 6, 2019

    @stuartmorgan-g
    Contributor

    Given that there are reasonable short-term fix options, I don't think there's a need to wait for 23604.

  24. added this to the October 2019 milestone on Aug 21, 2019
  25. github-actions commented on Aug 28, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  26. locked as resolved and limited conversation to collaborators on Aug 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

a: desktopRunning on desktopc: regressionIt was better in the past than it is nowf: gesturesflutter/packages/flutter/gestures repository.frameworkflutter/packages/flutter repository. See also f: labels.

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions