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