mouse: only clear scroll remainders after 1 second
When scrolling slowly on the Microsoft Surface Precision mouse, with
high-resolution scrolling enabled and with the wheel in smooth mode, the
page wasn't moving. This was due to `IntegralGestureFilterInterpreter`
resetting its remainders every time `SyncInterpret` was called (i.e.
every evdev `SYN_REPORT`). (Apparently, the reason the remainders need
resetting at all is to allow recordings to be replayed consistently.)
Changing it to only reset after a second of inactivity fixes this.
The 1-second deadline meant using a timer, so also moved some common
logic from `FlingStopFilterInterpreter` into `FilterInterpreter`.
BUG=chromium:888172
TEST=scroll slowly on MS Surface Precision, check that the page scrolls;
run touchtests and unit tests
Change-Id: Ic5a4ef887100badf2fa29162232a6ff3cc0148bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/gestures/+/2135286
Tested-by: Harry Cutts <hcutts@chromium.org>
Reviewed-by: Andrew de los Reyes <adlr@chromium.org>
Reviewed-by: Sean O'Brien <seobrien@chromium.org>
Commit-Queue: Harry Cutts <hcutts@chromium.org>
diff --git a/Makefile b/Makefile
index 68ac097..0c058d7 100644
--- a/Makefile
+++ b/Makefile
@@ -50,6 +50,7 @@
$(OBJDIR)/box_filter_interpreter_unittest.o \
$(OBJDIR)/click_wiggle_filter_interpreter_unittest.o \
$(OBJDIR)/command_line.o \
+ $(OBJDIR)/filter_interpreter_unittest.o \
$(OBJDIR)/fling_stop_filter_interpreter_unittest.o \
$(OBJDIR)/gestures_unittest.o \
$(OBJDIR)/iir_filter_interpreter_unittest.o \
diff --git a/include/filter_interpreter.h b/include/filter_interpreter.h
index e1ff60b..4b33f26 100644
--- a/include/filter_interpreter.h
+++ b/include/filter_interpreter.h
@@ -4,6 +4,7 @@
#include <memory>
+#include <gtest/gtest.h>
#include <json/value.h>
#include "gestures/include/interpreter.h"
@@ -19,6 +20,11 @@
// Interface for all filter interpreters.
class FilterInterpreter : public Interpreter, public GestureConsumer {
+ FRIEND_TEST(FilterInterpreterTest, DeadlineSettingNoDeadlines);
+ FRIEND_TEST(FilterInterpreterTest, DeadlineSettingLocalOnly);
+ FRIEND_TEST(FilterInterpreterTest, DeadlineSettingNextOnly);
+ FRIEND_TEST(FilterInterpreterTest, DeadlineSettingLocalBeforeNext);
+ FRIEND_TEST(FilterInterpreterTest, DeadlineSettingNextBeforeLocal);
public:
FilterInterpreter(PropRegistry* prop_reg,
Interpreter* next,
@@ -40,6 +46,17 @@
virtual void SyncInterpretImpl(HardwareState* hwstate, stime_t* timeout);
virtual void HandleTimerImpl(stime_t now, stime_t* timeout);
+ // When we need to call HandlerTimer on next_, or 0.0 if no outstanding timer.
+ stime_t next_timer_deadline_ = 0.0;
+ // Sets the next timer deadline, taking into account the deadline needed for
+ // this interpreter and the one from the next in the chain.
+ stime_t SetNextDeadlineAndReturnTimeoutVal(stime_t now,
+ stime_t local_deadline,
+ stime_t next_timeout);
+ // Utility method for determining whether the timer callback is for this
+ // interpreter or one further down the chain.
+ bool ShouldCallNextTimer(stime_t local_deadline);
+
std::unique_ptr<Interpreter> next_;
private:
diff --git a/include/fling_stop_filter_interpreter.h b/include/fling_stop_filter_interpreter.h
index 3c82bc3..25b0912 100644
--- a/include/fling_stop_filter_interpreter.h
+++ b/include/fling_stop_filter_interpreter.h
@@ -42,7 +42,6 @@
bool NeedsExtraTime(const HardwareState& hwstate) const;
bool FlingStopNeeded(const Gesture& gesture) const;
void UpdateFlingStopDeadline(const HardwareState& hwstate);
- stime_t SetNextDeadlineAndReturnTimeoutVal(stime_t now, stime_t next_timeout);
// Has the deadline has already been extended once
bool already_extended_;
@@ -65,8 +64,6 @@
// When we should send fling-stop, or 0.0 if not set.
stime_t fling_stop_deadline_;
- // When we need to call HandlerTimer on next_, or 0.0 if no outstanding timer.
- stime_t next_timer_deadline_;
// Device class (e.g. touchpad, mouse).
GestureInterpreterDeviceClass devclass_;
diff --git a/include/integral_gesture_filter_interpreter.h b/include/integral_gesture_filter_interpreter.h
index 4db5386..5bbb789 100644
--- a/include/integral_gesture_filter_interpreter.h
+++ b/include/integral_gesture_filter_interpreter.h
@@ -32,6 +32,10 @@
float hscroll_remainder_, vscroll_remainder_;
float hscroll_ordinal_remainder_, vscroll_ordinal_remainder_;
+ bool can_clear_remainders_;
+
+ stime_t remainder_reset_deadline_;
+ virtual void HandleTimerImpl(stime_t now, stime_t *timeout);
};
} // namespace gestures
diff --git a/src/filter_interpreter.cc b/src/filter_interpreter.cc
index 0d5bc92..220cefb 100644
--- a/src/filter_interpreter.cc
+++ b/src/filter_interpreter.cc
@@ -43,4 +43,27 @@
log_->Clear();
next_->Clear();
}
+
+stime_t FilterInterpreter::SetNextDeadlineAndReturnTimeoutVal(
+ stime_t now, stime_t local_deadline, stime_t next_timeout) {
+ next_timer_deadline_ = next_timeout > 0.0 ? now + next_timeout : 0.0;
+ stime_t local_timeout =
+ local_deadline <= 0.0 ? -1.0 : std::max(local_deadline - now, 0.0);
+
+ if (next_timeout <= 0.0 && local_timeout <= 0.0)
+ return -1.0;
+ if (next_timeout <= 0.0)
+ return local_timeout;
+ if (local_timeout <= 0.0)
+ return next_timeout;
+ return std::min(next_timeout, local_timeout);
+}
+
+bool FilterInterpreter::ShouldCallNextTimer(stime_t local_deadline) {
+ if (local_deadline > 0.0 && next_timer_deadline_ > 0.0)
+ return local_deadline > next_timer_deadline_;
+ else
+ return next_timer_deadline_ > 0.0;
+}
+
} // namespace gestures
diff --git a/src/filter_interpreter_unittest.cc b/src/filter_interpreter_unittest.cc
new file mode 100644
index 0000000..6d629fc
--- /dev/null
+++ b/src/filter_interpreter_unittest.cc
@@ -0,0 +1,69 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "gestures/include/gestures.h"
+#include "gestures/include/filter_interpreter.h"
+#include "gestures/include/unittest_util.h"
+#include "gestures/include/util.h"
+
+using std::string;
+
+namespace gestures {
+
+class FilterInterpreterTest : public ::testing::Test {
+ public:
+ FilterInterpreterTest() : interpreter(NULL, NULL, NULL, false) {}
+
+ protected:
+ FilterInterpreter interpreter;
+};
+
+TEST_F(FilterInterpreterTest, DeadlineSettingNoDeadlines) {
+ stime_t timeout_val =
+ interpreter.SetNextDeadlineAndReturnTimeoutVal(10000.0, 0.0, 0.0);
+ EXPECT_FLOAT_EQ(-1.0, timeout_val);
+ EXPECT_FLOAT_EQ(0.0, interpreter.next_timer_deadline_);
+}
+
+TEST_F(FilterInterpreterTest, DeadlineSettingLocalOnly) {
+ stime_t timeout_val =
+ interpreter.SetNextDeadlineAndReturnTimeoutVal(10000.0, 10001.0, 0.0);
+ EXPECT_FLOAT_EQ(1.0, timeout_val);
+ EXPECT_FLOAT_EQ(0.0, interpreter.next_timer_deadline_);
+
+ EXPECT_FALSE(interpreter.ShouldCallNextTimer(10001.00));
+}
+
+TEST_F(FilterInterpreterTest, DeadlineSettingNextOnly) {
+ stime_t timeout_val =
+ interpreter.SetNextDeadlineAndReturnTimeoutVal(10000.0, 0.0, 1.0);
+ EXPECT_FLOAT_EQ(1.0, timeout_val);
+ EXPECT_FLOAT_EQ(10001.0, interpreter.next_timer_deadline_);
+
+ EXPECT_TRUE(interpreter.ShouldCallNextTimer(0.0));
+}
+
+TEST_F(FilterInterpreterTest, DeadlineSettingLocalBeforeNext) {
+ stime_t timeout_val =
+ interpreter.SetNextDeadlineAndReturnTimeoutVal(10000.0, 10001.0, 2.0);
+ EXPECT_FLOAT_EQ(1.0, timeout_val);
+ EXPECT_FLOAT_EQ(10002.0, interpreter.next_timer_deadline_);
+
+ EXPECT_FALSE(interpreter.ShouldCallNextTimer(10001.0));
+}
+
+TEST_F(FilterInterpreterTest, DeadlineSettingNextBeforeLocal) {
+ stime_t timeout_val =
+ interpreter.SetNextDeadlineAndReturnTimeoutVal(10000.0, 10002.0, 1.0);
+ EXPECT_FLOAT_EQ(1.0, timeout_val);
+ EXPECT_FLOAT_EQ(10001.0, interpreter.next_timer_deadline_);
+
+ EXPECT_TRUE(interpreter.ShouldCallNextTimer(10002.0));
+}
+
+} // namespace gestures
diff --git a/src/fling_stop_filter_interpreter.cc b/src/fling_stop_filter_interpreter.cc
index d266011..4c557ea 100644
--- a/src/fling_stop_filter_interpreter.cc
+++ b/src/fling_stop_filter_interpreter.cc
@@ -17,7 +17,6 @@
prev_gesture_type_(kGestureTypeNull),
fling_stop_already_sent_(false),
fling_stop_deadline_(0.0),
- next_timer_deadline_(0.0),
devclass_(devclass),
fling_stop_timeout_(prop_reg, "Fling Stop Timeout", 0.03),
fling_stop_extra_delay_(prop_reg, "Fling Stop Extra Delay", 0.055) {
@@ -49,6 +48,7 @@
}
next_->SyncInterpret(hwstate, &next_timeout);
*timeout = SetNextDeadlineAndReturnTimeoutVal(hwstate->timestamp,
+ fling_stop_deadline_,
next_timeout);
}
@@ -114,31 +114,9 @@
prev_touch_cnt_ = hwstate.touch_cnt;
}
-stime_t FlingStopFilterInterpreter::SetNextDeadlineAndReturnTimeoutVal(
- stime_t now,
- stime_t next_timeout) {
- next_timer_deadline_ = next_timeout >= 0.0 ? now + next_timeout : 0.0;
- stime_t local_timeout = fling_stop_deadline_ == 0.0 ? -1.0 :
- std::max(fling_stop_deadline_ - now, 0.0);
-
- if (next_timeout < 0.0 && local_timeout < 0.0)
- return -1.0;
- if (next_timeout < 0.0)
- return local_timeout;
- if (local_timeout < 0.0)
- return next_timeout;
- return std::min(next_timeout, local_timeout);
-}
-
void FlingStopFilterInterpreter::HandleTimerImpl(stime_t now,
stime_t* timeout) {
- bool call_next = false;
- if (fling_stop_deadline_ > 0.0 && next_timer_deadline_ > 0.0)
- call_next = fling_stop_deadline_ > next_timer_deadline_;
- else
- call_next = next_timer_deadline_ > 0.0;
-
- if (!call_next) {
+ if (!ShouldCallNextTimer(fling_stop_deadline_)) {
if (fling_stop_deadline_ > now) {
Err("Spurious callback. now: %f, fs deadline: %f, next deadline: %f",
now, fling_stop_deadline_, next_timer_deadline_);
@@ -151,7 +129,8 @@
fling_stop_already_sent_ = true;
stime_t next_timeout = next_timer_deadline_ == 0.0 ? -1.0 :
std::max(0.0, next_timer_deadline_ - now);
- *timeout = SetNextDeadlineAndReturnTimeoutVal(now, next_timeout);
+ *timeout = SetNextDeadlineAndReturnTimeoutVal(now, fling_stop_deadline_,
+ next_timeout);
return;
}
// Call next_
@@ -162,7 +141,8 @@
}
stime_t next_timeout = -1.0;
next_->HandleTimer(now, &next_timeout);
- *timeout = SetNextDeadlineAndReturnTimeoutVal(now, next_timeout);
+ *timeout = SetNextDeadlineAndReturnTimeoutVal(now, fling_stop_deadline_,
+ next_timeout);
}
} // namespace gestures
diff --git a/src/integral_gesture_filter_interpreter.cc b/src/integral_gesture_filter_interpreter.cc
index 3360e63..d2f2ce4 100644
--- a/src/integral_gesture_filter_interpreter.cc
+++ b/src/integral_gesture_filter_interpreter.cc
@@ -8,6 +8,7 @@
#include "gestures/include/gestures.h"
#include "gestures/include/interpreter.h"
+#include "gestures/include/logging.h"
#include "gestures/include/tracer.h"
namespace gestures {
@@ -19,16 +20,51 @@
hscroll_remainder_(0.0),
vscroll_remainder_(0.0),
hscroll_ordinal_remainder_(0.0),
- vscroll_ordinal_remainder_(0.0) {
+ vscroll_ordinal_remainder_(0.0),
+ remainder_reset_deadline_(-1.0) {
InitName();
}
void IntegralGestureFilterInterpreter::SyncInterpretImpl(
HardwareState* hwstate, stime_t* timeout) {
- if (hwstate->finger_cnt == 0 && hwstate->touch_cnt == 0)
- hscroll_ordinal_remainder_ = vscroll_ordinal_remainder_ =
- hscroll_remainder_ = vscroll_remainder_ = 0.0;
- next_->SyncInterpret(hwstate, timeout);
+ can_clear_remainders_ = hwstate->finger_cnt == 0 && hwstate->touch_cnt == 0;
+ stime_t next_timeout = -1.0;
+ next_->SyncInterpret(hwstate, &next_timeout);
+ *timeout = SetNextDeadlineAndReturnTimeoutVal(
+ hwstate->timestamp, remainder_reset_deadline_, next_timeout);
+}
+
+void IntegralGestureFilterInterpreter::HandleTimerImpl(
+ stime_t now, stime_t *timeout) {
+ if (ShouldCallNextTimer(remainder_reset_deadline_)) {
+ if (next_timer_deadline_ > now) {
+ Err("Spurious callback. now: %f, next deadline: %f",
+ now, next_timer_deadline_);
+ return;
+ }
+
+ stime_t next_timeout = -1.0;
+ next_->HandleTimer(now, &next_timeout);
+ *timeout = SetNextDeadlineAndReturnTimeoutVal(now,
+ remainder_reset_deadline_,
+ next_timeout);
+ } else {
+ if (remainder_reset_deadline_ > now) {
+ Err("Spurious callback. now: %f, remainder reset deadline: %f",
+ now, remainder_reset_deadline_);
+ return;
+ }
+ if (can_clear_remainders_)
+ hscroll_ordinal_remainder_ = vscroll_ordinal_remainder_ =
+ hscroll_remainder_ = vscroll_remainder_ = 0.0;
+
+ remainder_reset_deadline_ = -1.0;
+ stime_t next_timeout = next_timer_deadline_ <= 0.0 ? -1.0 :
+ std::max(0.0, next_timer_deadline_ - now);
+ *timeout = SetNextDeadlineAndReturnTimeoutVal(now,
+ remainder_reset_deadline_,
+ next_timeout);
+ }
}
namespace {
@@ -69,6 +105,7 @@
ProduceGesture(Gesture(kGestureFling, copy.start_time, copy.end_time,
0, 0, GESTURES_FLING_TAP_DOWN));
}
+ remainder_reset_deadline_ = copy.end_time + 1.0;
break;
default:
ProduceGesture(gesture);
diff --git a/src/integral_gesture_filter_interpreter_unittest.cc b/src/integral_gesture_filter_interpreter_unittest.cc
index 77c19db..0821eca 100644
--- a/src/integral_gesture_filter_interpreter_unittest.cc
+++ b/src/integral_gesture_filter_interpreter_unittest.cc
@@ -38,7 +38,7 @@
}
virtual void HandleTimer(stime_t now, stime_t* timeout) {
- EXPECT_TRUE(false);
+ ADD_FAILURE() << "HandleTimer on the next interpreter shouldn't be called";
}
Gesture return_value_;
@@ -88,7 +88,8 @@
ASSERT_EQ(arraysize(expected_types), arraysize(expected_y));
for (size_t i = 0; i < arraysize(expected_x); i++) {
- Gesture* out = wrapper.SyncInterpret(&hs, NULL);
+ stime_t timeout;
+ Gesture* out = wrapper.SyncInterpret(&hs, &timeout);
if (out)
EXPECT_EQ(expected_types[i], out->type) << "i = " << i;
if (out == NULL) {
@@ -116,26 +117,30 @@
// causing finger, dx, dy, fingers, buttons down, buttons mask, hwstate:
base_interpreter->return_values_.push_back(
- Gesture(kGestureScroll, 0, 0, 3.9, 0.0));
+ Gesture(kGestureScroll, 10000.00, 10000.00, 3.9, 0.0));
+ Gesture empty_gesture = Gesture();
+ empty_gesture.start_time = 10000.01;
+ empty_gesture.end_time = 10000.01;
+ base_interpreter->return_values_.push_back(empty_gesture);
base_interpreter->return_values_.push_back(
- Gesture());
- base_interpreter->return_values_.push_back(
- Gesture(kGestureScroll, 0, 0, .2, 0.0));
+ Gesture(kGestureScroll, 10001.02, 10001.02, .2, 0.0));
FingerState fs = { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0 };
HardwareState hs[] = {
make_hwstate(10000.00, 0, 1, 1, &fs),
make_hwstate(10000.01, 0, 0, 0, NULL),
- make_hwstate(10000.02, 0, 1, 1, &fs),
+ make_hwstate(10001.02, 0, 1, 1, &fs),
};
size_t iter = 0;
- Gesture* out = wrapper.SyncInterpret(&hs[iter++], NULL);
+ stime_t timeout;
+ Gesture* out = wrapper.SyncInterpret(&hs[iter++], &timeout);
ASSERT_NE(reinterpret_cast<Gesture*>(NULL), out);
EXPECT_EQ(kGestureTypeScroll, out->type);
- out = wrapper.SyncInterpret(&hs[iter++], NULL);
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
- out = wrapper.SyncInterpret(&hs[iter++], NULL);
+ wrapper.HandleTimer(10001.02, &timeout);
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
}
@@ -159,10 +164,60 @@
};
size_t iter = 0;
- Gesture* out = wrapper.SyncInterpret(&hs[iter++], NULL);
+ stime_t timeout;
+ Gesture* out = wrapper.SyncInterpret(&hs[iter++], &timeout);
EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
- out = wrapper.SyncInterpret(&hs[iter++], NULL);
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
}
+// A bunch of scroll gestures with dy < 1 (such as the MS Surface Precision
+// mouse produces) should be combined into a smaller number of gestures.
+TEST(IntegralGestureFilterInterpreterTest, SlowScrollTest) {
+ IntegralGestureFilterInterpreterTestInterpreter* base_interpreter =
+ new IntegralGestureFilterInterpreterTestInterpreter;
+ IntegralGestureFilterInterpreter interpreter(base_interpreter, NULL);
+ TestInterpreterWrapper wrapper(&interpreter);
+
+ base_interpreter->return_values_.push_back(
+ Gesture(kGestureScroll, 10000.00, 10000.00, 0.0, 0.4));
+ base_interpreter->return_values_.push_back(
+ Gesture(kGestureScroll, 10000.05, 10000.05, 0.0, 0.4));
+ base_interpreter->return_values_.push_back(
+ Gesture(kGestureScroll, 10000.10, 10000.10, 0.0, 0.4));
+ base_interpreter->return_values_.push_back(
+ Gesture(kGestureScroll, 10000.15, 10000.15, 0.0, 0.4));
+ base_interpreter->return_values_.push_back(
+ Gesture(kGestureScroll, 10000.20, 10000.20, 0.0, 0.4));
+
+ HardwareState hs[] = {
+ make_hwstate(10000.00, 0, 0, 0, NULL),
+ make_hwstate(10000.05, 0, 0, 0, NULL),
+ make_hwstate(10000.10, 0, 0, 0, NULL),
+ make_hwstate(10000.15, 0, 0, 0, NULL),
+ make_hwstate(10000.20, 0, 0, 0, NULL),
+ };
+
+ size_t iter = 0;
+ stime_t timeout;
+ // The first two gestures should just add to the vertical scroll remainder.
+ Gesture* out = wrapper.SyncInterpret(&hs[iter++], &timeout);
+ EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
+ EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
+ // Then the remainder exceeds 1 so we should get a gesture.
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
+ EXPECT_NE(reinterpret_cast<Gesture*>(NULL), out);
+ EXPECT_EQ(kGestureTypeScroll, out->type);
+ EXPECT_FLOAT_EQ(1.0, out->details.scroll.dy);
+ // The next event just adds to the remainder again.
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
+ EXPECT_EQ(reinterpret_cast<Gesture*>(NULL), out);
+ // Then the remainder exceeds 1 again.
+ out = wrapper.SyncInterpret(&hs[iter++], &timeout);
+ EXPECT_NE(reinterpret_cast<Gesture*>(NULL), out);
+ EXPECT_EQ(kGestureTypeScroll, out->type);
+ EXPECT_FLOAT_EQ(1.0, out->details.scroll.dy);
+}
+
} // namespace gestures