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/scaling_filter_interpreter.h"
6
7 #include <math.h>
8
9 #include "include/gestures.h"
10 #include "include/interpreter.h"
11 #include "include/logging.h"
12 #include "include/tracer.h"
13
14 namespace gestures {
15
16 // Takes ownership of |next|:
ScalingFilterInterpreter(PropRegistry * prop_reg,Interpreter * next,Tracer * tracer,GestureInterpreterDeviceClass devclass)17 ScalingFilterInterpreter::ScalingFilterInterpreter(
18 PropRegistry* prop_reg, Interpreter* next, Tracer* tracer,
19 GestureInterpreterDeviceClass devclass)
20 : FilterInterpreter(nullptr, next, tracer, false),
21 tp_x_scale_(1.0),
22 tp_y_scale_(1.0),
23 tp_x_translate_(0.0),
24 tp_y_translate_(0.0),
25 screen_x_scale_(1.0),
26 screen_y_scale_(1.0),
27 orientation_scale_(1.0),
28 invert_scrolling_and_swiping_(prop_reg, "Australian Scrolling", false),
29 invert_scrolling_only_(prop_reg, "Invert Scrolling", false),
30 surface_area_from_pressure_(prop_reg,
31 "Compute Surface Area from Pressure", true),
32 use_touch_size_for_haptic_pad_(
33 prop_reg,
34 "Compute Surface Area from Touch Size for Haptic Pads",
35 false),
36 tp_x_bias_(prop_reg, "Touchpad Device Output Bias on X-Axis", 0.0),
37 tp_y_bias_(prop_reg, "Touchpad Device Output Bias on Y-Axis", 0.0),
38 pressure_scale_(prop_reg, "Pressure Calibration Slope", 1.0),
39 pressure_translate_(prop_reg, "Pressure Calibration Offset", 0.0),
40 pressure_threshold_(prop_reg, "Pressure Minimum Threshold", 0.0),
41 filter_low_pressure_(prop_reg, "Filter Low Pressure", false),
42 force_touch_count_to_match_finger_count_(
43 prop_reg,
44 "Force Touch Count To Match Finger Count",
45 false),
46 mouse_cpi_(prop_reg, "Mouse CPI", 1000.0),
47 device_mouse_(prop_reg, "Device Mouse", IsMouseDevice(devclass)),
48 device_pointing_stick_(prop_reg, "Device Pointing Stick",
49 IsPointingStick(devclass)),
50 device_touchpad_(prop_reg,
51 "Device Touchpad",
52 IsTouchpadDevice(devclass)) {
53 InitName();
54 }
55
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)56 void ScalingFilterInterpreter::SyncInterpretImpl(HardwareState& hwstate,
57 stime_t* timeout) {
58 const char name[] = "ScalingFilterInterpreter::SyncInterpretImpl";
59 LogHardwareStatePre(name, hwstate);
60
61 ScaleHardwareState(hwstate);
62
63 LogHardwareStatePost(name, hwstate);
64 next_->SyncInterpret(hwstate, timeout);
65 }
66
67 // Ignore the finger events with low pressure values especially for the SEMI_MT
68 // devices such as Synaptics touchpad on Cr-48.
FilterLowPressure(HardwareState & hwstate)69 void ScalingFilterInterpreter::FilterLowPressure(HardwareState& hwstate) {
70 unsigned short finger_cnt = hwstate.finger_cnt;
71 unsigned short touch_cnt = hwstate.touch_cnt;
72 float threshold = 0.0;
73
74 // If a button is down, only filter 0 pressure fingers:
75 // Pressing fingers might have low pressure, but hovering fingers are
76 // reported as fingers with 0 pressure and should still be filtered.
77 if (pressure_scale_.val_ > 0.0 && !hwstate.buttons_down) {
78 threshold = (pressure_threshold_.val_ - pressure_translate_.val_)
79 / pressure_scale_.val_ ;
80 }
81 for (short i = finger_cnt - 1 ; i >= 0; i--) {
82 if (hwstate.fingers[i].pressure <= threshold) {
83 if (i != finger_cnt - 1)
84 hwstate.fingers[i] = hwstate.fingers[finger_cnt - 1];
85 finger_cnt--;
86 if (touch_cnt > 0)
87 touch_cnt--;
88 }
89 }
90 hwstate.finger_cnt = finger_cnt;
91 hwstate.touch_cnt = touch_cnt;
92 }
93
FilterZeroArea(HardwareState & hwstate)94 void ScalingFilterInterpreter::FilterZeroArea(HardwareState& hwstate) {
95 unsigned short finger_cnt = hwstate.finger_cnt;
96 unsigned short touch_cnt = hwstate.touch_cnt;
97 for (short i = finger_cnt - 1 ; i >= 0; i--) {
98 if (hwstate.fingers[i].pressure == 0.0) {
99 if (i != finger_cnt - 1)
100 hwstate.fingers[i] = hwstate.fingers[finger_cnt - 1];
101 finger_cnt--;
102 if (touch_cnt > 0)
103 touch_cnt--;
104 }
105 }
106 hwstate.finger_cnt = finger_cnt;
107 hwstate.touch_cnt = touch_cnt;
108 }
109
IsMouseDevice(GestureInterpreterDeviceClass devclass)110 bool ScalingFilterInterpreter::IsMouseDevice(
111 GestureInterpreterDeviceClass devclass) {
112 return (devclass == GESTURES_DEVCLASS_MOUSE ||
113 devclass == GESTURES_DEVCLASS_MULTITOUCH_MOUSE);
114 }
115
IsPointingStick(GestureInterpreterDeviceClass devclass)116 bool ScalingFilterInterpreter::IsPointingStick(
117 GestureInterpreterDeviceClass devclass) {
118 return devclass == GESTURES_DEVCLASS_POINTING_STICK;
119 }
120
IsTouchpadDevice(GestureInterpreterDeviceClass devclass)121 bool ScalingFilterInterpreter::IsTouchpadDevice(
122 GestureInterpreterDeviceClass devclass) {
123 return (devclass == GESTURES_DEVCLASS_TOUCHPAD ||
124 devclass == GESTURES_DEVCLASS_MULTITOUCH_MOUSE ||
125 devclass == GESTURES_DEVCLASS_TOUCHSCREEN);
126 }
127
ScaleHardwareState(HardwareState & hwstate)128 void ScalingFilterInterpreter::ScaleHardwareState(HardwareState& hwstate) {
129 if (device_touchpad_.val_)
130 ScaleTouchpadHardwareState(hwstate);
131 if (device_mouse_.val_ || device_pointing_stick_.val_)
132 ScaleMouseHardwareState(hwstate);
133 }
134
ScaleMouseHardwareState(HardwareState & hwstate)135 void ScalingFilterInterpreter::ScaleMouseHardwareState(
136 HardwareState& hwstate) {
137 hwstate.rel_x = hwstate.rel_x / mouse_cpi_.val_ * 25.4;
138 hwstate.rel_y = hwstate.rel_y / mouse_cpi_.val_ * 25.4;
139 // TODO(clchiou): Scale rel_wheel and rel_hwheel
140 }
141
ScaleTouchpadHardwareState(HardwareState & hwstate)142 void ScalingFilterInterpreter::ScaleTouchpadHardwareState(
143 HardwareState& hwstate) {
144 if (force_touch_count_to_match_finger_count_.val_)
145 hwstate.touch_cnt = hwstate.finger_cnt;
146
147 if (surface_area_from_pressure_.val_) {
148 // Drop the small fingers, i.e. low pressures.
149 if (filter_low_pressure_.val_ || pressure_threshold_.val_ > 0.0)
150 FilterLowPressure(hwstate);
151 }
152
153 for (short i = 0; i < hwstate.finger_cnt; i++) {
154 float cos_2_orit = 0.0, sin_2_orit = 0.0, rx_2 = 0.0, ry_2 = 0.0;
155 hwstate.fingers[i].position_x *= tp_x_scale_;
156 hwstate.fingers[i].position_x += tp_x_translate_;
157 hwstate.fingers[i].position_y *= tp_y_scale_;
158 hwstate.fingers[i].position_y += tp_y_translate_;
159
160 // TODO(clchiou): Output orientation is computed on a pixel-unit circle,
161 // and it is only equal to the orientation computed on a mm-unit circle
162 // when tp_x_scale_ == tp_y_scale_. Since what we really want is the
163 // latter, fix this!
164 hwstate.fingers[i].orientation *= orientation_scale_;
165
166 if (hwstate.fingers[i].touch_major || hwstate.fingers[i].touch_minor) {
167 cos_2_orit = cosf(hwstate.fingers[i].orientation);
168 cos_2_orit *= cos_2_orit;
169 sin_2_orit = sinf(hwstate.fingers[i].orientation);
170 sin_2_orit *= sin_2_orit;
171 rx_2 = tp_x_scale_ * tp_x_scale_;
172 ry_2 = tp_y_scale_ * tp_y_scale_;
173 }
174 if (hwstate.fingers[i].touch_major) {
175 float bias = tp_x_bias_.val_ * sin_2_orit + tp_y_bias_.val_ * cos_2_orit;
176 hwstate.fingers[i].touch_major =
177 fabsf(hwstate.fingers[i].touch_major - bias) *
178 sqrtf(rx_2 * sin_2_orit + ry_2 * cos_2_orit);
179 }
180 if (hwstate.fingers[i].touch_minor) {
181 float bias = tp_x_bias_.val_ * cos_2_orit + tp_y_bias_.val_ * sin_2_orit;
182 hwstate.fingers[i].touch_minor =
183 fabsf(hwstate.fingers[i].touch_minor - bias) *
184 sqrtf(rx_2 * cos_2_orit + ry_2 * sin_2_orit);
185 }
186
187 // After calibration, touch_major could be smaller than touch_minor.
188 // If so, swap them here and update orientation.
189 if (orientation_scale_ &&
190 hwstate.fingers[i].touch_major < hwstate.fingers[i].touch_minor) {
191 std::swap(hwstate.fingers[i].touch_major,
192 hwstate.fingers[i].touch_minor);
193 if (hwstate.fingers[i].orientation > 0.0)
194 hwstate.fingers[i].orientation -= M_PI_2;
195 else
196 hwstate.fingers[i].orientation += M_PI_2;
197 }
198
199 if (surface_area_from_pressure_.val_) {
200 hwstate.fingers[i].pressure *= pressure_scale_.val_;
201 hwstate.fingers[i].pressure += pressure_translate_.val_;
202 } else {
203 if (hwstate.fingers[i].touch_major && hwstate.fingers[i].touch_minor)
204 hwstate.fingers[i].pressure = M_PI_4 *
205 hwstate.fingers[i].touch_major * hwstate.fingers[i].touch_minor;
206 else if (hwstate.fingers[i].touch_major)
207 hwstate.fingers[i].pressure = M_PI_4 *
208 hwstate.fingers[i].touch_major * hwstate.fingers[i].touch_major;
209 else
210 hwstate.fingers[i].pressure = 0;
211 }
212
213 hwstate.fingers[i].pressure = std::max(1.0f, hwstate.fingers[i].pressure);
214 }
215
216 if (!surface_area_from_pressure_.val_) {
217 FilterZeroArea(hwstate);
218 }
219 }
220
ConsumeGesture(const Gesture & gs)221 void ScalingFilterInterpreter::ConsumeGesture(const Gesture& gs) {
222 const char name[] = "ScalingFilterInterpreter::ConsumeGesture";
223 LogGestureConsume(name, gs);
224
225 Gesture copy = gs;
226 switch (copy.type) {
227 case kGestureTypeMove: {
228 int original_rel_x =
229 copy.details.move.ordinal_dx * mouse_cpi_.val_ / 25.4;
230 int original_rel_y =
231 copy.details.move.ordinal_dy * mouse_cpi_.val_ / 25.4;
232 copy.details.move.dx *= screen_x_scale_;
233 copy.details.move.dy *= screen_y_scale_;
234 copy.details.move.ordinal_dx *= screen_x_scale_;
235 copy.details.move.ordinal_dy *= screen_y_scale_;
236 // Special case of motion: if a mouse move of 1 device unit
237 // (rel_[xy] == 1) would move the cursor on the screen > 1
238 // pixel, force it to just one pixel. This prevents low-DPI mice
239 // from jumping 2 pixels at a time when doing slow moves.
240 // Note, we use 1 / 1.2 = 0.8333 instead of 1 for the number of
241 // screen pixels, as external monitors get a 20% distance boost.
242 // Mice are most commonly used w/ external displays.
243 if (device_mouse_.val_ &&
244 ((original_rel_x == 0) != (original_rel_y == 0))) {
245 const double kMinPixels = 1.0 / 1.2;
246 if (fabs(copy.details.move.dx) > kMinPixels &&
247 abs(original_rel_x) == 1) {
248 copy.details.move.dx = copy.details.move.ordinal_dx =
249 copy.details.move.dx > 0.0 ? kMinPixels : -kMinPixels;
250 }
251 if (fabs(copy.details.move.dy) > kMinPixels &&
252 abs(original_rel_y) == 1) {
253 copy.details.move.dy = copy.details.move.ordinal_dy =
254 copy.details.move.dy > 0.0 ? kMinPixels : -kMinPixels;
255 }
256 }
257 break;
258 }
259 case kGestureTypeScroll:
260 if (device_touchpad_.val_) {
261 copy.details.scroll.dx *= screen_x_scale_;
262 copy.details.scroll.dy *= screen_y_scale_;
263 copy.details.scroll.ordinal_dx *= screen_x_scale_;
264 copy.details.scroll.ordinal_dy *= screen_y_scale_;
265 }
266 if (!(invert_scrolling_and_swiping_.val_ ||
267 invert_scrolling_only_.val_)) {
268 copy.details.scroll.dx *= -1;
269 copy.details.scroll.dy *= -1;
270 copy.details.scroll.ordinal_dx *= -1;
271 copy.details.scroll.ordinal_dy *= -1;
272 }
273 break;
274 case kGestureTypeMouseWheel:
275 if (!(invert_scrolling_and_swiping_.val_ ||
276 invert_scrolling_only_.val_)) {
277 copy.details.wheel.dx *= -1;
278 copy.details.wheel.dy *= -1;
279 copy.details.wheel.tick_120ths_dx *= -1;
280 copy.details.wheel.tick_120ths_dy *= -1;
281 }
282 break;
283 case kGestureTypeFling:
284 copy.details.fling.vx *= screen_x_scale_;
285 copy.details.fling.vy *= screen_y_scale_;
286 copy.details.fling.ordinal_vx *= screen_x_scale_;
287 copy.details.fling.ordinal_vy *= screen_y_scale_;
288 if (!(invert_scrolling_and_swiping_.val_ ||
289 invert_scrolling_only_.val_)) {
290 copy.details.fling.vx *= -1;
291 copy.details.fling.vy *= -1;
292 copy.details.fling.ordinal_vx *= -1;
293 copy.details.fling.ordinal_vy *= -1;
294 }
295 break;
296 case kGestureTypeSwipe:
297 // Scale swipes, as we want them to follow the pointer speed.
298 copy.details.swipe.dx *= screen_x_scale_;
299 copy.details.swipe.dy *= screen_y_scale_;
300 copy.details.swipe.ordinal_dx *= screen_x_scale_;
301 copy.details.swipe.ordinal_dy *= screen_y_scale_;
302 if (!invert_scrolling_and_swiping_.val_) {
303 copy.details.swipe.dy *= -1;
304 copy.details.swipe.ordinal_dy *= -1;
305 }
306 break;
307 case kGestureTypeFourFingerSwipe:
308 // Scale swipes, as we want them to follow the pointer speed.
309 copy.details.four_finger_swipe.dx *= screen_x_scale_;
310 copy.details.four_finger_swipe.dy *= screen_y_scale_;
311 copy.details.four_finger_swipe.ordinal_dx *= screen_x_scale_;
312 copy.details.four_finger_swipe.ordinal_dy *= screen_y_scale_;
313 if (!invert_scrolling_and_swiping_.val_) {
314 copy.details.four_finger_swipe.dy *= -1;
315 copy.details.four_finger_swipe.ordinal_dy *= -1;
316 }
317 break;
318 default:
319 break;
320 }
321
322 LogGestureProduce(name, copy);
323 ProduceGesture(copy);
324 }
325
Initialize(const HardwareProperties * hwprops,Metrics * metrics,MetricsProperties * mprops,GestureConsumer * consumer)326 void ScalingFilterInterpreter::Initialize(const HardwareProperties* hwprops,
327 Metrics* metrics,
328 MetricsProperties* mprops,
329 GestureConsumer* consumer) {
330 // If the touchpad doesn't specify its resolution in one or both axes, use a
331 // reasonable default instead.
332 float res_x = hwprops->res_x != 0 ? hwprops->res_x : 32;
333 float res_y = hwprops->res_y != 0 ? hwprops->res_y : 32;
334
335 tp_x_scale_ = 1.0 / res_x;
336 tp_y_scale_ = 1.0 / res_y;
337 tp_x_translate_ = -1.0 * (hwprops->left * tp_x_scale_);
338 tp_y_translate_ = -1.0 * (hwprops->top * tp_y_scale_);
339
340 screen_x_scale_ = 133 / 25.4;
341 screen_y_scale_ = 133 / 25.4;
342
343 if (hwprops->orientation_maximum)
344 orientation_scale_ =
345 M_PI / (hwprops->orientation_maximum -
346 hwprops->orientation_minimum + 1);
347 else
348 orientation_scale_ = 0.0; // no orientation is provided
349
350 float friendly_orientation_minimum;
351 float friendly_orientation_maximum;
352 if (orientation_scale_) {
353 // Transform minimum and maximum values into radians.
354 friendly_orientation_minimum =
355 orientation_scale_ * hwprops->orientation_minimum;
356 friendly_orientation_maximum =
357 orientation_scale_ * hwprops->orientation_maximum;
358 } else {
359 friendly_orientation_minimum = 0.0;
360 friendly_orientation_maximum = 0.0;
361 }
362
363 // For haptic touchpads, the pressure field is actual force in grams, not an
364 // estimate of surface area.
365 if (hwprops->is_haptic_pad && use_touch_size_for_haptic_pad_.val_)
366 surface_area_from_pressure_.val_ = false;
367
368 // Make fake idealized hardware properties to report to next_.
369 friendly_props_ = *hwprops;
370 friendly_props_.left = 0.0;
371 friendly_props_.top = 0.0;
372 friendly_props_.right = (hwprops->right - hwprops->left) * tp_x_scale_;
373 friendly_props_.bottom = (hwprops->bottom - hwprops->top) * tp_y_scale_;
374 friendly_props_.res_x = 1.0; // X pixels/mm
375 friendly_props_.res_y = 1.0; // Y pixels/mm
376 friendly_props_.orientation_minimum = friendly_orientation_minimum;
377 friendly_props_.orientation_maximum = friendly_orientation_maximum;
378
379 // current metrics is no longer valid, pass metrics=nullptr
380 FilterInterpreter::Initialize(&friendly_props_, nullptr, mprops, consumer);
381 }
382
383 } // namespace gestures
384