1
/*****************************************************************************
3
* grail - Gesture Recognition And Instantiation Library
5
* Copyright (C) 2012 Canonical Ltd.
7
* This program is free software: you can redistribute it and/or modify it
8
* under the terms of the GNU General Public License as published by the
9
* Free Software Foundation, either version 3 of the License, or (at your
10
* option) any later version.
12
* This program is distributed in the hope that it will be useful, but
13
* WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* General Public License for more details.
17
* You should have received a copy of the GNU General Public License along
18
* with this program. If not, see <http://www.gnu.org/licenses/>.
20
****************************************************************************/
22
#include "v3/atomic-recognizer.h"
30
#include <utouch/frame.h>
31
#include <utouch/frame_x11.h>
33
#include "v3/handle.h"
34
#include "v3/gesture.h"
38
const uint64_t MAX_TOUCHES_FOR_GESTURES = 5;
46
* Create a new atomic recognizer for a given device and window
48
AtomicRecognizer::AtomicRecognizer(UGHandle* handle, const UFDevice device, UFWindowId window)
49
: Recognizer(handle, device, window) {
54
* Process a uTouch-Frame event
56
void AtomicRecognizer::ProcessFrameEvent(const UFEvent event) {
57
LOG(Dbg) << "new event " << event << " with time "
58
<< frame_event_get_time(event) << "\n";
60
uint64_t event_time = frame_event_get_time(event);
62
UpdateTime(event_time);
63
CollectNewTouches(event);
64
if (new_touches_.size() > 0) {
65
/* process all new touches at once to avoid the premature initiation of
66
gestures for less touches than what the event brings */
67
MatchSubscriptionsForNewTouches();
70
FindGestureToAccept(event_time);
75
* Perform tasks necessary for when new touches occur and there is an existing
78
* If a gesture may add touches without crossing the maximum for the
79
* subscription, add the touches to the gesture and accept them. Otherwise, end
80
* the gesture and add the current gesture touches to the free touches list.
82
void AtomicRecognizer::HandleNewTouchesForAcceptedGesture(const SharedGesture& gesture) {
83
UGSubscription* subscription = gesture->subscription();
84
if (gesture->touches().size() + new_touches_.size() <= subscription->touches_max()) {
85
gesture->AddTouches(new_touches_);
86
LOG(Dbg) << "new_touches_ have been added to atomic gesture "
87
<< gesture->id() << "\n";
89
for (UFTouchId touch_id : new_touches_) {
90
if (frame_x11_accept_touch(device_, window_id_, touch_id) !=
92
LOG(Err) << "touch " << touch_id << " failed to be accepted\n";
94
LOG(Dbg) << "touch " << touch_id
95
<< " has been accepted because it has been added to an atomic gesture\n";
97
INSERT_TOUCH(touch_id, accepted_touches_);
98
ERASE_TOUCH(touch_id, unaccepted_touches_);
99
ERASE_TOUCH(touch_id, free_touches_);
101
CLEAR_TOUCHES(new_touches_);
103
for (UFTouchId touch : gesture->touches())
104
INSERT_TOUCH(touch, free_touches_);
106
LOG(Dbg) << "ended active atomic gesture " << gesture->id()
107
<< " because " << new_touches_.size() << " new touch(es) began and the "
108
"max touches has been reached\n";
109
accepted_gestures_.erase(gesture);
115
* Perform tasks necessary for when new touches occur and there is an existing
118
* If a gesture may receive the new touches without crossing the maximum for
119
* the subscription, add the touches to the gesture. Otherwise, cancel the
120
* gesture and add the current gesture touches to the free touches list.
122
void AtomicRecognizer::HandleNewTouchesForUnacceptedGesture(
123
const SharedGesture& gesture) {
124
UGSubscription* subscription = gesture->subscription();
125
if (gesture->touches().size() + new_touches_.size() <= subscription->touches_max()) {
126
for (UFTouchId touch_id : new_touches_) {
127
gesture->AddTouch(touch_id);
128
LOG(Dbg) << "touch " << touch_id << " has been added to atomic gesture "
129
<< gesture->id() << "\n";
132
for (UFTouchId touch_id : gesture->touches())
133
INSERT_TOUCH(touch_id, free_touches_);
135
LOG(Dbg) << "canceled inactive atomic gesture " << gesture->id()
136
<< " because a new touch began and the max touches has been "
138
unaccepted_gestures_.erase(gesture);
144
* Register all new touches present in the given uTouch-Frame event.
146
void AtomicRecognizer::CollectNewTouches(const UFEvent event) {
147
/* Check if any subscriptions are active before doing any processing */
148
if (num_subscriptions_ == 0)
152
UFStatus status = frame_event_get_property(event, UFEventPropertyFrame,
154
if (status != UFStatusSuccess) {
155
LOG(Warn) << "failed to get frame from event\n";
159
unsigned int num_touches = frame_frame_get_num_touches(frame);
160
uint64_t touch_start_time;
162
for (unsigned int i = 0; i < num_touches; ++i) {
164
status = frame_frame_get_touch_by_index(frame, i, &touch);
165
if (status != UFStatusSuccess) {
166
LOG(Warn) << "failed to get touch from frame\n";
170
touch_id = frame_touch_get_id(touch);
172
switch (frame_touch_get_state(touch)) {
173
case UFTouchStateBegin:
174
touch_start_time = frame_touch_get_start_time(touch);
176
/* Note touch start time and add to initial touch lists */
177
start_times_[touch_id] = touch_start_time;
178
LOG(Dbg) << "touch " << touch_id << " began with start time "
179
<< start_times_[touch_id] << "\n";
181
INSERT_TOUCH(touch_id, new_touches_);
184
case UFTouchStateEnd:
185
ERASE_TOUCH(touch_id, new_touches_);
196
* Consume the new touches.
197
* Check if any new atomic gestures should begin because of the new touches
200
void AtomicRecognizer::MatchSubscriptionsForNewTouches() {
201
/* Check if any subscriptions are active before doing any processing */
202
if (num_subscriptions_ == 0)
205
// The new touches can now be used
206
for (UFTouchId touch_id : new_touches_) {
207
INSERT_TOUCH(touch_id, unaccepted_touches_);
208
INSERT_TOUCH(touch_id, free_touches_);
211
// Under atomic gestures rules there can be only one accepted gesture
212
assert(accepted_gestures_.size() <= 1);
213
if (accepted_gestures_.size() != 0) {
214
const SharedGesture& gesture = *accepted_gestures_.begin();
215
HandleNewTouchesForAcceptedGesture(gesture);
218
if (new_touches_.size() == 0) {
219
// they've all been consumed by the accepted gesture.
223
/* HandleNewTouchForUnacceptedGesture may erase the gesture from
224
* accepted_gestures_, so we can't use range-based for loops */
225
for (auto it = unaccepted_gestures_.begin();
226
it != unaccepted_gestures_.end();
228
const SharedGesture& gesture = *it++;
229
HandleNewTouchesForUnacceptedGesture(gesture);
233
CLEAR_TOUCHES(new_touches_);
236
void AtomicRecognizer::MatchGestures() {
237
if (free_touches_.size() == 0
238
|| free_touches_.size() > MAX_TOUCHES_FOR_GESTURES)
241
uint64_t min_start_time = std::numeric_limits<uint64_t>::max();
242
uint64_t max_start_time = 0;
243
for (UFTouchId touch_id : free_touches_) {
244
if (unaccepted_touches_.find(touch_id) == unaccepted_touches_.end())
247
if (start_times_[touch_id] < min_start_time)
248
min_start_time = start_times_[touch_id];
250
if (start_times_[touch_id] > max_start_time)
251
max_start_time = start_times_[touch_id];
254
/* All touches in a gesture must begin within a composition timeframe */
255
if ((max_start_time - min_start_time) >= kCompositionTime)
258
for (UGSubscription* subscription : subscriptions_[free_touches_.size()-1]) {
259
Gesture* gesture = new Gesture(this, subscription, free_touches_,
262
/* hold slice events until we accept the gesture */
263
gesture->SetKeepSlices(true);
265
unaccepted_gestures_.insert(SharedGesture(gesture));
267
LOG(Dbg) << "New tentative gesture " << gesture->id()
268
<< " matched subscription " << subscription << " with mask "
269
<< subscription->mask() << " for touches " << free_touches_.ToString()
274
void AtomicRecognizer::FindGestureToAccept(uint64_t event_time)
277
for (auto it = unaccepted_gestures_.begin();
278
it != unaccepted_gestures_.end();
280
const SharedGesture& gesture = *it++;
282
delta_time = event_time - gesture->start_time();
284
/* Atomic gestures must be accepted if they meet the subscription
286
Wait a bit until accepting them to avoid premature gestures that
287
will immediately get cancelled due to the apparition of a new touch
288
point on the following events.
289
e.g. like when a user puts four fingers on a touch screen but
290
the corresponding touch points come in separate events (because fingers
291
don't land precisely in sync and/or UTouch frame doesn't process their
292
arrivals in the very same event). We should generate only the "final"
293
4-touches' gesture and not the intermediates 2-touches and 3-touches
296
if (gesture->IsActive() && delta_time > 0
297
&& delta_time >= kCompositionTime) {
299
gesture->SetKeepSlices(false);
301
LOG(Dbg) << "accepting active atomic gesture " << gesture->id() << "\n";
302
AcceptGesture(gesture->id());
308
} // namespace utouch