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