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
MouseInterpreter(PropRegistry * prop_reg,Tracer * tracer)21 MouseInterpreter::MouseInterpreter(PropRegistry* prop_reg, Tracer* tracer)
22 : Interpreter(NULL, tracer, false),
23 wheel_emulation_accu_x_(0.0),
24 wheel_emulation_accu_y_(0.0),
25 wheel_emulation_active_(false),
26 reverse_scrolling_(prop_reg, "Mouse Reverse Scrolling", false),
27 hi_res_scrolling_(prop_reg, "Mouse High Resolution Scrolling", true),
28 scroll_accel_curve_prop_(prop_reg, "Mouse Scroll Accel Curve",
29 scroll_accel_curve_, sizeof(scroll_accel_curve_) / sizeof(double)),
30 scroll_max_allowed_input_speed_(prop_reg,
31 "Mouse Scroll Max Input Speed",
32 177.0),
33 force_scroll_wheel_emulation_(prop_reg,
34 "Force Scroll Wheel Emulation",
35 false),
36 scroll_wheel_emulation_speed_(prop_reg,
37 "Scroll Wheel Emulation Speed",
38 100.0),
39 scroll_wheel_emulation_thresh_(prop_reg,
40 "Scroll Wheel Emulation Threshold",
41 1.0),
42 output_mouse_wheel_gestures_(prop_reg,
43 "Output Mouse Wheel Gestures", false) {
44 InitName();
45 memset(&prev_state_, 0, sizeof(prev_state_));
46 memset(&last_wheel_, 0, sizeof(last_wheel_));
47 memset(&last_hwheel_, 0, sizeof(last_hwheel_));
48 // Scroll acceleration curve coefficients. See the definition for more
49 // details on how to generate them.
50 scroll_accel_curve_[0] = 1.0374e+01;
51 scroll_accel_curve_[1] = 4.1773e-01;
52 scroll_accel_curve_[2] = 2.5737e-02;
53 scroll_accel_curve_[3] = 8.0428e-05;
54 scroll_accel_curve_[4] = -9.1149e-07;
55 scroll_max_allowed_input_speed_.SetDelegate(this);
56 }
57
SyncInterpretImpl(HardwareState * hwstate,stime_t * timeout)58 void MouseInterpreter::SyncInterpretImpl(HardwareState* hwstate,
59 stime_t* timeout) {
60 if(!EmulateScrollWheel(*hwstate)) {
61 // Interpret mouse events in the order of pointer moves, scroll wheels and
62 // button clicks.
63 InterpretMouseMotionEvent(prev_state_, *hwstate);
64 // Note that unlike touchpad scrolls, we interpret and send separate events
65 // for horizontal/vertical mouse wheel scrolls. This is partly to match what
66 // the xf86-input-evdev driver does and is partly because not all code in
67 // Chrome honors MouseWheelEvent that has both X and Y offsets.
68 InterpretScrollWheelEvent(*hwstate, true);
69 InterpretScrollWheelEvent(*hwstate, false);
70 InterpretMouseButtonEvent(prev_state_, *hwstate);
71 }
72 // Pass max_finger_cnt = 0 to DeepCopy() since we don't care fingers and
73 // did not allocate any space for fingers.
74 prev_state_.DeepCopy(*hwstate, 0);
75 }
76
ComputeScrollAccelFactor(double input_speed)77 double MouseInterpreter::ComputeScrollAccelFactor(double input_speed) {
78 double result = 0.0;
79 double term = 1.0;
80 double allowed_speed = fabs(input_speed);
81 if (allowed_speed > scroll_max_allowed_input_speed_.val_)
82 allowed_speed = scroll_max_allowed_input_speed_.val_;
83
84 // Compute the scroll acceleration factor.
85 for (size_t i = 0; i < arraysize(scroll_accel_curve_); i++) {
86 result += term * scroll_accel_curve_[i];
87 term *= allowed_speed;
88 }
89 return result;
90 }
91
EmulateScrollWheel(const HardwareState & hwstate)92 bool MouseInterpreter::EmulateScrollWheel(const HardwareState& hwstate) {
93 if (!force_scroll_wheel_emulation_.val_ && hwprops_->has_wheel)
94 return false;
95
96 bool down = hwstate.buttons_down & GESTURES_BUTTON_MIDDLE ||
97 (hwstate.buttons_down & GESTURES_BUTTON_LEFT &&
98 hwstate.buttons_down & GESTURES_BUTTON_RIGHT);
99 bool prev_down = prev_state_.buttons_down & GESTURES_BUTTON_MIDDLE ||
100 (prev_state_.buttons_down & GESTURES_BUTTON_LEFT &&
101 prev_state_.buttons_down & GESTURES_BUTTON_RIGHT);
102 bool raising = down && !prev_down;
103 bool falling = !down && prev_down;
104
105 // Reset scroll emulation detection on button down.
106 if (raising) {
107 wheel_emulation_accu_x_ = 0;
108 wheel_emulation_accu_y_ = 0;
109 wheel_emulation_active_ = false;
110 }
111
112 // Send button event if button has been released without scrolling.
113 if (falling && !wheel_emulation_active_) {
114 ProduceGesture(Gesture(kGestureButtonsChange,
115 prev_state_.timestamp,
116 hwstate.timestamp,
117 prev_state_.buttons_down,
118 prev_state_.buttons_down,
119 false)); // is_tap
120 }
121
122 if (down) {
123 // Detect scroll emulation
124 if (!wheel_emulation_active_) {
125 wheel_emulation_accu_x_ += hwstate.rel_x;
126 wheel_emulation_accu_y_ += hwstate.rel_y;
127 double dist_sq = wheel_emulation_accu_x_ * wheel_emulation_accu_x_ +
128 wheel_emulation_accu_y_ * wheel_emulation_accu_y_;
129 double thresh_sq = scroll_wheel_emulation_thresh_.val_ *
130 scroll_wheel_emulation_thresh_.val_;
131 if (dist_sq > thresh_sq) {
132 // Lock into scroll emulation until button is released.
133 wheel_emulation_active_ = true;
134 }
135 }
136
137 // Transform motion into scrolling.
138 if (wheel_emulation_active_) {
139 double scroll_x = hwstate.rel_x * scroll_wheel_emulation_speed_.val_;
140 double scroll_y = hwstate.rel_y * scroll_wheel_emulation_speed_.val_;
141 ProduceGesture(Gesture(kGestureScroll, hwstate.timestamp,
142 hwstate.timestamp, scroll_x, scroll_y));
143 }
144 return true;
145 }
146
147 return false;
148 }
149
InterpretScrollWheelEvent(const HardwareState & hwstate,bool is_vertical)150 void MouseInterpreter::InterpretScrollWheelEvent(const HardwareState& hwstate,
151 bool is_vertical) {
152 const float scroll_wheel_event_time_delta_min = 0.008;
153 bool use_high_resolution =
154 is_vertical && hwprops_->wheel_is_hi_res
155 && hi_res_scrolling_.val_;
156 // Vertical wheel or horizontal wheel.
157 float current_wheel_value = hwstate.rel_hwheel;
158 int ticks = hwstate.rel_hwheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
159 WheelRecord* last_wheel_record = &last_hwheel_;
160 if (is_vertical) {
161 // Only vertical high-res scrolling is supported for now.
162 if (use_high_resolution) {
163 current_wheel_value = hwstate.rel_wheel_hi_res
164 / REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
165 ticks = hwstate.rel_wheel_hi_res;
166 } else {
167 current_wheel_value = hwstate.rel_wheel;
168 ticks = hwstate.rel_wheel * REL_WHEEL_HI_RES_UNITS_PER_NOTCH;
169 }
170 last_wheel_record = &last_wheel_;
171 }
172
173 // Check if the wheel is scrolled.
174 if (current_wheel_value) {
175 stime_t start_time, end_time = hwstate.timestamp;
176 // Check if this scroll is in same direction as previous scroll event.
177 if ((current_wheel_value < 0 && last_wheel_record->value < 0) ||
178 (current_wheel_value > 0 && last_wheel_record->value > 0)) {
179 start_time = last_wheel_record->timestamp;
180 } else {
181 start_time = end_time;
182 }
183
184 // If start_time == end_time, compute velocity using dt = 1 second.
185 // (this happens when the user initially starts scrolling)
186 stime_t dt = (end_time - start_time) ?: 1.0;
187 if (dt < scroll_wheel_event_time_delta_min) {
188 // the first packet received after BT wakeup may be delayed, causing the
189 // time delta between that and the subsequent packet to be very small.
190 // Prevent small time deltas from triggering large amounts of acceleration
191 // by enforcing a minimum time delta.
192 dt = scroll_wheel_event_time_delta_min;
193 }
194
195 float velocity = current_wheel_value / dt;
196 float offset = current_wheel_value * ComputeScrollAccelFactor(velocity);
197 last_wheel_record->timestamp = hwstate.timestamp;
198 last_wheel_record->value = current_wheel_value;
199
200 if (is_vertical) {
201 // For historical reasons the vertical wheel (REL_WHEEL) is inverted
202 if (!reverse_scrolling_.val_) {
203 offset = -offset;
204 ticks = -ticks;
205 }
206 ProduceGesture(
207 CreateWheelGesture(start_time, end_time, 0, offset, 0, ticks));
208 } else {
209 ProduceGesture(
210 CreateWheelGesture(start_time, end_time, offset, 0, ticks, 0));
211 }
212 }
213 }
214
CreateWheelGesture(stime_t start_time,stime_t end_time,float dx,float dy,int tick_120ths_dx,int tick_120ths_dy)215 Gesture MouseInterpreter::CreateWheelGesture(
216 stime_t start_time, stime_t end_time, float dx, float dy,
217 int tick_120ths_dx, int tick_120ths_dy) {
218 if (output_mouse_wheel_gestures_.val_) {
219 return Gesture(kGestureMouseWheel, start_time, end_time, dx, dy,
220 tick_120ths_dx, tick_120ths_dy);
221 } else {
222 return Gesture(kGestureScroll, start_time, end_time, dx, dy);
223 }
224 }
225
InterpretMouseButtonEvent(const HardwareState & prev_state,const HardwareState & hwstate)226 void MouseInterpreter::InterpretMouseButtonEvent(
227 const HardwareState& prev_state, const HardwareState& hwstate) {
228 const unsigned buttons[] = {
229 GESTURES_BUTTON_LEFT,
230 GESTURES_BUTTON_MIDDLE,
231 GESTURES_BUTTON_RIGHT,
232 GESTURES_BUTTON_BACK,
233 GESTURES_BUTTON_FORWARD
234 };
235 unsigned down = 0, up = 0;
236
237 for (unsigned i = 0; i < arraysize(buttons); i++) {
238 if (!(prev_state.buttons_down & buttons[i]) &&
239 (hwstate.buttons_down & buttons[i]))
240 down |= buttons[i];
241 if ((prev_state.buttons_down & buttons[i]) &&
242 !(hwstate.buttons_down & buttons[i]))
243 up |= buttons[i];
244 }
245
246 if (down || up) {
247 ProduceGesture(Gesture(kGestureButtonsChange,
248 prev_state.timestamp,
249 hwstate.timestamp,
250 down,
251 up,
252 false)); // is_tap
253 }
254 }
255
InterpretMouseMotionEvent(const HardwareState & prev_state,const HardwareState & hwstate)256 void MouseInterpreter::InterpretMouseMotionEvent(
257 const HardwareState& prev_state,
258 const HardwareState& hwstate) {
259 if (hwstate.rel_x || hwstate.rel_y) {
260 ProduceGesture(Gesture(kGestureMove,
261 prev_state.timestamp,
262 hwstate.timestamp,
263 hwstate.rel_x,
264 hwstate.rel_y));
265 }
266 }
267
268 } // namespace gestures
269