~chasedouglas/grail/work-in-progress

« back to all changes in this revision

Viewing changes to src/v3/atomic-recognizer.cpp

  • Committer: Daniel d'Andrada
  • Date: 2012-03-14 19:15:09 UTC
  • mfrom: (196.1.3 lp949916_v3)
  • Revision ID: daniel.dandrada@canonical.com-20120314191509-tcaxcjy9nxshghm5
Merge "Atomic rules: Don't send slices from premature gestures."

+ refactoring of v3/Recognizer
+ regression test

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*****************************************************************************
 
2
 *
 
3
 * grail - Gesture Recognition And Instantiation Library
 
4
 *
 
5
 * Copyright (C) 2012 Canonical Ltd.
 
6
 *
 
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.
 
11
 *
 
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.
 
16
 *
 
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/>.
 
19
 *
 
20
 ****************************************************************************/
 
21
 
 
22
#include "v3/atomic-recognizer.h"
 
23
 
 
24
#include <algorithm>
 
25
#include <cassert>
 
26
#include <cstdint>
 
27
#include <cstdio>
 
28
#include <limits>
 
29
 
 
30
#include <utouch/frame.h>
 
31
#include <utouch/frame_x11.h>
 
32
 
 
33
#include "v3/handle.h"
 
34
#include "v3/gesture.h"
 
35
#include "v3/log.h"
 
36
 
 
37
namespace {
 
38
const uint64_t MAX_TOUCHES_FOR_GESTURES = 5;
 
39
} // namespace
 
40
 
 
41
namespace utouch {
 
42
namespace grail {
 
43
 
 
44
/**
 
45
 * @internal
 
46
 * Create a new atomic recognizer for a given device and window
 
47
 */
 
48
AtomicRecognizer::AtomicRecognizer(UGHandle* handle, const UFDevice device, UFWindowId window)
 
49
    : Recognizer(handle, device, window) {
 
50
}
 
51
 
 
52
/**
 
53
 * @internal
 
54
 * Process a uTouch-Frame event
 
55
 */
 
56
void AtomicRecognizer::ProcessFrameEvent(const UFEvent event) {
 
57
  LOG(Dbg) << "new event " << event << " with time "
 
58
           << frame_event_get_time(event) << "\n";
 
59
 
 
60
  uint64_t event_time = frame_event_get_time(event);
 
61
 
 
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();
 
68
  }
 
69
  ProcessEvent(event);
 
70
  FindGestureToAccept(event_time);
 
71
}
 
72
 
 
73
/**
 
74
 * @internal
 
75
 * Perform tasks necessary for when new touches occur and there is an existing
 
76
 * accepted gesture
 
77
 *
 
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.
 
81
 */
 
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";
 
88
 
 
89
    for (UFTouchId touch_id : new_touches_) {
 
90
      if (frame_x11_accept_touch(device_, window_id_, touch_id) !=
 
91
          UFStatusSuccess)
 
92
        LOG(Err) << "touch " << touch_id << " failed to be accepted\n";
 
93
 
 
94
      LOG(Dbg) << "touch " << touch_id
 
95
        << " has been accepted because it has been added to an atomic gesture\n";
 
96
 
 
97
      INSERT_TOUCH(touch_id, accepted_touches_);
 
98
      ERASE_TOUCH(touch_id, unaccepted_touches_);
 
99
      ERASE_TOUCH(touch_id, free_touches_);
 
100
    }
 
101
    CLEAR_TOUCHES(new_touches_);
 
102
  } else {
 
103
    for (UFTouchId touch : gesture->touches())
 
104
      INSERT_TOUCH(touch, free_touches_);
 
105
    gesture->End();
 
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);
 
110
  }
 
111
}
 
112
 
 
113
/**
 
114
 * @internal
 
115
 * Perform tasks necessary for when new touches occur and there is an existing
 
116
 * unaccepted gesture
 
117
 *
 
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.
 
121
 */
 
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";
 
130
    }
 
131
  } else {
 
132
    for (UFTouchId touch_id : gesture->touches())
 
133
      INSERT_TOUCH(touch_id, free_touches_);
 
134
    gesture->Cancel();
 
135
    LOG(Dbg) << "canceled inactive atomic gesture " << gesture->id()
 
136
             << " because a new touch began and the max touches has been "
 
137
             << "reached\n";
 
138
    unaccepted_gestures_.erase(gesture);
 
139
  }
 
140
}
 
141
 
 
142
/**
 
143
 * @internal
 
144
 * Register all new touches present in the given uTouch-Frame event.
 
145
 */
 
146
void AtomicRecognizer::CollectNewTouches(const UFEvent event) {
 
147
  /* Check if any subscriptions are active before doing any processing */
 
148
  if (num_subscriptions_ == 0)
 
149
    return;
 
150
 
 
151
  UFFrame frame;
 
152
  UFStatus status = frame_event_get_property(event, UFEventPropertyFrame,
 
153
                                             &frame);
 
154
  if (status != UFStatusSuccess) {
 
155
    LOG(Warn) << "failed to get frame from event\n";
 
156
    return;
 
157
  }
 
158
 
 
159
  unsigned int num_touches = frame_frame_get_num_touches(frame);
 
160
  uint64_t touch_start_time;
 
161
  UFTouchId touch_id;
 
162
  for (unsigned int i = 0; i < num_touches; ++i) {
 
163
    UFTouch touch;
 
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";
 
167
      continue;
 
168
    }
 
169
 
 
170
    touch_id = frame_touch_get_id(touch);
 
171
 
 
172
    switch (frame_touch_get_state(touch)) {
 
173
      case UFTouchStateBegin:
 
174
        touch_start_time = frame_touch_get_start_time(touch);
 
175
 
 
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";
 
180
 
 
181
        INSERT_TOUCH(touch_id, new_touches_);
 
182
        break;
 
183
 
 
184
      case UFTouchStateEnd:
 
185
        ERASE_TOUCH(touch_id, new_touches_);
 
186
        break;
 
187
 
 
188
      default:
 
189
        break;
 
190
    }
 
191
  }
 
192
}
 
193
 
 
194
/**
 
195
 * @internal
 
196
 * Consume the new touches.
 
197
 * Check if any new atomic gestures should begin because of the new touches
 
198
 * that came.
 
199
 */
 
200
void AtomicRecognizer::MatchSubscriptionsForNewTouches() {
 
201
  /* Check if any subscriptions are active before doing any processing */
 
202
  if (num_subscriptions_ == 0)
 
203
    return;
 
204
 
 
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_);
 
209
  }
 
210
 
 
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);
 
216
  }
 
217
 
 
218
  if (new_touches_.size() == 0) {
 
219
    // they've all been consumed by the accepted gesture.
 
220
    return;
 
221
  }
 
222
 
 
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();
 
227
      ) {
 
228
    const SharedGesture& gesture = *it++;
 
229
    HandleNewTouchesForUnacceptedGesture(gesture);
 
230
  }
 
231
 
 
232
  MatchGestures();
 
233
  CLEAR_TOUCHES(new_touches_);
 
234
}
 
235
 
 
236
void AtomicRecognizer::MatchGestures() {
 
237
  if (free_touches_.size() == 0
 
238
      || free_touches_.size() > MAX_TOUCHES_FOR_GESTURES)
 
239
    return;
 
240
 
 
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())
 
245
      continue;
 
246
 
 
247
    if (start_times_[touch_id] < min_start_time)
 
248
      min_start_time = start_times_[touch_id];
 
249
 
 
250
    if (start_times_[touch_id] > max_start_time)
 
251
      max_start_time = start_times_[touch_id];
 
252
  }
 
253
 
 
254
  /* All touches in a gesture must begin within a composition timeframe */
 
255
  if ((max_start_time - min_start_time) >= kCompositionTime)
 
256
    return;
 
257
 
 
258
  for (UGSubscription* subscription : subscriptions_[free_touches_.size()-1]) {
 
259
    Gesture* gesture = new Gesture(this, subscription, free_touches_,
 
260
        max_start_time);
 
261
 
 
262
    /* hold slice events until we accept the gesture */
 
263
    gesture->SetKeepSlices(true);
 
264
 
 
265
    unaccepted_gestures_.insert(SharedGesture(gesture));
 
266
 
 
267
    LOG(Dbg) << "New tentative gesture " << gesture->id()
 
268
      << " matched subscription " << subscription << " with mask "
 
269
      << subscription->mask() << " for touches " << free_touches_.ToString()
 
270
      << std::endl;
 
271
  }
 
272
}
 
273
 
 
274
void AtomicRecognizer::FindGestureToAccept(uint64_t event_time)
 
275
{
 
276
  uint64_t delta_time;
 
277
  for (auto it = unaccepted_gestures_.begin();
 
278
       it != unaccepted_gestures_.end();
 
279
       ) {
 
280
    const SharedGesture& gesture = *it++;
 
281
 
 
282
    delta_time = event_time - gesture->start_time();
 
283
 
 
284
      /* Atomic gestures must be accepted if they meet the subscription
 
285
         criteria.
 
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
 
294
         gestures.
 
295
       */
 
296
    if (gesture->IsActive() && delta_time > 0
 
297
        && delta_time >= kCompositionTime) {
 
298
 
 
299
      gesture->SetKeepSlices(false);
 
300
 
 
301
      LOG(Dbg) << "accepting active atomic gesture " << gesture->id() << "\n";
 
302
      AcceptGesture(gesture->id());
 
303
    }
 
304
  }
 
305
}
 
306
 
 
307
} // namespace grail
 
308
} // namespace utouch