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