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 if (!state_buffer_.Get(0)->fingers) {
66 Err("Must call SetHardwareProperties() before interpreting anything.");
67 return;
68 }
69
70 // Should we remove all fingers from our structures, or just removed ones?
71 if ((hwstate->rel_x * hwstate->rel_x + hwstate->rel_y * hwstate->rel_y) >
72 moving_min_rel_amount_.val_ * moving_min_rel_amount_.val_) {
73 start_position_.clear();
74 moving_.clear();
75 should_fling_ = false;
76 } else {
77 RemoveMissingIdsFromMap(&start_position_, *hwstate);
78 RemoveMissingIdsFromSet(&moving_, *hwstate);
79 }
80
81 // Set start positions/moving
82 for (size_t i = 0; i < hwstate->finger_cnt; i++) {
83 const FingerState& fs = hwstate->fingers[i];
84 if (MapContainsKey(start_position_, fs.tracking_id)) {
85 // Is moving?
86 if (!SetContainsValue(moving_, fs.tracking_id) && // not already moving &
87 start_position_[fs.tracking_id].Sub(Vector2(fs)).MagSq() >= // moving
88 min_finger_move_distance_.val_ * min_finger_move_distance_.val_) {
89 moving_.insert(fs.tracking_id);
90 }
91 continue;
92 }
93 start_position_[fs.tracking_id] = Vector2(fs);
94 }
95
96 // Mark all non-moving fingers as unable to cause scroll
97 for (size_t i = 0; i < hwstate->finger_cnt; i++) {
98 FingerState* fs = &hwstate->fingers[i];
99 if (!SetContainsValue(moving_, fs->tracking_id))
100 fs->flags |=
101 GESTURES_FINGER_WARP_X_NON_MOVE | GESTURES_FINGER_WARP_Y_NON_MOVE;
102 }
103
104 // Record current HardwareState now.
105 state_buffer_.PushState(*hwstate);
106
107 // TODO(clchiou): Remove palm and thumb.
108 gs_fingers_.clear();
109 size_t num_fingers = std::min(kMaxGesturingFingers,
110 (size_t)state_buffer_.Get(0)->finger_cnt);
111 const FingerState* fs = state_buffer_.Get(0)->fingers;
112 for (size_t i = 0; i < num_fingers; i++)
113 gs_fingers_.insert(fs[i].tracking_id);
114
115 InterpretScrollWheelEvent(*hwstate, true);
116 InterpretScrollWheelEvent(*hwstate, false);
117 InterpretMouseButtonEvent(prev_state_, *state_buffer_.Get(0));
118 InterpretMouseMotionEvent(prev_state_, *state_buffer_.Get(0));
119
120 bool should_interpret_multitouch = true;
121
122 // Some mice (Logitech) will interleave finger data and rel data, which can
123 // make finger tracking tricky. To avoid problems, if this current frame
124 // was rel data, and the previous finger data exactly matches this finger
125 // data, we remove the last hardware state from our buffer. This is okay
126 // because we already processed the rel data.
127 if (state_buffer_.Get(0) && state_buffer_.Get(1)) {
128 HardwareState* prev_hs = state_buffer_.Get(1);
129 HardwareState* cur_hs = state_buffer_.Get(0);
130 bool cur_has_rel = cur_hs->rel_x || cur_hs->rel_y ||
131 cur_hs->rel_wheel || cur_hs->rel_hwheel;
132 bool different_fingers = prev_hs->touch_cnt != cur_hs->touch_cnt ||
133 prev_hs->finger_cnt != cur_hs->finger_cnt;
134 if (!different_fingers && cur_has_rel) {
135 // Compare actual fingers themselves
136 for (size_t i = 0; i < cur_hs->finger_cnt; i++) {
137 if (!cur_hs->fingers[i].NonFlagsEquals(prev_hs->fingers[i])) {
138 different_fingers = true;
139 break;
140 }
141 }
142 if (!different_fingers) {
143 state_buffer_.PopState();
144 should_interpret_multitouch = false;
145 }
146 }
147 }
148
149 if (should_interpret_multitouch)
150 InterpretMultitouchEvent();
151
152 // We don't keep finger data here, this is just for standard mouse:
153 prev_state_ = *hwstate;
154 prev_state_.fingers = NULL;
155 prev_state_.finger_cnt = 0;
156
157 prev_gs_fingers_ = gs_fingers_;
158 prev_gesture_type_ = current_gesture_type_;
159 }
160
Initialize(const HardwareProperties * hw_props,Metrics * metrics,MetricsProperties * mprops,GestureConsumer * consumer)161 void MultitouchMouseInterpreter::Initialize(
162 const HardwareProperties* hw_props,
163 Metrics* metrics,
164 MetricsProperties* mprops,
165 GestureConsumer* consumer) {
166 Interpreter::Initialize(hw_props, metrics, mprops, consumer);
167 state_buffer_.Reset(hw_props->max_finger_cnt);
168 }
169
InterpretMultitouchEvent()170 void MultitouchMouseInterpreter::InterpretMultitouchEvent() {
171 Gesture result;
172
173 // If a gesturing finger just left, do fling/lift
174 if (should_fling_ && AnyGesturingFingerLeft(*state_buffer_.Get(0),
175 prev_gs_fingers_)) {
176 current_gesture_type_ = kGestureTypeFling;
177 scroll_manager_.FillResultFling(state_buffer_, scroll_buffer_, &result);
178 if (result.type == kGestureTypeFling)
179 result.details.fling.vx = 0.0;
180 if (result.details.fling.vy == 0.0)
181 result.type = kGestureTypeNull;
182 should_fling_ = false;
183 } else if (gs_fingers_.size() > 0) {
184 // In general, finger movements are interpreted as scroll, but as
185 // clicks and scrolls on multi-touch mice are both single-finger
186 // gesture, we have to recognize and separate clicks from scrolls,
187 // when a user is actually clicking.
188 //
189 // This is how we do for now: We look for characteristic patterns of
190 // clicks, and if we find one, we hold off emitting scroll gesture for
191 // a few time frames to prevent premature scrolls.
192 //
193 // The patterns we look for:
194 // * Small finger movements when button is down
195 // * Finger movements after button goes up
196
197 bool update_scroll_buffer =
198 scroll_manager_.FillResultScroll(state_buffer_,
199 prev_gs_fingers_,
200 gs_fingers_,
201 prev_gesture_type_,
202 prev_result_,
203 &result,
204 &scroll_buffer_);
205 current_gesture_type_ = result.type;
206 if (current_gesture_type_ == kGestureTypeScroll)
207 should_fling_ = true;
208
209 bool hold_off_scroll = false;
210 const HardwareState& state = *state_buffer_.Get(0);
211 // Check small finger movements when button is down
212 if (state.buttons_down) {
213 float dist_sq, dt;
214 scroll_buffer_.GetSpeedSq(click_buffer_depth_.val_, &dist_sq, &dt);
215 if (dist_sq < click_max_distance_.val_ * click_max_distance_.val_)
216 hold_off_scroll = true;
217 }
218 // Check button going up lead time
219 stime_t now = state.timestamp;
220 stime_t button_left_age =
221 now - origin_.ButtonGoingUp(GESTURES_BUTTON_LEFT);
222 stime_t button_right_age =
223 now - origin_.ButtonGoingUp(GESTURES_BUTTON_RIGHT);
224 hold_off_scroll = hold_off_scroll ||
225 (button_left_age < click_left_button_going_up_lead_time_.val_) ||
226 (button_right_age < click_right_button_going_up_lead_time_.val_);
227
228 if (hold_off_scroll && result.type == kGestureTypeScroll) {
229 current_gesture_type_ = kGestureTypeNull;
230 result.type = kGestureTypeNull;
231 }
232 if (current_gesture_type_ == kGestureTypeScroll &&
233 !update_scroll_buffer) {
234 return;
235 }
236 }
237 scroll_manager_.UpdateScrollEventBuffer(current_gesture_type_,
238 &scroll_buffer_);
239 if (result.type != kGestureTypeNull)
240 ProduceGesture(result);
241 prev_result_ = result;
242 }
243
244 } // namespace gestures
245