• 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/accel_filter_interpreter.h"
6 
7 #include <algorithm>
8 #include <math.h>
9 
10 #include "include/gestures.h"
11 #include "include/interpreter.h"
12 #include "include/logging.h"
13 #include "include/macros.h"
14 #include "include/tracer.h"
15 
16 namespace gestures {
17 
18 // Takes ownership of |next|:
AccelFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer)19 AccelFilterInterpreter::AccelFilterInterpreter(PropRegistry* prop_reg,
20                                                Interpreter* next,
21                                                Tracer* tracer)
22     : FilterInterpreter(NULL, next, tracer, false),
23       last_reasonable_dt_(0.05),
24       last_end_time_(-1.0),
25       last_mags_size_(0),
26 #pragma GCC diagnostic push
27 #pragma GCC diagnostic ignored "-Wsizeof-array-div"
28       // Hack: cast tp_custom_point_/mouse_custom_point_/tp_custom_scroll_
29       // to float arrays.
30       tp_custom_point_prop_(prop_reg, "Pointer Accel Curve",
31                             reinterpret_cast<double*>(&tp_custom_point_),
32                             sizeof(tp_custom_point_) / sizeof(double)),
33       tp_custom_scroll_prop_(prop_reg, "Scroll Accel Curve",
34                              reinterpret_cast<double*>(&tp_custom_scroll_),
35                              sizeof(tp_custom_scroll_) / sizeof(double)),
36       mouse_custom_point_prop_(prop_reg, "Mouse Pointer Accel Curve",
37                                reinterpret_cast<double*>(&mouse_custom_point_),
38                                sizeof(mouse_custom_point_) / sizeof(double)),
39 #pragma GCC diagnostic pop
40       use_custom_tp_point_curve_(
41           prop_reg, "Use Custom Touchpad Pointer Accel Curve", false),
42       use_custom_tp_scroll_curve_(
43           prop_reg, "Use Custom Touchpad Scroll Accel Curve", false),
44       use_custom_mouse_curve_(
45           prop_reg, "Use Custom Mouse Pointer Accel Curve", false),
46       pointer_sensitivity_(prop_reg, "Pointer Sensitivity", 3),
47       scroll_sensitivity_(prop_reg, "Scroll Sensitivity", 3),
48       point_x_out_scale_(prop_reg, "Point X Out Scale", 1.0),
49       point_y_out_scale_(prop_reg, "Point Y Out Scale", 1.0),
50       scroll_x_out_scale_(prop_reg, "Scroll X Out Scale", 2.5),
51       scroll_y_out_scale_(prop_reg, "Scroll Y Out Scale", 2.5),
52       use_mouse_point_curves_(prop_reg, "Mouse Accel Curves", false),
53       use_mouse_scroll_curves_(prop_reg, "Mouse Scroll Curves", false),
54       use_old_mouse_point_curves_(prop_reg, "Old Mouse Accel Curves", false),
55       pointer_acceleration_(prop_reg, "Pointer Acceleration", true),
56       min_reasonable_dt_(prop_reg, "Accel Min dt", 0.003),
57       max_reasonable_dt_(prop_reg, "Accel Max dt", 0.050),
58       smooth_accel_(prop_reg, "Smooth Accel", false) {
59   InitName();
60   // Set up default curves.
61 
62   // Our pointing curves are the following.
63   // x = input speed of movement (mm/s, always >= 0), y = output speed (mm/s)
64   // 1: y = x (No acceleration)
65   // 2: y = 32x/60   (x < 32), x^2/60   (x < 150), linear with same slope after
66   // 3: y = 32x/37.5 (x < 32), x^2/37.5 (x < 150), linear with same slope after
67   // 4: y = 32x/30   (x < 32), x^2/30   (x < 150), linear with same slope after
68   // 5: y = 32x/25   (x < 32), x^2/25   (x < 150), linear with same slope after
69 
70   const float point_divisors[] = { 0.0, // unused
71                                    60.0, 37.5, 30.0, 25.0 };  // used
72 
73 
74   // i starts as 1 b/c we skip the first slot, since the default is fine for it.
75   for (size_t i = 1; i < kMaxAccelCurves; ++i) {
76     const float divisor = point_divisors[i];
77     const float linear_until_x = 32.0;
78     const float init_slope = linear_until_x / divisor;
79     point_curves_[i][0] = CurveSegment(linear_until_x, 0, init_slope, 0);
80     const float x_border = 150;
81     point_curves_[i][1] = CurveSegment(x_border, 1 / divisor, 0, 0);
82     const float slope = x_border * 2 / divisor;
83     const float y_at_border = x_border * x_border / divisor;
84     const float icept = y_at_border - slope * x_border;
85     point_curves_[i][2] = CurveSegment(INFINITY, 0, slope, icept);
86   }
87 
88   // Setup unaccelerated touchpad curves. Each one is just a single linear
89   // segment with the slope from |unaccel_tp_slopes|.
90   const float unaccel_tp_slopes[] = { 1.0, 2.0, 3.0, 4.0, 5.0 };
91   for (size_t i = 0; i < kMaxAccelCurves; ++i) {
92     unaccel_point_curves_[i] = CurveSegment(
93         INFINITY, 0, unaccel_tp_slopes[i], 0);
94   }
95 
96   const float old_mouse_speed_straight_cutoff[] = { 5.0, 5.0, 5.0, 8.0, 8.0 };
97   const float old_mouse_speed_accel[] = { 1.0, 1.4, 1.8, 2.0, 2.2 };
98 
99   for (size_t i = 0; i < kMaxAccelCurves; ++i) {
100     const float kParabolaA = 1.3;
101     const float kParabolaB = 0.2;
102     const float cutoff_x = old_mouse_speed_straight_cutoff[i];
103     const float cutoff_y =
104         kParabolaA * cutoff_x * cutoff_x + kParabolaB * cutoff_x;
105     const float line_m = 2.0 * kParabolaA * cutoff_x + kParabolaB;
106     const float line_b = cutoff_y - cutoff_x * line_m;
107     const float kOutMult = old_mouse_speed_accel[i];
108 
109     old_mouse_point_curves_[i][0] =
110         CurveSegment(cutoff_x * 25.4, kParabolaA * kOutMult / 25.4,
111                      kParabolaB * kOutMult, 0.0);
112     old_mouse_point_curves_[i][1] = CurveSegment(INFINITY, 0.0, line_m * kOutMult,
113                                              line_b * kOutMult * 25.4);
114   }
115 
116   // These values were determined empirically through user studies:
117   const float kMouseMultiplierA = 0.0311;
118   const float kMouseMultiplierB = 3.26;
119   const float kMouseCutoff = 195.0;
120   const float kMultipliers[] = { 1.2, 1.4, 1.6, 1.8, 2.0 };
121   for (size_t i = 0; i < kMaxAccelCurves; ++i) {
122     float mouse_a = kMouseMultiplierA * kMultipliers[i] * kMultipliers[i];
123     float mouse_b = kMouseMultiplierB * kMultipliers[i];
124     float cutoff = kMouseCutoff / kMultipliers[i];
125     float second_slope =
126         (2.0 * kMouseMultiplierA * kMouseCutoff + kMouseMultiplierB) *
127         kMultipliers[i];
128     mouse_point_curves_[i][0] = CurveSegment(cutoff, mouse_a, mouse_b, 0.0);
129     mouse_point_curves_[i][1] = CurveSegment(INFINITY, 0.0, second_slope, -1182);
130   }
131 
132   // Setup unaccelerated mouse curves. Each one is just a single linear
133   // segment with the slope from |unaccel_mouse_slopes|.
134   const float unaccel_mouse_slopes[] = { 2.0, 4.0, 8.0, 16.0, 24.0 };
135   for (size_t i = 0; i < kMaxAccelCurves; ++i) {
136     unaccel_mouse_curves_[i] = CurveSegment(
137         INFINITY, 0, unaccel_mouse_slopes[i], 0);
138   }
139 
140   const float scroll_divisors[] = { 0.0, // unused
141                                     150, 75.0, 70.0, 65.0 };  // used
142   // Our scrolling curves are the following.
143   // x = input speed of movement (mm/s, always >= 0), y = output speed (mm/s)
144   // 1: y = x (No acceleration)
145   // 2: y = 75x/150   (x < 75), x^2/150   (x < 600), linear (initial slope).
146   // 3: y = 75x/75    (x < 75), x^2/75    (x < 600), linear (initial slope).
147   // 4: y = 75x/70    (x < 75), x^2/70    (x < 600), linear (initial slope).
148   // 5: y = 75x/65    (x < 75), x^2/65    (x < 600), linear (initial slope).
149   // i starts as 1 b/c we skip the first slot, since the default is fine for it.
150   for (size_t i = 1; i < kMaxAccelCurves; ++i) {
151     const float divisor = scroll_divisors[i];
152     const float linear_until_x = 75.0;
153     const float init_slope = linear_until_x / divisor;
154     scroll_curves_[i][0] = CurveSegment(linear_until_x, 0, init_slope, 0);
155     const float x_border = 600;
156     scroll_curves_[i][1] = CurveSegment(x_border, 1 / divisor, 0, 0);
157     // For scrolling / flinging we level off the speed.
158     const float slope = init_slope;
159     const float y_at_border = x_border * x_border / divisor;
160     const float icept = y_at_border - slope * x_border;
161     scroll_curves_[i][2] = CurveSegment(INFINITY, 0, slope, icept);
162   }
163 }
164 
ConsumeGesture(const Gesture & gs)165 void AccelFilterInterpreter::ConsumeGesture(const Gesture& gs) {
166   Gesture copy = gs;
167   CurveSegment* segs = NULL;
168   float* dx = NULL;
169   float* dy = NULL;
170 
171   // Check if clock changed backwards
172   if (last_end_time_ > gs.start_time)
173     last_end_time_ = -1.0;
174 
175   // Calculate dt and see if it's reasonable
176   float dt = copy.end_time - copy.start_time;
177   if (dt < min_reasonable_dt_.val_ || dt > max_reasonable_dt_.val_)
178     dt = last_reasonable_dt_;
179   else
180     last_reasonable_dt_ = dt;
181 
182   size_t max_segs = kMaxCurveSegs;
183   float x_scale = 1.0;
184   float y_scale = 1.0;
185   float mag = 0.0;
186   // The quantities to scale:
187   float* scale_out_x = NULL;
188   float* scale_out_y = NULL;
189   // We scale ordinal values of scroll/fling gestures as well because we use
190   // them in Chrome for history navigation (back/forward page gesture) and
191   // we will easily run out of the touchpad space if we just use raw values
192   // as they are. To estimate the length one needs to scroll on the touchpad
193   // to trigger the history navigation:
194   //
195   // Pixel:
196   // 1280 (screen width in DIPs) * 0.25 (overscroll threshold) /
197   // (133 / 25.4) (conversion factor from DIP to mm) = 61.1 mm
198   // Most other low-res devices:
199   // 1366 * 0.25 / (133 / 25.4) = 65.2 mm
200   //
201   // With current scroll output scaling factor (2.5), we can reduce the length
202   // required to about one inch on all devices.
203   float* scale_out_x_ordinal = NULL;
204   float* scale_out_y_ordinal = NULL;
205 
206   switch (copy.type) {
207     case kGestureTypeMove:
208     case kGestureTypeSwipe:
209     case kGestureTypeFourFingerSwipe:
210       if (copy.type == kGestureTypeMove) {
211         scale_out_x = dx = &copy.details.move.dx;
212         scale_out_y = dy = &copy.details.move.dy;
213       } else if (copy.type == kGestureTypeSwipe) {
214         scale_out_x = dx = &copy.details.swipe.dx;
215         scale_out_y = dy = &copy.details.swipe.dy;
216       } else {
217         scale_out_x = dx = &copy.details.four_finger_swipe.dx;
218         scale_out_y = dy = &copy.details.four_finger_swipe.dy;
219       }
220       if (use_mouse_point_curves_.val_ && use_custom_mouse_curve_.val_) {
221         segs = mouse_custom_point_;
222         max_segs = kMaxCustomCurveSegs;
223       } else if (!use_mouse_point_curves_.val_ &&
224                  use_custom_tp_point_curve_.val_) {
225         segs = tp_custom_point_;
226         max_segs = kMaxCustomCurveSegs;
227       } else {
228         if (use_mouse_point_curves_.val_) {
229           if (!pointer_acceleration_.val_) {
230             segs = &unaccel_mouse_curves_[pointer_sensitivity_.val_ - 1];
231             max_segs = 1;
232           } else if (use_old_mouse_point_curves_.val_) {
233             segs = old_mouse_point_curves_[pointer_sensitivity_.val_ - 1];
234           } else {
235             segs = mouse_point_curves_[pointer_sensitivity_.val_ - 1];
236           }
237         } else {
238           if (!pointer_acceleration_.val_) {
239             segs = &unaccel_point_curves_[pointer_sensitivity_.val_ - 1];
240             max_segs = 1;
241           } else {
242             segs = point_curves_[pointer_sensitivity_.val_ - 1];
243           }
244         }
245       }
246       x_scale = point_x_out_scale_.val_;
247       y_scale = point_y_out_scale_.val_;
248       break;
249     case kGestureTypeFling:  // fall through
250     case kGestureTypeScroll:
251       if (copy.type == kGestureTypeFling) {
252         float vx = copy.details.fling.vx;
253         float vy = copy.details.fling.vy;
254         mag = sqrtf(vx * vx + vy * vy);
255         scale_out_x = &copy.details.fling.vx;
256         scale_out_y = &copy.details.fling.vy;
257         scale_out_x_ordinal = &copy.details.fling.ordinal_vx;
258         scale_out_y_ordinal = &copy.details.fling.ordinal_vy;
259       } else {
260         scale_out_x = dx = &copy.details.scroll.dx;
261         scale_out_y = dy = &copy.details.scroll.dy;
262         scale_out_x_ordinal = &copy.details.scroll.ordinal_dx;
263         scale_out_y_ordinal = &copy.details.scroll.ordinal_dy;
264       }
265       // We bypass mouse scroll events as they have a separate acceleration
266       // algorithm implemented in mouse_interpreter.
267       if (use_mouse_scroll_curves_.val_) {
268         ProduceGesture(gs);
269         return;
270       }
271       if (!use_custom_tp_scroll_curve_.val_) {
272         segs = scroll_curves_[scroll_sensitivity_.val_ - 1];
273       } else {
274         segs = tp_custom_scroll_;
275         max_segs = kMaxCustomCurveSegs;
276       }
277       x_scale = scroll_x_out_scale_.val_;
278       y_scale = scroll_y_out_scale_.val_;
279       break;
280     default:  // Nothing to accelerate
281       ProduceGesture(gs);
282       return;
283   }
284 
285   if (dx != NULL && dy != NULL) {
286     if (dt < 0.00001) {
287       ProduceGesture(gs);
288       return;  // Avoid division by 0
289     }
290     mag = sqrtf(*dx * *dx + *dy * *dy) / dt;
291   }
292 
293   if (smooth_accel_.val_) {
294     if (last_end_time_ == gs.start_time) {
295       float new_mag = mag;
296       if (last_mags_size_ < arraysize(last_mags_))
297           last_mags_[last_mags_size_] = last_mags_[last_mags_size_ - 1];
298       for (size_t i = last_mags_size_ - 1; i > 0; i--) {
299         new_mag += last_mags_[i];
300         last_mags_[i] = last_mags_[i - 1];
301       }
302       new_mag += last_mags_[0];
303       new_mag /= last_mags_size_ + 1;
304 
305       last_mags_[0] = mag;
306       last_mags_size_ = std::min(arraysize(last_mags_), last_mags_size_ + 1);
307       mag = new_mag;
308     } else {
309       last_mags_size_ = 1;
310       last_mags_[0] = mag;
311     }
312     last_end_time_ = gs.end_time;
313   }
314 
315   if (mag < 0.00001) {
316     if (gs.type == kGestureTypeFling)
317       ProduceGesture(gs);  // Filter out zero length gestures
318     return;  // Avoid division by 0
319   }
320 
321   for (size_t i = 0; i < max_segs; ++i) {
322     if (mag > segs[i].x_)
323       continue;
324     float ratio = segs[i].sqr_ * mag + segs[i].mul_ + segs[i].int_ / mag;
325     *scale_out_x *= ratio * x_scale;
326     *scale_out_y *= ratio * y_scale;
327     if (copy.type == kGestureTypeFling ||
328         copy.type == kGestureTypeScroll) {
329       // We don't accelerate the ordinal values as we do for normal ones
330       // because this is how the Chrome needs it.
331       *scale_out_x_ordinal *= x_scale;
332       *scale_out_y_ordinal *= y_scale;
333     }
334     ProduceGesture(copy);
335     return;
336   }
337 }
338 
339 }  // namespace gestures
340