xref: /aosp_15_r20/external/libchrome-gestures/src/scaling_filter_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
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