1 // Copyright 2012 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "include/mouse_interpreter.h"
6
7 #include <math.h>
8
9 #include "include/logging.h"
10 #include "include/macros.h"
11 #include "include/tracer.h"
12
13 namespace gestures {
14
15 /*
16 * The number of subdivisions that `REL_WHEEL_HI_RES` and `REL_HWHEEL_HI_RES`
17 * have relative to `REL_WHEEL` and `REL_HWHEEL` respectively.
18 */
19 const static int REL_WHEEL_HI_RES_UNITS_PER_NOTCH = 120;
20
21 // Default value for mouse scroll sensitivity.
22 const static int kMouseScrollSensitivityDefaultValue = 3;
23
MouseInterpreter(PropRegistry * prop_reg,Tracer * tracer)24 MouseInterpreter::MouseInterpreter(PropRegistry* prop_reg, Tracer* tracer)
25 : Interpreter(nullptr, tracer, false),
26 wheel_emulation_accu_x_(0.0),
27 wheel_emulation_accu_y_(0.0),
28 wheel_emulation_active_(false),
29 reverse_scrolling_(prop_reg, "Mouse Reverse Scrolling", false),
30 scroll_acceleration_(prop_reg, "Mouse Scroll Acceleration", true),
31 scroll_sensitivity_(prop_reg,"Mouse Scroll Sensitivity",
32 kMouseScrollSensitivityDefaultValue),
33 hi_res_scrolling_(prop_reg, "Mouse High Resolution Scrolling", true),
34 scroll_velocity_buffer_size_(prop_reg, "Scroll Wheel Velocity Buffer", 3),
35 scroll_accel_curve_prop_(prop_reg, "Mouse Scroll Accel Curve",
36 scroll_accel_curve_, sizeof(scroll_accel_curve_) / sizeof(double)),
37 scroll_max_allowed_input_speed_(prop_reg,
38 "Mouse Scroll Max Input Speed",
39 177.0),
40 force_scroll_wheel_emulation_(prop_reg,
41 "Force Scroll Wheel Emulation",
42 false),
43 scroll_wheel_emulation_speed_(prop_reg,
44 "Scroll Wheel Emulation Speed",
45 100.0),
46 scroll_wheel_emulation_thresh_(prop_reg,
47 "Scroll Wheel Emulation Threshold",
48 1.0),
49 output_mouse_wheel_gestures_(prop_reg,
50 "Output Mouse Wheel Gestures", false) {
51 InitName();
52 memset(&prev_state_, 0, sizeof(prev_state_));
53 // Scroll acceleration curve coefficients. See the definition for more
54 // details on how to generate them.
55 scroll_accel_curve_[0] = 1.0374e+01;
56 scroll_accel_curve_[1] = 4.1773e-01;
57 scroll_accel_curve_[2] = 2.5737e-02;
58 scroll_accel_curve_[3] = 8.0428e-05;
59 scroll_accel_curve_[4] = -9.1149e-07;
60 scroll_max_allowed_input_speed_.SetDelegate(this);
61 }
62
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)63 void MouseInterpreter::SyncInterpretImpl(HardwareState& hwstate,
64 stime_t* timeout) {
65 const char name[] = "MouseInterpreter::SyncInterpretImpl";
66 LogHardwareStatePre(name, hwstate);
67
68 if(!EmulateScrollWheel(hwstate)) {
69 // Interpret mouse events in the order of pointer moves, scroll wheels and
70 // button clicks.
71 InterpretMouseMotionEvent(prev_state_, hwstate);
72 // Note that unlike touchpad scrolls, we interpret and send separate events
73 // for horizontal/vertical mouse wheel scrolls. This is partly to match what
74 // the xf86-input-evdev driver does and is partly because not all code in
75 // Chrome honors MouseWheelEvent that has both X and Y offsets.
76 InterpretScrollWheelEvent(hwstate, true);
77 InterpretScrollWheelEvent(hwstate, false);
78 InterpretMouseButtonEvent(prev_state_, hwstate);
79 }
80 // Pass max_finger_cnt = 0 to DeepCopy() since we don't care fingers and
81 // did not allocate any space for fingers.
82 prev_state_.DeepCopy(hwstate, 0);
83
84 LogHardwareStatePost(name, hwstate);
85 }
86
ComputeScrollAccelFactor(double input_speed)87 double MouseInterpreter::ComputeScrollAccelFactor(double input_speed) {
88 double result = 0.0;
89 double term = 1.0;
90 double allowed_speed = fabs(input_speed);
91 if (allowed_speed > scroll_max_allowed_input_speed_.val_)
92 allowed_speed = scroll_max_allowed_input_speed_.val_;
93
94 // Compute the scroll acceleration factor.
95 for (size_t i = 0; i < arraysize(scroll_accel_curve_); i++) {
96 result += term * scroll_accel_curve_[i];
97 term *= allowed_speed;
98 }
99 return result;
100 }
101
EmulateScrollWheel(const HardwareState & hwstate)102 bool MouseInterpreter::EmulateScrollWheel(const HardwareState& hwstate) {
103 const char name[] = "MouseInterpreter::EmulateScrollWheel";
104
105 if (!force_scroll_wheel_emulation_.val_ && hwprops_->has_wheel)
106 return false;
107 bool down = hwstate.buttons_down & GESTURES_BUTTON_MIDDLE ||
108 (hwstate.buttons_down & GESTURES_BUTTON_LEFT &&
109 hwstate.buttons_down & GESTURES_BUTTON_RIGHT);
110 bool prev_down = prev_state_.buttons_down & GESTURES_BUTTON_MIDDLE ||
111 (prev_state_.buttons_down & GESTURES_BUTTON_LEFT &&
112 prev_state_.buttons_down & GESTURES_BUTTON_RIGHT);
113 bool raising = down && !prev_down;
114 bool falling = !down && prev_down;
115
116 // Reset scroll emulation detection on button down.
117 if (raising) {
118 wheel_emulation_accu_x_ = 0;
119 wheel_emulation_accu_y_ = 0;
120 wheel_emulation_active_ = false;
121 }
122
123 // Send button event if button has been released without scrolling.
124 if (falling && !wheel_emulation_active_) {
125 auto button_change = Gesture(kGestureButtonsChange,
126 prev_state_.timestamp,
127 hwstate.timestamp,
128 prev_state_.buttons_down,
129 prev_state_.buttons_down,
130 false); // is_tap
131 LogGestureProduce(name, button_change);
132 ProduceGesture(button_change);
133 }
134
135 if (down) {
136 // Detect scroll emulation
137 if (!wheel_emulation_active_) {
138 wheel_emulation_accu_x_ += hwstate.rel_x;
139 wheel_emulation_accu_y_ += hwstate.rel_y;
140 double dist_sq = wheel_emulation_accu_x_ * wheel_emulation_accu_x_ +
141 wheel_emulation_accu_y_ * wheel_emulation_accu_y_;
142 double thresh_sq = scroll_wheel_emulation_thresh_.val_ *
143 scroll_wheel_emulation_thresh_.val_;
144 if (dist_sq > thresh_sq) {
145 // Lock into scroll emulation until button is released.
146 wheel_emulation_active_ = true;
147 }
148 }
149
150 // Transform motion into scrolling.
151 if (wheel_emulation_active_) {
152 double scroll_x = hwstate.rel_x * scroll_wheel_emulation_speed_.val_;
153 double scroll_y = hwstate.rel_y * scroll_wheel_emulation_speed_.val_;
154
155 auto scroll = Gesture(kGestureScroll, hwstate.timestamp,
156 hwstate.timestamp, scroll_x, scroll_y);
157 LogGestureProduce(name, scroll);
158 ProduceGesture(scroll);
159 }
160 return true;
161 }
162
163 return false;
164 }
165
InterpretScrollWheelEvent(const HardwareState & hwstate,bool is_vertical)166 void MouseInterpreter::InterpretScrollWheelEvent(const HardwareState& hwstate,
167 bool is_vertical) {
168 const char name[] = "MouseInterpreter::InterpretScrollWheelEvent";
169
170 const size_t max_buffer_size = scroll_velocity_buffer_size_.val_;
171 const float scroll_wheel_event_time_delta_min = 0.008 * max_buffer_size;
172 bool use_high_resolution =
173 is_vertical && hwprops_->wheel_is_hi_res
174 && hi_res_scrolling_.val_;
175 // Vertical wheel or horizontal wheel.
176 WheelRecord current_wheel;
177 current_wheel.timestamp = hwstate.timestamp;
178 int ticks;
179 std::vector<WheelRecord>* last_wheels;
180 if (is_vertical) {
181 // Only vertical high-res scrolling is supported for now.
182 if (use_high_resolution) {
183 current_wheel.change = hwstate.rel_wheel_hi_res
184 / REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
185 ticks = hwstate.rel_wheel_hi_res;
186 } else {
187 current_wheel.change = hwstate.rel_wheel;
188 ticks = hwstate.rel_wheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
189 }
190 last_wheels = &last_vertical_wheels_;
191 } else {
192 last_wheels = &last_horizontal_wheels_;
193 current_wheel.change = hwstate.rel_hwheel;
194 ticks = hwstate.rel_hwheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
195 }
196
197 // Check if the wheel is scrolled.
198 if (current_wheel.change) {
199 stime_t start_time, end_time = hwstate.timestamp;
200 // Check if this scroll is in same direction as previous scroll event.
201 if (!last_wheels->empty() &&
202 ((current_wheel.change < 0 && last_wheels->back().change < 0) ||
203 (current_wheel.change > 0 && last_wheels->back().change > 0))) {
204 start_time = last_wheels->begin()->timestamp;
205 } else {
206 last_wheels->clear();
207 start_time = end_time;
208 }
209
210 // We will only accelerate scrolls if we have filled our buffer of scroll
211 // events all in the same direction. If the buffer is full, then calculate
212 // scroll velocity using the average velocity of the entire buffer.
213 float velocity;
214 if (last_wheels->size() < max_buffer_size) {
215 velocity = 0.0;
216 } else {
217 stime_t dt = end_time - last_wheels->back().timestamp;
218 if (dt < scroll_wheel_event_time_delta_min) {
219 // The first packets received after BT wakeup may be delayed, causing
220 // the time delta between that and the subsequent packets to be
221 // artificially very small.
222 // Prevent small time deltas from triggering large amounts of
223 // acceleration by enforcing a minimum time delta.
224 dt = scroll_wheel_event_time_delta_min;
225 }
226
227 last_wheels->pop_back();
228 float buffer_scroll_distance = current_wheel.change;
229 for (auto wheel : *last_wheels) {
230 buffer_scroll_distance += wheel.change;
231 }
232
233 velocity = buffer_scroll_distance / dt;
234 }
235 last_wheels->insert(last_wheels->begin(), current_wheel);
236
237 // When scroll acceleration is off, the scroll factor does not relate to
238 // scroll velocity. It's simply a constant multiplier to the wheel value.
239 const double unaccel_scroll_factors[] = { 20.0, 36.0, 72.0, 112.0, 164.0 };
240
241 float offset = current_wheel.change * (
242 scroll_acceleration_.val_?
243 ComputeScrollAccelFactor(velocity) :
244 unaccel_scroll_factors[scroll_sensitivity_.val_ - 1]);
245
246 if (is_vertical) {
247 // For historical reasons the vertical wheel (REL_WHEEL) is inverted
248 if (!reverse_scrolling_.val_) {
249 offset = -offset;
250 ticks = -ticks;
251 }
252 auto scroll_wheel = CreateWheelGesture(start_time, end_time,
253 0, offset, 0, ticks);
254 LogGestureProduce(name, scroll_wheel);
255 ProduceGesture(scroll_wheel);
256 } else {
257 auto scroll_wheel = CreateWheelGesture(start_time, end_time,
258 offset, 0, ticks, 0);
259 LogGestureProduce(name, scroll_wheel);
260 ProduceGesture(scroll_wheel);
261 }
262 }
263 }
264
CreateWheelGesture(stime_t start_time,stime_t end_time,float dx,float dy,int tick_120ths_dx,int tick_120ths_dy)265 Gesture MouseInterpreter::CreateWheelGesture(
266 stime_t start_time, stime_t end_time, float dx, float dy,
267 int tick_120ths_dx, int tick_120ths_dy) {
268 if (output_mouse_wheel_gestures_.val_) {
269 return Gesture(kGestureMouseWheel, start_time, end_time, dx, dy,
270 tick_120ths_dx, tick_120ths_dy);
271 } else {
272 return Gesture(kGestureScroll, start_time, end_time, dx, dy);
273 }
274 }
275
InterpretMouseButtonEvent(const HardwareState & prev_state,const HardwareState & hwstate)276 void MouseInterpreter::InterpretMouseButtonEvent(
277 const HardwareState& prev_state, const HardwareState& hwstate) {
278 const char name[] = "MouseInterpreter::InterpretMouseButtonEvent";
279
280 const unsigned buttons[] = {
281 GESTURES_BUTTON_LEFT,
282 GESTURES_BUTTON_MIDDLE,
283 GESTURES_BUTTON_RIGHT,
284 GESTURES_BUTTON_BACK,
285 GESTURES_BUTTON_FORWARD,
286 GESTURES_BUTTON_SIDE,
287 GESTURES_BUTTON_EXTRA,
288 };
289 unsigned down = 0, up = 0;
290
291 for (unsigned i = 0; i < arraysize(buttons); i++) {
292 if (!(prev_state.buttons_down & buttons[i]) &&
293 (hwstate.buttons_down & buttons[i]))
294 down |= buttons[i];
295 if ((prev_state.buttons_down & buttons[i]) &&
296 !(hwstate.buttons_down & buttons[i]))
297 up |= buttons[i];
298 }
299
300 if (down || up) {
301 auto button_change = Gesture(kGestureButtonsChange,
302 prev_state.timestamp,
303 hwstate.timestamp,
304 down,
305 up,
306 false); // is_tap
307 LogGestureProduce(name, button_change);
308 ProduceGesture(button_change);
309 }
310 }
311
InterpretMouseMotionEvent(const HardwareState & prev_state,const HardwareState & hwstate)312 void MouseInterpreter::InterpretMouseMotionEvent(
313 const HardwareState& prev_state,
314 const HardwareState& hwstate) {
315 const char name[] = "MouseInterpreter::InterpretMouseMotionEvent";
316
317 if (hwstate.rel_x || hwstate.rel_y) {
318 auto move = Gesture(kGestureMove,
319 prev_state.timestamp,
320 hwstate.timestamp,
321 hwstate.rel_x,
322 hwstate.rel_y);
323 LogGestureProduce(name, move);
324 ProduceGesture(move);
325 }
326 }
327
328 } // namespace gestures
329