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