xref: /aosp_15_r20/external/libchrome-gestures/src/multitouch_mouse_interpreter.cc (revision aed3e5085e770be5b69ce25295ecf6ddf906af95)
1 // Copyright 2013 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/multitouch_mouse_interpreter.h"
6 
7 #include <algorithm>
8 
9 #include "include/tracer.h"
10 #include "include/util.h"
11 
12 namespace gestures {
13 
PushGesture(const Gesture & result)14 void Origin::PushGesture(const Gesture& result) {
15   if (result.type == kGestureTypeButtonsChange) {
16     if (result.details.buttons.up & GESTURES_BUTTON_LEFT)
17       button_going_up_left_ = result.end_time;
18     if (result.details.buttons.up & GESTURES_BUTTON_MIDDLE)
19       button_going_up_middle_ = result.end_time;
20     if (result.details.buttons.up & GESTURES_BUTTON_RIGHT)
21       button_going_up_right_ = result.end_time;
22   }
23 }
24 
ButtonGoingUp(int button) const25 stime_t Origin::ButtonGoingUp(int button) const {
26   if (button == GESTURES_BUTTON_LEFT)
27     return button_going_up_left_;
28   if (button == GESTURES_BUTTON_MIDDLE)
29     return button_going_up_middle_;
30   if (button == GESTURES_BUTTON_RIGHT)
31     return button_going_up_right_;
32   return 0;
33 }
34 
MultitouchMouseInterpreter(PropRegistry * prop_reg,Tracer * tracer)35 MultitouchMouseInterpreter::MultitouchMouseInterpreter(
36     PropRegistry* prop_reg,
37     Tracer* tracer)
38     : MouseInterpreter(prop_reg, tracer),
39       state_buffer_(2),
40       scroll_buffer_(15),
41       prev_gesture_type_(kGestureTypeNull),
42       current_gesture_type_(kGestureTypeNull),
43       should_fling_(false),
44       scroll_manager_(prop_reg),
45       click_buffer_depth_(prop_reg, "Click Buffer Depth", 10),
46       click_max_distance_(prop_reg, "Click Max Distance", 1.0),
47       click_left_button_going_up_lead_time_(prop_reg,
48           "Click Left Button Going Up Lead Time", 0.01),
49       click_right_button_going_up_lead_time_(prop_reg,
50           "Click Right Button Going Up Lead Time", 0.1),
51       min_finger_move_distance_(prop_reg, "Minimum Mouse Finger Move Distance",
52                                 1.75),
53       moving_min_rel_amount_(prop_reg, "Moving Min Rel Magnitude", 0.1) {
54   InitName();
55   memset(&prev_state_, 0, sizeof(prev_state_));
56 }
57 
ProduceGesture(const Gesture & gesture)58 void MultitouchMouseInterpreter::ProduceGesture(const Gesture& gesture) {
59   origin_.PushGesture(gesture);
60   MouseInterpreter::ProduceGesture(gesture);
61 }
62 
SyncInterpretImpl(HardwareState & hwstate,stime_t * timeout)63 void MultitouchMouseInterpreter::SyncInterpretImpl(HardwareState& hwstate,
64                                                        stime_t* timeout) {
65   const char name[] = "MultitouchMouseInterpreter::SyncInterpretImpl";
66   LogHardwareStatePre(name, hwstate);
67 
68   if (!state_buffer_.Get(0).fingers) {
69     Err("Must call SetHardwareProperties() before interpreting anything.");
70     return;
71   }
72 
73   // Should we remove all fingers from our structures, or just removed ones?
74   if ((hwstate.rel_x * hwstate.rel_x + hwstate.rel_y * hwstate.rel_y) >
75       moving_min_rel_amount_.val_ * moving_min_rel_amount_.val_) {
76     start_position_.clear();
77     moving_.clear();
78     should_fling_ = false;
79   } else {
80     RemoveMissingIdsFromMap(&start_position_, hwstate);
81     RemoveMissingIdsFromSet(&moving_, hwstate);
82   }
83 
84   // Set start positions/moving
85   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
86     const FingerState& fs = hwstate.fingers[i];
87     if (MapContainsKey(start_position_, fs.tracking_id)) {
88       // Is moving?
89       if (!SetContainsValue(moving_, fs.tracking_id) &&  // not already moving &
90           start_position_[fs.tracking_id].Sub(Vector2(fs)).MagSq() >=  // moving
91           min_finger_move_distance_.val_ * min_finger_move_distance_.val_) {
92         moving_.insert(fs.tracking_id);
93       }
94       continue;
95     }
96     start_position_[fs.tracking_id] = Vector2(fs);
97   }
98 
99   // Mark all non-moving fingers as unable to cause scroll
100   for (size_t i = 0; i < hwstate.finger_cnt; i++) {
101     FingerState* fs = &hwstate.fingers[i];
102     if (!SetContainsValue(moving_, fs->tracking_id))
103       fs->flags |=
104           GESTURES_FINGER_WARP_X_NON_MOVE | GESTURES_FINGER_WARP_Y_NON_MOVE;
105   }
106 
107   // Record current HardwareState now.
108   state_buffer_.PushState(hwstate);
109 
110   // TODO(clchiou): Remove palm and thumb.
111   gs_fingers_.clear();
112   size_t num_fingers = std::min(kMaxGesturingFingers,
113                                 (size_t)state_buffer_.Get(0).finger_cnt);
114   const FingerState* fs = state_buffer_.Get(0).fingers;
115   for (size_t i = 0; i < num_fingers; i++)
116     gs_fingers_.insert(fs[i].tracking_id);
117 
118   InterpretScrollWheelEvent(hwstate, true);
119   InterpretScrollWheelEvent(hwstate, false);
120   InterpretMouseButtonEvent(prev_state_, state_buffer_.Get(0));
121   InterpretMouseMotionEvent(prev_state_, state_buffer_.Get(0));
122 
123   bool should_interpret_multitouch = true;
124 
125   // Some mice (Logitech) will interleave finger data and rel data, which can
126   // make finger tracking tricky. To avoid problems, if this current frame
127   // was rel data, and the previous finger data exactly matches this finger
128   // data, we remove the last hardware state from our buffer. This is okay
129   // because we already processed the rel data.
130   const HardwareState& prev_hs = state_buffer_.Get(1);
131   const HardwareState& cur_hs = state_buffer_.Get(0);
132   bool cur_has_rel = cur_hs.rel_x || cur_hs.rel_y ||
133       cur_hs.rel_wheel || cur_hs.rel_hwheel;
134   bool different_fingers = prev_hs.touch_cnt != cur_hs.touch_cnt ||
135       prev_hs.finger_cnt != cur_hs.finger_cnt;
136   if (!different_fingers && cur_has_rel) {
137     // Compare actual fingers themselves
138     for (size_t i = 0; i < cur_hs.finger_cnt; i++) {
139       if (!cur_hs.fingers[i].NonFlagsEquals(prev_hs.fingers[i])) {
140         different_fingers = true;
141         break;
142       }
143     }
144     if (!different_fingers) {
145       state_buffer_.PopState();
146       should_interpret_multitouch = false;
147     }
148   }
149 
150   if (should_interpret_multitouch)
151     InterpretMultitouchEvent();
152 
153   // We don't keep finger data here, this is just for standard mouse:
154   prev_state_ = hwstate;
155   prev_state_.fingers = nullptr;
156   prev_state_.finger_cnt = 0;
157 
158   prev_gs_fingers_ = gs_fingers_;
159   prev_gesture_type_ = current_gesture_type_;
160 
161   LogHardwareStatePost(name, hwstate);
162 }
163 
Initialize(const HardwareProperties * hw_props,Metrics * metrics,MetricsProperties * mprops,GestureConsumer * consumer)164 void MultitouchMouseInterpreter::Initialize(
165     const HardwareProperties* hw_props,
166     Metrics* metrics,
167     MetricsProperties* mprops,
168     GestureConsumer* consumer) {
169   Interpreter::Initialize(hw_props, metrics, mprops, consumer);
170   state_buffer_.Reset(hw_props->max_finger_cnt);
171 }
172 
InterpretMultitouchEvent()173 void MultitouchMouseInterpreter::InterpretMultitouchEvent() {
174   const char name[] = "MultitouchMouseInterpreter::InterpretMultitouchEvent";
175 
176   Gesture result;
177 
178   // If a gesturing finger just left, do fling/lift
179   if (should_fling_ && AnyGesturingFingerLeft(state_buffer_.Get(0),
180                                               prev_gs_fingers_)) {
181     current_gesture_type_ = kGestureTypeFling;
182     scroll_manager_.FillResultFling(state_buffer_, scroll_buffer_, &result);
183     if (result.type == kGestureTypeFling)
184       result.details.fling.vx = 0.0;
185     if (result.details.fling.vy == 0.0)
186       result.type = kGestureTypeNull;
187     should_fling_ = false;
188   } else if (gs_fingers_.size() > 0) {
189     // In general, finger movements are interpreted as scroll, but as
190     // clicks and scrolls on multi-touch mice are both single-finger
191     // gesture, we have to recognize and separate clicks from scrolls,
192     // when a user is actually clicking.
193     //
194     // This is how we do for now: We look for characteristic patterns of
195     // clicks, and if we find one, we hold off emitting scroll gesture for
196     // a few time frames to prevent premature scrolls.
197     //
198     // The patterns we look for:
199     // * Small finger movements when button is down
200     // * Finger movements after button goes up
201 
202     bool update_scroll_buffer =
203         scroll_manager_.FillResultScroll(state_buffer_,
204                                       prev_gs_fingers_,
205                                       gs_fingers_,
206                                       prev_gesture_type_,
207                                       prev_result_,
208                                       &result,
209                                       &scroll_buffer_);
210     current_gesture_type_ = result.type;
211     if (current_gesture_type_ == kGestureTypeScroll)
212       should_fling_ = true;
213 
214     bool hold_off_scroll = false;
215     const HardwareState& state = state_buffer_.Get(0);
216     // Check small finger movements when button is down
217     if (state.buttons_down) {
218       float dist_sq, dt;
219       scroll_buffer_.GetSpeedSq(click_buffer_depth_.val_, &dist_sq, &dt);
220       if (dist_sq < click_max_distance_.val_ * click_max_distance_.val_)
221         hold_off_scroll = true;
222     }
223     // Check button going up lead time
224     stime_t now = state.timestamp;
225     stime_t button_left_age =
226         now - origin_.ButtonGoingUp(GESTURES_BUTTON_LEFT);
227     stime_t button_right_age =
228         now - origin_.ButtonGoingUp(GESTURES_BUTTON_RIGHT);
229     hold_off_scroll = hold_off_scroll ||
230         (button_left_age < click_left_button_going_up_lead_time_.val_) ||
231         (button_right_age < click_right_button_going_up_lead_time_.val_);
232 
233     if (hold_off_scroll && result.type == kGestureTypeScroll) {
234       current_gesture_type_ = kGestureTypeNull;
235       result.type = kGestureTypeNull;
236     }
237     if (current_gesture_type_ == kGestureTypeScroll &&
238         !update_scroll_buffer) {
239       return;
240     }
241   }
242   scroll_manager_.UpdateScrollEventBuffer(current_gesture_type_,
243                                           &scroll_buffer_);
244   if (result.type != kGestureTypeNull) {
245     LogGestureProduce(name, result);
246     ProduceGesture(result);
247   }
248   prev_result_ = result;
249 }
250 
251 }  // namespace gestures
252