89991c2a63b8583baea5cc7bfba95099837b7b08
[WebKit-https.git] / Tools / WebKitTestRunner / ios / HIDEventGenerator.mm
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "HIDEventGenerator.h"
28
29 #import "GeneratedTouchesDebugWindow.h"
30 #import "UIKitSPI.h"
31 #import <mach/mach_time.h>
32 #import <pal/spi/cocoa/IOKitSPI.h>
33 #import <wtf/Assertions.h>
34 #import <wtf/BlockPtr.h>
35 #import <wtf/Optional.h>
36 #import <wtf/SoftLinking.h>
37
38 SOFT_LINK_PRIVATE_FRAMEWORK(BackBoardServices)
39 SOFT_LINK(BackBoardServices, BKSHIDEventSetDigitizerInfo, void, (IOHIDEventRef digitizerEvent, uint32_t contextID, uint8_t systemGestureisPossible, uint8_t isSystemGestureStateChangeEvent, CFStringRef displayUUID, CFTimeInterval initialTouchTimestamp, float maxForce), (digitizerEvent, contextID, systemGestureisPossible, isSystemGestureStateChangeEvent, displayUUID, initialTouchTimestamp, maxForce));
40
41 NSString* const TopLevelEventInfoKey = @"events";
42 NSString* const HIDEventInputType = @"inputType";
43 NSString* const HIDEventTimeOffsetKey = @"timeOffset";
44 NSString* const HIDEventTouchesKey = @"touches";
45 NSString* const HIDEventPhaseKey = @"phase";
46 NSString* const HIDEventInterpolateKey = @"interpolate";
47 NSString* const HIDEventTimestepKey = @"timestep";
48 NSString* const HIDEventCoordinateSpaceKey = @"coordinateSpace";
49 NSString* const HIDEventStartEventKey = @"startEvent";
50 NSString* const HIDEventEndEventKey = @"endEvent";
51 NSString* const HIDEventTouchIDKey = @"id";
52 NSString* const HIDEventPressureKey = @"pressure";
53 NSString* const HIDEventXKey = @"x";
54 NSString* const HIDEventYKey = @"y";
55 NSString* const HIDEventTwistKey = @"twist";
56 NSString* const HIDEventMajorRadiusKey = @"majorRadius";
57 NSString* const HIDEventMinorRadiusKey = @"minorRadius";
58
59 NSString* const HIDEventInputTypeHand = @"hand";
60 NSString* const HIDEventInputTypeFinger = @"finger";
61 NSString* const HIDEventInputTypeStylus = @"stylus";
62
63 NSString* const HIDEventCoordinateSpaceTypeGlobal = @"global";
64 NSString* const HIDEventCoordinateSpaceTypeContent = @"content";
65
66 NSString* const HIDEventInterpolationTypeLinear = @"linear";
67 NSString* const HIDEventInterpolationTypeSimpleCurve = @"simpleCurve";
68
69 NSString* const HIDEventPhaseBegan = @"began";
70 NSString* const HIDEventPhaseStationary = @"stationary";
71 NSString* const HIDEventPhaseMoved = @"moved";
72 NSString* const HIDEventPhaseEnded = @"ended";
73 NSString* const HIDEventPhaseCanceled = @"canceled";
74
75 static const NSTimeInterval fingerLiftDelay = 0.05;
76 static const NSTimeInterval multiTapInterval = 0.15;
77 static const NSTimeInterval fingerMoveInterval = 0.016;
78 static const NSTimeInterval longPressHoldDelay = 2.0;
79 static const IOHIDFloat defaultMajorRadius = 5;
80 static const IOHIDFloat defaultPathPressure = 0;
81 static const long nanosecondsPerSecond = 1e9;
82
83 NSUInteger const HIDMaxTouchCount = 5;
84
85
86
87 static int fingerIdentifiers[HIDMaxTouchCount] = { 2, 3, 4, 5, 1 };
88
89 typedef enum {
90     InterpolationTypeLinear,
91     InterpolationTypeSimpleCurve,
92 } InterpolationType;
93
94 typedef enum {
95     HandEventNull,
96     HandEventTouched,
97     HandEventMoved,
98     HandEventChordChanged,
99     HandEventLifted,
100     HandEventCanceled,
101     StylusEventTouched,
102     StylusEventMoved,
103     StylusEventLifted,
104 } HandEventType;
105
106 typedef struct {
107     int identifier;
108     CGPoint point;
109     IOHIDFloat pathMajorRadius;
110     IOHIDFloat pathPressure;
111     UInt8 pathProximity;
112     BOOL isStylus;
113     IOHIDFloat azimuthAngle;
114     IOHIDFloat altitudeAngle;
115 } SyntheticEventDigitizerInfo;
116
117 static CFTimeInterval secondsSinceAbsoluteTime(CFAbsoluteTime startTime)
118 {
119     return (CFAbsoluteTimeGetCurrent() - startTime);
120 }
121
122 static double linearInterpolation(double a, double b, double t)
123 {
124     return (a + (b - a) * t );
125 }
126
127 static double simpleCurveInterpolation(double a, double b, double t)
128 {
129     return (a + (b - a) * sin(sin(t * M_PI / 2) * t * M_PI / 2));
130 }
131
132
133 static CGPoint calculateNextCurveLocation(CGPoint a, CGPoint b, CFTimeInterval t)
134 {
135     return CGPointMake(simpleCurveInterpolation(a.x, b.x, t), simpleCurveInterpolation(a.y, b.y, t));
136 }
137
138 typedef double(*pressureInterpolationFunction)(double, double, CFTimeInterval);
139 static pressureInterpolationFunction interpolations[] = {
140     linearInterpolation,
141     simpleCurveInterpolation,
142 };
143
144 static void delayBetweenMove(int eventIndex, double elapsed)
145 {
146     // Delay next event until expected elapsed time.
147     double delay = (eventIndex * fingerMoveInterval) - elapsed;
148     if (delay > 0) {
149         struct timespec moveDelay = { 0, static_cast<long>(delay * nanosecondsPerSecond) };
150         nanosleep(&moveDelay, NULL);
151     }   
152 }
153
154 @interface HIDEventGenerator ()
155 @property (nonatomic, strong) NSMutableDictionary *eventCallbacks;
156 @property (nonatomic, strong) NSArray<UIView *> *debugTouchViews;
157 @end
158
159 @implementation HIDEventGenerator {
160     IOHIDEventSystemClientRef _ioSystemClient;
161     SyntheticEventDigitizerInfo _activePoints[HIDMaxTouchCount];
162     NSUInteger _activePointCount;
163 }
164
165 + (HIDEventGenerator *)sharedHIDEventGenerator
166 {
167     static HIDEventGenerator *eventGenerator = nil;
168     if (!eventGenerator)
169         eventGenerator = [[HIDEventGenerator alloc] init];
170
171     return eventGenerator;
172 }
173
174 + (CFIndex)nextEventCallbackID
175 {
176     static CFIndex callbackID = 0;
177     return ++callbackID;
178 }
179
180 - (instancetype)init
181 {
182     self = [super init];
183     if (!self)
184         return nil;
185
186     for (NSUInteger i = 0; i < HIDMaxTouchCount; ++i)
187         _activePoints[i].identifier = fingerIdentifiers[i];
188
189     _eventCallbacks = [[NSMutableDictionary alloc] init];
190
191     return self;
192 }
193
194 - (void)dealloc
195 {
196     [_eventCallbacks release];
197     [_debugTouchViews release];
198     [super dealloc];
199 }
200
201 - (void)_sendIOHIDKeyboardEvent:(uint64_t)timestamp usage:(uint32_t)usage isKeyDown:(bool)isKeyDown
202 {
203     RetainPtr<IOHIDEventRef> eventRef = adoptCF(IOHIDEventCreateKeyboardEvent(kCFAllocatorDefault,
204         timestamp,
205         kHIDPage_KeyboardOrKeypad,
206         usage,
207         isKeyDown,
208         kIOHIDEventOptionNone));
209     [self _sendHIDEvent:eventRef.get()];
210 }
211
212 static IOHIDDigitizerTransducerType transducerTypeFromString(NSString * transducerTypeString)
213 {
214     if ([transducerTypeString isEqualToString:HIDEventInputTypeHand])
215         return kIOHIDDigitizerTransducerTypeHand;
216
217     if ([transducerTypeString isEqualToString:HIDEventInputTypeFinger])
218         return kIOHIDDigitizerTransducerTypeFinger;
219
220     if ([transducerTypeString isEqualToString:HIDEventInputTypeStylus])
221         return kIOHIDDigitizerTransducerTypeStylus;
222     
223     ASSERT_NOT_REACHED();
224     return 0;
225 }
226
227 static UITouchPhase phaseFromString(NSString *string)
228 {
229     if ([string isEqualToString:HIDEventPhaseBegan])
230         return UITouchPhaseBegan;
231     
232     if ([string isEqualToString:HIDEventPhaseStationary])
233         return UITouchPhaseStationary;
234
235     if ([string isEqualToString:HIDEventPhaseMoved])
236         return UITouchPhaseMoved;
237
238     if ([string isEqualToString:HIDEventPhaseEnded])
239         return UITouchPhaseEnded;
240
241     if ([string isEqualToString:HIDEventPhaseCanceled])
242         return UITouchPhaseCancelled;
243
244     return UITouchPhaseStationary;
245 }
246
247 static InterpolationType interpolationFromString(NSString *string)
248 {
249     if ([string isEqualToString:HIDEventInterpolationTypeLinear])
250         return InterpolationTypeLinear;
251     
252     if ([string isEqualToString:HIDEventInterpolationTypeSimpleCurve])
253         return InterpolationTypeSimpleCurve;
254     
255     return InterpolationTypeLinear;
256 }
257
258 - (IOHIDDigitizerEventMask)eventMaskFromEventInfo:(NSDictionary *)info
259 {
260     IOHIDDigitizerEventMask eventMask = 0;
261     NSArray *childEvents = info[HIDEventTouchesKey];
262     for (NSDictionary *touchInfo in childEvents) {
263         UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
264         // If there are any new or ended events, mask includes touch.
265         if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled)
266             eventMask |= kIOHIDDigitizerEventTouch;
267         // If there are any pressure readings, set mask must include attribute
268         if ([touchInfo[HIDEventPressureKey] floatValue])
269             eventMask |= kIOHIDDigitizerEventAttribute;
270     }
271     
272     return eventMask;
273 }
274
275 // Returns 1 for all events where the fingers are on the glass (everything but ended and canceled).
276 - (CFIndex)touchFromEventInfo:(NSDictionary *)info
277 {
278     NSArray *childEvents = info[HIDEventTouchesKey];
279     for (NSDictionary *touchInfo in childEvents) {
280         UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
281         if (phase == UITouchPhaseBegan || phase == UITouchPhaseMoved || phase == UITouchPhaseStationary)
282             return 1;
283     }
284     
285     return 0;
286 }
287
288 // FIXME: callers of _createIOHIDEventType could switch to this.
289 - (IOHIDEventRef)_createIOHIDEventWithInfo:(NSDictionary *)info
290 {
291     uint64_t machTime = mach_absolute_time();
292
293     IOHIDDigitizerEventMask eventMask = [self eventMaskFromEventInfo:info];
294
295     CFIndex range = 0;
296     // touch is 1 if a finger is down.
297     CFIndex touch = [self touchFromEventInfo:info];
298
299     IOHIDEventRef eventRef = IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, machTime,
300         transducerTypeFromString(info[HIDEventInputType]),  // transducerType
301         0,                                                  // index
302         0,                                                  // identifier
303         eventMask,                                          // event mask
304         0,                                                  // button event
305         0,                                                  // x
306         0,                                                  // y
307         0,                                                  // z
308         0,                                                  // presure
309         0,                                                  // twist
310         range,                                              // range
311         touch,                                              // touch
312         kIOHIDEventOptionNone);
313
314     IOHIDEventSetIntegerValue(eventRef, kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
315
316     NSArray *childEvents = info[HIDEventTouchesKey];
317     for (NSDictionary *touchInfo in childEvents) {
318         
319         [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] updateDebugIndicatorForTouch:[touchInfo[HIDEventTouchIDKey] intValue] withPointInWindowCoordinates:CGPointMake([touchInfo[HIDEventXKey] floatValue], [touchInfo[HIDEventYKey] floatValue]) isTouching:(BOOL)touch];
320         
321         IOHIDDigitizerEventMask childEventMask = 0;
322
323         UITouchPhase phase = phaseFromString(touchInfo[HIDEventPhaseKey]);
324         if (phase != UITouchPhaseCancelled && phase != UITouchPhaseBegan && phase != UITouchPhaseEnded && phase != UITouchPhaseStationary)
325             childEventMask |= kIOHIDDigitizerEventPosition;
326
327         if (phase == UITouchPhaseBegan || phase == UITouchPhaseEnded || phase == UITouchPhaseCancelled)
328             childEventMask |= (kIOHIDDigitizerEventTouch | kIOHIDDigitizerEventRange);
329
330         if (phase == UITouchPhaseCancelled)
331             childEventMask |= kIOHIDDigitizerEventCancel;
332         
333         if ([touchInfo[HIDEventPressureKey] floatValue])
334             childEventMask |= kIOHIDDigitizerEventAttribute;
335
336         IOHIDEventRef subEvent = IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault, machTime,
337             [touchInfo[HIDEventTouchIDKey] intValue],               // index
338             2,                                                      // identifier (which finger we think it is). FIXME: this should come from the data.
339             childEventMask,
340             [touchInfo[HIDEventXKey] floatValue],
341             [touchInfo[HIDEventYKey] floatValue],
342             0, // z
343             [touchInfo[HIDEventPressureKey] floatValue],
344             [touchInfo[HIDEventTwistKey] floatValue],
345             touch,                                                  // range
346             touch,                                                  // touch
347             kIOHIDEventOptionNone);
348
349         IOHIDEventSetFloatValue(subEvent, kIOHIDEventFieldDigitizerMajorRadius, [touchInfo[HIDEventMajorRadiusKey] floatValue]);
350         IOHIDEventSetFloatValue(subEvent, kIOHIDEventFieldDigitizerMinorRadius, [touchInfo[HIDEventMinorRadiusKey] floatValue]);
351
352         IOHIDEventAppendEvent(eventRef, subEvent, 0);
353         CFRelease(subEvent);
354     }
355
356     return eventRef;
357 }
358
359 - (IOHIDEventRef)_createIOHIDEventType:(HandEventType)eventType
360 {
361     BOOL isTouching = (eventType == HandEventTouched || eventType == HandEventMoved || eventType == HandEventChordChanged || eventType == StylusEventTouched || eventType == StylusEventMoved);
362
363     IOHIDDigitizerEventMask eventMask = kIOHIDDigitizerEventTouch;
364     if (eventType == HandEventMoved) {
365         eventMask &= ~kIOHIDDigitizerEventTouch;
366         eventMask |= kIOHIDDigitizerEventPosition;
367         eventMask |= kIOHIDDigitizerEventAttribute;
368     } else if (eventType == HandEventChordChanged) {
369         eventMask |= kIOHIDDigitizerEventPosition;
370         eventMask |= kIOHIDDigitizerEventAttribute;
371     } else if (eventType == HandEventTouched || eventType == HandEventCanceled || eventType == HandEventLifted)
372         eventMask |= kIOHIDDigitizerEventIdentity;
373
374     uint64_t machTime = mach_absolute_time();
375     RetainPtr<IOHIDEventRef> eventRef = adoptCF(IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, machTime,
376         kIOHIDDigitizerTransducerTypeHand,
377         0,
378         0,
379         eventMask,
380         0,
381         0, 0, 0,
382         0,
383         0,
384         0,
385         isTouching,
386         kIOHIDEventOptionNone));
387
388     IOHIDEventSetIntegerValue(eventRef.get(), kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
389
390     for (NSUInteger i = 0; i < _activePointCount; ++i) {
391         SyntheticEventDigitizerInfo* pointInfo = &_activePoints[i];
392         if (eventType == HandEventTouched) {
393             if (!pointInfo->pathMajorRadius)
394                 pointInfo->pathMajorRadius = defaultMajorRadius;
395             if (!pointInfo->pathPressure)
396                 pointInfo->pathPressure = defaultPathPressure;
397             if (!pointInfo->pathProximity)
398                 pointInfo->pathProximity = kGSEventPathInfoInTouch | kGSEventPathInfoInRange;
399         } else if (eventType == HandEventLifted || eventType == HandEventCanceled || eventType == StylusEventLifted) {
400             pointInfo->pathMajorRadius = 0;
401             pointInfo->pathPressure = 0;
402             pointInfo->pathProximity = 0;
403         }
404
405         CGPoint point = pointInfo->point;
406         point = CGPointMake(roundf(point.x), roundf(point.y));
407         
408         [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] updateDebugIndicatorForTouch:i withPointInWindowCoordinates:point isTouching:isTouching];
409
410         RetainPtr<IOHIDEventRef> subEvent;
411         if (pointInfo->isStylus) {
412             if (eventType == StylusEventTouched) {
413                 eventMask |= kIOHIDDigitizerEventEstimatedAltitude;
414                 eventMask |= kIOHIDDigitizerEventEstimatedAzimuth;
415                 eventMask |= kIOHIDDigitizerEventEstimatedPressure;
416             } else if (eventType == StylusEventMoved)
417                 eventMask = kIOHIDDigitizerEventPosition;
418
419             subEvent = adoptCF(IOHIDEventCreateDigitizerStylusEventWithPolarOrientation(kCFAllocatorDefault, machTime,
420                 pointInfo->identifier,
421                 pointInfo->identifier,
422                 eventMask,
423                 0,
424                 point.x, point.y, 0,
425                 pointInfo->pathPressure,
426                 pointInfo->pathPressure,
427                 0,
428                 pointInfo->altitudeAngle,
429                 pointInfo->azimuthAngle,
430                 1,
431                 0,
432                 isTouching ? kIOHIDTransducerTouch : 0));
433
434             if (eventType == StylusEventTouched)
435                 IOHIDEventSetIntegerValue(subEvent.get(), kIOHIDEventFieldDigitizerWillUpdateMask, 0x0400);
436             else if (eventType == StylusEventMoved)
437                 IOHIDEventSetIntegerValue(subEvent.get(), kIOHIDEventFieldDigitizerDidUpdateMask, 0x0400);
438
439         } else {
440             subEvent = adoptCF(IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault, machTime,
441                 pointInfo->identifier,
442                 pointInfo->identifier,
443                 eventMask,
444                 point.x, point.y, 0,
445                 pointInfo->pathPressure,
446                 0,
447                 pointInfo->pathProximity & kGSEventPathInfoInRange,
448                 pointInfo->pathProximity & kGSEventPathInfoInTouch,
449                 kIOHIDEventOptionNone));
450         }
451
452         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMajorRadius, pointInfo->pathMajorRadius);
453         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMinorRadius, pointInfo->pathMajorRadius);
454
455         IOHIDEventAppendEvent(eventRef.get(), subEvent.get(), 0);
456     }
457
458     return eventRef.leakRef();
459 }
460
461 - (BOOL)_sendHIDEvent:(IOHIDEventRef)eventRef
462 {
463     if (!_ioSystemClient)
464         _ioSystemClient = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
465
466     if (eventRef) {
467         RetainPtr<IOHIDEventRef> strongEvent = eventRef;
468         dispatch_async(dispatch_get_main_queue(), ^{
469             uint32_t contextID = [UIApplication sharedApplication].keyWindow._contextId;
470             ASSERT(contextID);
471             BKSHIDEventSetDigitizerInfo(strongEvent.get(), contextID, false, false, NULL, 0, 0);
472             [[UIApplication sharedApplication] _enqueueHIDEvent:strongEvent.get()];
473         });
474     }
475     return YES;
476 }
477
478 - (BOOL)sendMarkerHIDEventWithCompletionBlock:(void (^)(void))completionBlock
479 {
480     auto callbackID = [HIDEventGenerator nextEventCallbackID];
481     [_eventCallbacks setObject:Block_copy(completionBlock) forKey:@(callbackID)];
482
483     auto markerEvent = adoptCF(IOHIDEventCreateVendorDefinedEvent(kCFAllocatorDefault,
484         mach_absolute_time(),
485         kHIDPage_VendorDefinedStart + 100,
486         0,
487         1,
488         (uint8_t*)&callbackID,
489         sizeof(CFIndex),
490         kIOHIDEventOptionNone));
491     
492     if (markerEvent) {
493         dispatch_async(dispatch_get_main_queue(), [markerEvent = WTFMove(markerEvent)] {
494             auto contextID = [UIApplication sharedApplication].keyWindow._contextId;
495             ASSERT(contextID);
496             BKSHIDEventSetDigitizerInfo(markerEvent.get(), contextID, false, false, NULL, 0, 0);
497             [[UIApplication sharedApplication] _enqueueHIDEvent:markerEvent.get()];
498         });
499     }
500     return YES;
501 }
502
503 - (void)_updateTouchPoints:(CGPoint*)points count:(NSUInteger)count
504 {
505     HandEventType handEventType;
506     
507     // The hand event type is based on previous state.
508     if (!_activePointCount)
509         handEventType = HandEventTouched;
510     else if (!count)
511         handEventType = HandEventLifted;
512     else if (count == _activePointCount)
513         handEventType = HandEventMoved;
514     else
515         handEventType = HandEventChordChanged;
516     
517     // Update previous count for next event.
518     _activePointCount = count;
519
520     // Update point locations.
521     for (NSUInteger i = 0; i < count; ++i) {
522         _activePoints[i].point = points[i];
523         [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] updateDebugIndicatorForTouch:i withPointInWindowCoordinates:points[i] isTouching:YES];
524     }
525     
526     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:handEventType]);
527     [self _sendHIDEvent:eventRef.get()];
528 }
529
530 - (void)touchDownAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
531 {
532     touchCount = std::min(touchCount, HIDMaxTouchCount);
533
534     _activePointCount = touchCount;
535
536     for (NSUInteger index = 0; index < touchCount; ++index) {
537         _activePoints[index].point = locations[index];
538         _activePoints[index].isStylus = NO;
539         
540         [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] updateDebugIndicatorForTouch:index withPointInWindowCoordinates:locations[index] isTouching:YES];
541     }
542
543     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventTouched]);
544     [self _sendHIDEvent:eventRef.get()];
545 }
546
547 - (void)touchDown:(CGPoint)location touchCount:(NSUInteger)touchCount
548 {
549     touchCount = std::min(touchCount, HIDMaxTouchCount);
550
551     CGPoint locations[touchCount];
552
553     for (NSUInteger index = 0; index < touchCount; ++index)
554         locations[index] = location;
555     
556     [self touchDownAtPoints:locations touchCount:touchCount];
557 }
558
559 - (void)touchDown:(CGPoint)location
560 {
561     [self touchDownAtPoints:&location touchCount:1];
562 }
563
564 - (void)liftUpAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
565 {
566     touchCount = std::min(touchCount, HIDMaxTouchCount);
567     touchCount = std::min(touchCount, _activePointCount);
568
569     NSUInteger newPointCount = _activePointCount - touchCount;
570
571     for (NSUInteger index = 0; index < touchCount; ++index) {
572         _activePoints[newPointCount + index].point = locations[index];
573         
574         [[GeneratedTouchesDebugWindow sharedGeneratedTouchesDebugWindow] updateDebugIndicatorForTouch:index withPointInWindowCoordinates:CGPointZero isTouching:NO];
575     }
576     
577     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventLifted]);
578     [self _sendHIDEvent:eventRef.get()];
579     
580     _activePointCount = newPointCount;
581 }
582
583 - (void)liftUp:(CGPoint)location touchCount:(NSUInteger)touchCount
584 {
585     touchCount = std::min(touchCount, HIDMaxTouchCount);
586
587     CGPoint locations[touchCount];
588
589     for (NSUInteger index = 0; index < touchCount; ++index)
590         locations[index] = location;
591     
592     [self liftUpAtPoints:locations touchCount:touchCount];
593 }
594
595 - (void)liftUp:(CGPoint)location
596 {
597     [self liftUp:location touchCount:1];
598 }
599
600 - (void)moveToPoints:(CGPoint*)newLocations touchCount:(NSUInteger)touchCount duration:(NSTimeInterval)seconds
601 {
602     touchCount = std::min(touchCount, HIDMaxTouchCount);
603
604     CGPoint startLocations[touchCount];
605     CGPoint nextLocations[touchCount];
606
607     CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
608     CFTimeInterval elapsed = 0;
609
610     int eventIndex = 0;
611     while (elapsed < (seconds - fingerMoveInterval)) {
612         elapsed = secondsSinceAbsoluteTime(startTime);
613         CFTimeInterval interval = elapsed / seconds;
614         
615         for (NSUInteger i = 0; i < touchCount; ++i) {
616             if (!eventIndex)
617                 startLocations[i] = _activePoints[i].point;
618
619             nextLocations[i] = calculateNextCurveLocation(startLocations[i], newLocations[i], interval);
620         }
621         [self _updateTouchPoints:nextLocations count:touchCount];
622
623         delayBetweenMove(eventIndex++, elapsed);
624     }
625
626     [self _updateTouchPoints:newLocations count:touchCount];
627 }
628
629 - (void)touchDown:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock
630 {
631     [self touchDown:location touchCount:count];
632     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
633 }
634
635 - (void)liftUp:(CGPoint)location touchCount:(NSUInteger)count completionBlock:(void (^)(void))completionBlock
636 {
637     [self liftUp:location touchCount:count];
638     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
639 }
640
641 - (void)stylusDownAtPoint:(CGPoint)location azimuthAngle:(CGFloat)azimuthAngle altitudeAngle:(CGFloat)altitudeAngle pressure:(CGFloat)pressure
642 {
643     _activePointCount = 1;
644     _activePoints[0].point = location;
645     _activePoints[0].isStylus = YES;
646
647     // At the time of writing, the IOKit documentation isn't always correct. For example
648     // it says that pressure is a value [0,1], but in practice it is [0,500] for stylus
649     // data. It does not mention that the azimuth angle is offset from a full rotation.
650     // Also, UIKit and IOHID interpret the altitude as different adjacent angles.
651     _activePoints[0].pathPressure = pressure * 500;
652     _activePoints[0].azimuthAngle = M_PI * 2 - azimuthAngle;
653     _activePoints[0].altitudeAngle = M_PI_2 - altitudeAngle;
654
655     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:StylusEventTouched]);
656     [self _sendHIDEvent:eventRef.get()];
657 }
658
659 - (void)stylusMoveToPoint:(CGPoint)location azimuthAngle:(CGFloat)azimuthAngle altitudeAngle:(CGFloat)altitudeAngle pressure:(CGFloat)pressure
660 {
661     _activePointCount = 1;
662     _activePoints[0].point = location;
663     _activePoints[0].isStylus = YES;
664     // See notes above for details on these calculations.
665     _activePoints[0].pathPressure = pressure * 500;
666     _activePoints[0].azimuthAngle = M_PI * 2 - azimuthAngle;
667     _activePoints[0].altitudeAngle = M_PI_2 - altitudeAngle;
668
669     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:StylusEventMoved]);
670     [self _sendHIDEvent:eventRef.get()];
671 }
672
673 - (void)stylusUpAtPoint:(CGPoint)location
674 {
675     _activePointCount = 1;
676     _activePoints[0].point = location;
677     _activePoints[0].isStylus = YES;
678     _activePoints[0].pathPressure = 0;
679     _activePoints[0].azimuthAngle = 0;
680     _activePoints[0].altitudeAngle = 0;
681
682     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:StylusEventLifted]);
683     [self _sendHIDEvent:eventRef.get()];
684 }
685
686 - (void)stylusDownAtPoint:(CGPoint)location azimuthAngle:(CGFloat)azimuthAngle altitudeAngle:(CGFloat)altitudeAngle pressure:(CGFloat)pressure completionBlock:(void (^)(void))completionBlock
687 {
688     [self stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure];
689     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
690 }
691
692 - (void)stylusMoveToPoint:(CGPoint)location azimuthAngle:(CGFloat)azimuthAngle altitudeAngle:(CGFloat)altitudeAngle pressure:(CGFloat)pressure completionBlock:(void (^)(void))completionBlock
693 {
694     [self stylusMoveToPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure];
695     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
696 }
697
698 - (void)stylusUpAtPoint:(CGPoint)location completionBlock:(void (^)(void))completionBlock
699 {
700     [self stylusUpAtPoint:location];
701     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
702 }
703
704 - (void)stylusTapAtPoint:(CGPoint)location azimuthAngle:(CGFloat)azimuthAngle altitudeAngle:(CGFloat)altitudeAngle pressure:(CGFloat)pressure completionBlock:(void (^)(void))completionBlock
705 {
706     struct timespec pressDelay = { 0, static_cast<long>(fingerLiftDelay * nanosecondsPerSecond) };
707
708     [self stylusDownAtPoint:location azimuthAngle:azimuthAngle altitudeAngle:altitudeAngle pressure:pressure];
709     nanosleep(&pressDelay, 0);
710     [self stylusUpAtPoint:location];
711
712     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
713 }
714
715 - (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount completionBlock:(void (^)(void))completionBlock
716 {
717     struct timespec doubleDelay = { 0, static_cast<long>(multiTapInterval * nanosecondsPerSecond) };
718     struct timespec pressDelay = { 0, static_cast<long>(fingerLiftDelay * nanosecondsPerSecond) };
719
720     for (int i = 0; i < tapCount; i++) {
721         [self touchDown:location touchCount:touchCount];
722         nanosleep(&pressDelay, 0);
723         [self liftUp:location touchCount:touchCount];
724         if (i + 1 != tapCount) 
725             nanosleep(&doubleDelay, 0);
726     }
727     
728     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
729 }
730
731 - (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
732 {
733     [self sendTaps:1 location:location withNumberOfTouches:1 completionBlock:completionBlock];
734 }
735
736 - (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
737 {
738     [self sendTaps:2 location:location withNumberOfTouches:1 completionBlock:completionBlock];
739 }
740
741 - (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
742 {
743     [self sendTaps:1 location:location withNumberOfTouches:2 completionBlock:completionBlock];
744 }
745
746 - (void)longPress:(CGPoint)location completionBlock:(void (^)(void))completionBlock
747 {
748     [self touchDown:location touchCount:1];
749     auto completionBlockCopy = makeBlockPtr(completionBlock);
750
751     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, longPressHoldDelay * nanosecondsPerSecond), dispatch_get_main_queue(), ^ {
752         [self liftUp:location];
753         [self sendMarkerHIDEventWithCompletionBlock:completionBlockCopy.get()];
754     });
755 }
756
757 - (void)dragWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
758 {
759     [self touchDown:startLocation touchCount:1];
760     [self moveToPoints:&endLocation touchCount:1 duration:seconds];
761     [self liftUp:endLocation];
762     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
763 }
764
765 - (void)pinchCloseWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
766 {
767 }
768
769 - (void)pinchOpenWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
770 {
771 }
772
773 - (void)markerEventReceived:(IOHIDEventRef)event
774 {
775     if (IOHIDEventGetType(event) != kIOHIDEventTypeVendorDefined)
776         return;
777
778     CFIndex callbackID = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedData);
779     void (^completionBlock)() = [_eventCallbacks objectForKey:@(callbackID)];
780     if (completionBlock) {
781         [_eventCallbacks removeObjectForKey:@(callbackID)];
782         completionBlock();
783         Block_release(completionBlock);
784     }
785 }
786
787 - (BOOL)checkForOutstandingCallbacks
788 {
789     return !([_eventCallbacks count] > 0);
790 }
791
792 static inline bool shouldWrapWithShiftKeyEventForCharacter(NSString *key)
793 {
794     if (key.length != 1)
795         return false;
796     int keyCode = [key characterAtIndex:0];
797     if (65 <= keyCode && keyCode <= 90)
798         return true;
799     switch (keyCode) {
800     case '!':
801     case '@':
802     case '#':
803     case '$':
804     case '%':
805     case '^':
806     case '&':
807     case '*':
808     case '(':
809     case ')':
810     case '_':
811     case '+':
812     case '{':
813     case '}':
814     case '|':
815     case ':':
816     case '"':
817     case '<':
818     case '>':
819     case '?':
820     case '~':
821         return true;
822     }
823     return false;
824 }
825
826 static Optional<uint32_t> keyCodeForDOMFunctionKey(NSString *key)
827 {
828     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
829     // If the input string is a function-key name, set its key code. On iOS the key codes for the first 12
830     // function keys are disjoint from the key codes of the last 12 function keys.
831     for (int i = 1; i <= 12; ++i) {
832         if ([key isEqualToString:[NSString stringWithFormat:@"F%d", i]])
833             return kHIDUsage_KeyboardF1 + i - 1;
834     }
835     for (int i = 13; i <= 24; ++i) {
836         if ([key isEqualToString:[NSString stringWithFormat:@"F%d", i]])
837             return kHIDUsage_KeyboardF13 + i - 13;
838     }
839     return WTF::nullopt;
840 }
841
842 static inline uint32_t hidUsageCodeForCharacter(NSString *key)
843 {
844     const int uppercaseAlphabeticOffset = 'A' - kHIDUsage_KeyboardA;
845     const int lowercaseAlphabeticOffset = 'a' - kHIDUsage_KeyboardA;
846     const int numericNonZeroOffset = '1' - kHIDUsage_Keyboard1;
847     if (key.length == 1) {
848         // Handle alphanumeric characters and basic symbols.
849         int keyCode = [key characterAtIndex:0];
850         if (97 <= keyCode && keyCode <= 122) // Handle a-z.
851             return keyCode - lowercaseAlphabeticOffset;
852
853         if (65 <= keyCode && keyCode <= 90) // Handle A-Z.
854             return keyCode - uppercaseAlphabeticOffset;
855
856         if (49 <= keyCode && keyCode <= 57) // Handle 1-9.
857             return keyCode - numericNonZeroOffset;
858
859         // Handle all other cases.
860         switch (keyCode) {
861         case '`':
862         case '~':
863             return kHIDUsage_KeyboardGraveAccentAndTilde;
864         case '!':
865             return kHIDUsage_Keyboard1;
866         case '@':
867             return kHIDUsage_Keyboard2;
868         case '#':
869             return kHIDUsage_Keyboard3;
870         case '$':
871             return kHIDUsage_Keyboard4;
872         case '%':
873             return kHIDUsage_Keyboard5;
874         case '^':
875             return kHIDUsage_Keyboard6;
876         case '&':
877             return kHIDUsage_Keyboard7;
878         case '*':
879             return kHIDUsage_Keyboard8;
880         case '(':
881             return kHIDUsage_Keyboard9;
882         case ')':
883         case '0':
884             return kHIDUsage_Keyboard0;
885         case '-':
886         case '_':
887             return kHIDUsage_KeyboardHyphen;
888         case '=':
889         case '+':
890             return kHIDUsage_KeyboardEqualSign;
891         case '\b':
892             return kHIDUsage_KeyboardDeleteOrBackspace;
893         case '\t':
894             return kHIDUsage_KeyboardTab;
895         case '[':
896         case '{':
897             return kHIDUsage_KeyboardOpenBracket;
898         case ']':
899         case '}':
900             return kHIDUsage_KeyboardCloseBracket;
901         case '\\':
902         case '|':
903             return kHIDUsage_KeyboardBackslash;
904         case ';':
905         case ':':
906             return kHIDUsage_KeyboardSemicolon;
907         case '\'':
908         case '"':
909             return kHIDUsage_KeyboardQuote;
910         case '\r':
911         case '\n':
912             return kHIDUsage_KeyboardReturnOrEnter;
913         case ',':
914         case '<':
915             return kHIDUsage_KeyboardComma;
916         case '.':
917         case '>':
918             return kHIDUsage_KeyboardPeriod;
919         case '/':
920         case '?':
921             return kHIDUsage_KeyboardSlash;
922         case ' ':
923             return kHIDUsage_KeyboardSpacebar;
924         }
925     }
926
927     if (auto keyCode = keyCodeForDOMFunctionKey(key))
928         return *keyCode;
929
930     if ([key isEqualToString:@"capsLock"] || [key isEqualToString:@"capsLockKey"])
931         return kHIDUsage_KeyboardCapsLock;
932     if ([key isEqualToString:@"pageUp"])
933         return kHIDUsage_KeyboardPageUp;
934     if ([key isEqualToString:@"pageDown"])
935         return kHIDUsage_KeyboardPageDown;
936     if ([key isEqualToString:@"home"])
937         return kHIDUsage_KeyboardHome;
938     if ([key isEqualToString:@"insert"])
939         return kHIDUsage_KeyboardInsert;
940     if ([key isEqualToString:@"end"])
941         return kHIDUsage_KeyboardEnd;
942     if ([key isEqualToString:@"escape"])
943         return kHIDUsage_KeyboardEscape;
944     if ([key isEqualToString:@"return"] || [key isEqualToString:@"enter"])
945         return kHIDUsage_KeyboardReturnOrEnter;
946     if ([key isEqualToString:@"leftArrow"])
947         return kHIDUsage_KeyboardLeftArrow;
948     if ([key isEqualToString:@"rightArrow"])
949         return kHIDUsage_KeyboardRightArrow;
950     if ([key isEqualToString:@"upArrow"])
951         return kHIDUsage_KeyboardUpArrow;
952     if ([key isEqualToString:@"downArrow"])
953         return kHIDUsage_KeyboardDownArrow;
954     if ([key isEqualToString:@"delete"])
955         return kHIDUsage_KeyboardDeleteOrBackspace;
956     if ([key isEqualToString:@"forwardDelete"])
957         return kHIDUsage_KeyboardDeleteForward;
958     if ([key isEqualToString:@"leftCommand"] || [key isEqualToString:@"metaKey"])
959         return kHIDUsage_KeyboardLeftGUI;
960     if ([key isEqualToString:@"rightCommand"])
961         return kHIDUsage_KeyboardRightGUI;
962     if ([key isEqualToString:@"clear"]) // Num Lock / Clear
963         return kHIDUsage_KeypadNumLock;
964     if ([key isEqualToString:@"leftControl"] || [key isEqualToString:@"ctrlKey"])
965         return kHIDUsage_KeyboardLeftControl;
966     if ([key isEqualToString:@"rightControl"])
967         return kHIDUsage_KeyboardRightControl;
968     if ([key isEqualToString:@"leftShift"] || [key isEqualToString:@"shiftKey"])
969         return kHIDUsage_KeyboardLeftShift;
970     if ([key isEqualToString:@"rightShift"])
971         return kHIDUsage_KeyboardRightShift;
972     if ([key isEqualToString:@"leftAlt"] || [key isEqualToString:@"altKey"])
973         return kHIDUsage_KeyboardLeftAlt;
974     if ([key isEqualToString:@"rightAlt"])
975         return kHIDUsage_KeyboardRightAlt;
976     if ([key isEqualToString:@"numpadComma"])
977         return kHIDUsage_KeypadComma;
978
979     return 0;
980 }
981
982 RetainPtr<IOHIDEventRef> createHIDKeyEvent(NSString *character, uint64_t timestamp, bool isKeyDown)
983 {
984     return adoptCF(IOHIDEventCreateKeyboardEvent(kCFAllocatorDefault, timestamp, kHIDPage_KeyboardOrKeypad, hidUsageCodeForCharacter(character), isKeyDown, kIOHIDEventOptionNone));
985 }
986
987 - (void)keyDown:(NSString *)character
988 {
989     [self _sendIOHIDKeyboardEvent:mach_absolute_time() usage:hidUsageCodeForCharacter(character) isKeyDown:true];
990 }
991
992 - (void)keyUp:(NSString *)character
993 {
994     [self _sendIOHIDKeyboardEvent:mach_absolute_time() usage:hidUsageCodeForCharacter(character) isKeyDown:false];
995 }
996
997 - (void)keyPress:(NSString *)character completionBlock:(void (^)(void))completionBlock
998 {
999     bool shouldWrapWithShift = shouldWrapWithShiftKeyEventForCharacter(character);
1000     uint32_t usage = hidUsageCodeForCharacter(character);
1001     uint64_t absoluteMachTime = mach_absolute_time();
1002
1003     if (shouldWrapWithShift)
1004         [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:kHIDUsage_KeyboardLeftShift isKeyDown:true];
1005
1006     [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:usage isKeyDown:true];
1007     [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:usage isKeyDown:false];
1008
1009     if (shouldWrapWithShift)
1010         [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:kHIDUsage_KeyboardLeftShift isKeyDown:false];
1011
1012     [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
1013 }
1014
1015 - (void)dispatchEventWithInfo:(NSDictionary *)eventInfo
1016 {
1017     ASSERT([NSThread isMainThread]);
1018
1019     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventWithInfo:eventInfo]);
1020     [self _sendHIDEvent:eventRef.get()];
1021 }
1022
1023 - (NSArray *)interpolatedEvents:(NSDictionary *)interpolationsDictionary
1024 {
1025     NSDictionary *startEvent = interpolationsDictionary[HIDEventStartEventKey];
1026     NSDictionary *endEvent = interpolationsDictionary[HIDEventEndEventKey];
1027     NSTimeInterval timeStep = [interpolationsDictionary[HIDEventTimestepKey] doubleValue];
1028     InterpolationType interpolationType = interpolationFromString(interpolationsDictionary[HIDEventInterpolateKey]);
1029     
1030     NSMutableArray *interpolatedEvents = [NSMutableArray arrayWithObject:startEvent];
1031     
1032     NSTimeInterval startTime = [startEvent[HIDEventTimeOffsetKey] doubleValue];
1033     NSTimeInterval endTime = [endEvent[HIDEventTimeOffsetKey] doubleValue];
1034     NSTimeInterval time = startTime + timeStep;
1035     
1036     NSArray *startTouches = startEvent[HIDEventTouchesKey];
1037     NSArray *endTouches = endEvent[HIDEventTouchesKey];
1038     
1039     while (time < endTime) {
1040         NSMutableDictionary *newEvent = [endEvent mutableCopy];
1041         double timeRatio = (time - startTime) / (endTime - startTime);
1042         newEvent[HIDEventTimeOffsetKey] = [NSNumber numberWithDouble:(time)];
1043         
1044         NSEnumerator *startEnumerator = [startTouches objectEnumerator];
1045         NSDictionary *startTouch;
1046         NSMutableArray *newTouches = [NSMutableArray arrayWithCapacity:[endTouches count]];
1047         while (startTouch = [startEnumerator nextObject])  {
1048             NSEnumerator *endEnumerator = [endTouches objectEnumerator];
1049             NSDictionary *endTouch = [endEnumerator nextObject];
1050             NSInteger startTouchID = [startTouch[HIDEventTouchIDKey] integerValue];
1051             
1052             while (endTouch && ([endTouch[HIDEventTouchIDKey] integerValue] != startTouchID))
1053                 endTouch = [endEnumerator nextObject];
1054             
1055             if (endTouch) {
1056                 NSMutableDictionary *newTouch = [endTouch mutableCopy];
1057                 
1058                 if (newTouch[HIDEventXKey] != startTouch[HIDEventXKey])
1059                     newTouch[HIDEventXKey] = @(interpolations[interpolationType]([startTouch[HIDEventXKey] doubleValue], [endTouch[HIDEventXKey] doubleValue], timeRatio));
1060                 
1061                 if (newTouch[HIDEventYKey] != startTouch[HIDEventYKey])
1062                     newTouch[HIDEventYKey] = @(interpolations[interpolationType]([startTouch[HIDEventYKey] doubleValue], [endTouch[HIDEventYKey] doubleValue], timeRatio));
1063                 
1064                 if (newTouch[HIDEventPressureKey] != startTouch[HIDEventPressureKey])
1065                     newTouch[HIDEventPressureKey] = @(interpolations[interpolationType]([startTouch[HIDEventPressureKey] doubleValue], [endTouch[HIDEventPressureKey] doubleValue], timeRatio));
1066                 
1067                 [newTouches addObject:newTouch];
1068                 [newTouch release];
1069             } else
1070                 NSLog(@"Missing End Touch with ID: %ld", (long)startTouchID);
1071         }
1072         
1073         newEvent[HIDEventTouchesKey] = newTouches;
1074         
1075         [interpolatedEvents addObject:newEvent];
1076         [newEvent release];
1077         time += timeStep;
1078     }
1079     
1080     [interpolatedEvents addObject:endEvent];
1081
1082     return interpolatedEvents;
1083 }
1084
1085 - (NSArray *)expandEvents:(NSArray *)events withStartTime:(CFAbsoluteTime)startTime
1086 {
1087     NSMutableArray *expandedEvents = [NSMutableArray array];
1088     for (NSDictionary *event in events) {
1089         NSString *interpolate = event[HIDEventInterpolateKey];
1090         // we have key events that we need to generate
1091         if (interpolate) {
1092             NSArray *newEvents = [self interpolatedEvents:event];
1093             [expandedEvents addObjectsFromArray:[self expandEvents:newEvents withStartTime:startTime]];
1094         } else
1095             [expandedEvents addObject:event];
1096     }
1097     return expandedEvents;
1098 }
1099
1100 - (void)eventDispatchThreadEntry:(NSDictionary *)threadData
1101 {
1102     NSDictionary *eventStream = threadData[@"eventInfo"];
1103     void (^completionBlock)() = threadData[@"completionBlock"];
1104     
1105     NSArray *events = eventStream[TopLevelEventInfoKey];
1106     if (!events.count) {
1107         NSLog(@"No events found in event stream");
1108         return;
1109     }
1110     
1111     CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
1112     
1113     NSArray *expandedEvents = [self expandEvents:events withStartTime:startTime];
1114     
1115     for (NSDictionary *eventInfo in expandedEvents) {
1116         NSTimeInterval eventRelativeTime = [eventInfo[HIDEventTimeOffsetKey] doubleValue];
1117         CFAbsoluteTime targetTime = startTime + eventRelativeTime;
1118         
1119         CFTimeInterval waitTime = targetTime - CFAbsoluteTimeGetCurrent();
1120         if (waitTime > 0)
1121             [NSThread sleepForTimeInterval:waitTime];
1122         
1123         dispatch_async(dispatch_get_main_queue(), ^ {
1124             [self dispatchEventWithInfo:eventInfo];
1125         });
1126     }
1127     
1128     dispatch_async(dispatch_get_main_queue(), ^ {
1129         [self sendMarkerHIDEventWithCompletionBlock:completionBlock];
1130     });
1131 }
1132
1133 - (void)sendEventStream:(NSDictionary *)eventInfo completionBlock:(void (^)(void))completionBlock
1134 {
1135     if (!eventInfo) {
1136         NSLog(@"eventInfo is nil");
1137         if (completionBlock)
1138             completionBlock();
1139         return;
1140     }
1141     
1142     NSDictionary* threadData = @{
1143         @"eventInfo": [[eventInfo copy] autorelease],
1144         @"completionBlock": [[completionBlock copy] autorelease]
1145     };
1146     
1147     NSThread *eventDispatchThread = [[[NSThread alloc] initWithTarget:self selector:@selector(eventDispatchThreadEntry:) object:threadData] autorelease];
1148     eventDispatchThread.qualityOfService = NSQualityOfServiceUserInteractive;
1149     [eventDispatchThread start];
1150 }
1151
1152 @end