• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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