dbb828ddcafea9758c1c9abc0d148faca15ce54c
[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 "IOKitSPI.h"
30 #import "UIKitSPI.h"
31 #import <WebCore/SoftLinking.h>
32 #import <mach/mach_time.h>
33 #import <wtf/Assertions.h>
34 #import <wtf/RetainPtr.h>
35
36 SOFT_LINK_PRIVATE_FRAMEWORK(BackBoardServices)
37 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));
38
39 static const NSTimeInterval fingerLiftDelay = 5e7;
40 static const NSTimeInterval multiTapInterval = 15e7;
41 static const NSTimeInterval fingerMoveInterval = 0.016;
42 static const IOHIDFloat defaultMajorRadius = 5;
43 static const IOHIDFloat defaultPathPressure = 0;
44 static const NSUInteger maxTouchCount = 5;
45 static const long nanosecondsPerSecond = 1e9;
46
47 static int fingerIdentifiers[maxTouchCount] = { 2, 3, 4, 5, 1 };
48
49 typedef enum {
50     HandEventNull,
51     HandEventTouched,
52     HandEventMoved,
53     HandEventChordChanged,
54     HandEventLifted,
55     HandEventCanceled,
56     HandEventInRange,
57     HandEventInRangeLift,
58 } HandEventType;
59
60 typedef struct {
61     int identifier;
62     CGPoint point;
63     IOHIDFloat pathMajorRadius;
64     IOHIDFloat pathPressure;
65     UInt8 pathProximity;
66 } SyntheticEventDigitizerInfo;
67
68 static CFTimeInterval secondsSinceAbsoluteTime(CFAbsoluteTime startTime)
69 {
70     return (CFAbsoluteTimeGetCurrent() - startTime);
71 }
72
73 static double simpleDragCurve(double a, double b, double t)
74 {
75     return (a + (b - a) * sin(sin(t * M_PI / 2) * t * M_PI / 2));
76 }
77
78 static CGPoint calculateNextLocation(CGPoint a, CGPoint b, CFTimeInterval t)
79 {
80     return CGPointMake(simpleDragCurve(a.x, b.x, t), simpleDragCurve(a.y, b.y, t));
81 }
82
83 static void delayBetweenMove(int eventIndex, double elapsed)
84 {
85     // Delay next event until expected elapsed time.
86     double delay = (eventIndex * fingerMoveInterval) - elapsed;
87     if (delay > 0) {
88         struct timespec moveDelay = { 0, static_cast<long>(delay * nanosecondsPerSecond) };
89         nanosleep(&moveDelay, NULL);
90     }   
91 }
92
93 @interface HIDEventGenerator ()
94 @property (nonatomic, strong) NSMutableDictionary *eventCallbacks;
95 @end
96
97 @implementation HIDEventGenerator {
98     IOHIDEventSystemClientRef _ioSystemClient;
99     SyntheticEventDigitizerInfo _activePoints[maxTouchCount];
100     NSUInteger _activePointCount;
101 }
102
103 + (HIDEventGenerator *)sharedHIDEventGenerator
104 {
105     static HIDEventGenerator *eventGenerator = nil;
106     if (!eventGenerator)
107         eventGenerator = [[HIDEventGenerator alloc] init];
108
109     return eventGenerator;
110 }
111
112 + (unsigned)nextEventCallbackID
113 {
114     static unsigned callbackID = 0;
115     return ++callbackID;
116 }
117
118 - (instancetype)init
119 {
120     self = [super init];
121     if (!self)
122         return nil;
123
124     for (NSUInteger i = 0; i < maxTouchCount; ++i)
125         _activePoints[i].identifier = fingerIdentifiers[i];
126
127     _eventCallbacks = [[NSMutableDictionary alloc] init];
128
129     return self;
130 }
131
132 - (void)_sendIOHIDKeyboardEvent:(uint64_t)timestamp usage:(uint32_t)usage isKeyDown:(bool)isKeyDown
133 {
134     RetainPtr<IOHIDEventRef> eventRef = adoptCF(IOHIDEventCreateKeyboardEvent(kCFAllocatorDefault,
135         timestamp,
136         kHIDPage_KeyboardOrKeypad,
137         usage,
138         isKeyDown,
139         kIOHIDEventOptionNone));
140     [self _sendHIDEvent:eventRef.get()];
141 }
142
143 - (IOHIDEventRef)_createIOHIDEventType:(HandEventType)eventType
144 {
145     BOOL isTouching = (eventType == HandEventTouched || eventType == HandEventMoved || eventType == HandEventChordChanged);
146
147     IOHIDDigitizerEventMask eventMask = kIOHIDDigitizerEventTouch;
148     if (eventType == HandEventMoved) {
149         eventMask &= ~kIOHIDDigitizerEventTouch;
150         eventMask |= kIOHIDDigitizerEventPosition;
151         eventMask |= kIOHIDDigitizerEventAttribute;
152     } else if (eventType == HandEventChordChanged) {
153         eventMask |= kIOHIDDigitizerEventPosition;
154         eventMask |= kIOHIDDigitizerEventAttribute;
155     } else if (eventType == HandEventTouched  || eventType == HandEventCanceled) {
156         eventMask |= kIOHIDDigitizerEventIdentity;
157     } else if (eventType == HandEventLifted)
158         eventMask |= kIOHIDDigitizerEventIdentity;
159
160     uint64_t machTime = mach_absolute_time();
161     RetainPtr<IOHIDEventRef> eventRef = adoptCF(IOHIDEventCreateDigitizerEvent(kCFAllocatorDefault, machTime,
162         kIOHIDDigitizerTransducerTypeHand,
163         0,
164         0,
165         eventMask,
166         0,
167         0, 0, 0,
168         0,
169         0,
170         0,
171         isTouching,
172         kIOHIDEventOptionNone));
173
174     IOHIDEventSetIntegerValue(eventRef.get(), kIOHIDEventFieldDigitizerIsDisplayIntegrated, 1);
175
176     for (NSUInteger i = 0; i < _activePointCount; ++i) {
177         SyntheticEventDigitizerInfo* pointInfo = &_activePoints[i];
178         if (eventType == HandEventTouched) {
179             if (!pointInfo->pathMajorRadius)
180                 pointInfo->pathMajorRadius = defaultMajorRadius;
181             if (!pointInfo->pathPressure)
182                 pointInfo->pathPressure = defaultPathPressure;
183             if (!pointInfo->pathProximity)
184                 pointInfo->pathProximity = kGSEventPathInfoInTouch | kGSEventPathInfoInRange;
185         } else if (eventType == HandEventLifted || eventType == HandEventCanceled) {
186             pointInfo->pathMajorRadius = 0;
187             pointInfo->pathPressure = 0;
188             pointInfo->pathProximity = 0;
189         }
190
191         CGPoint point = pointInfo->point;
192         point = CGPointMake(roundf(point.x), roundf(point.y));
193         RetainPtr<IOHIDEventRef> subEvent = adoptCF(IOHIDEventCreateDigitizerFingerEvent(kCFAllocatorDefault, machTime,
194             pointInfo->identifier,
195             pointInfo->identifier,
196             eventMask,
197             point.x, point.y, 0,
198             pointInfo->pathPressure,
199             0,
200             pointInfo->pathProximity & kGSEventPathInfoInRange,
201             pointInfo->pathProximity & kGSEventPathInfoInTouch,
202             kIOHIDEventOptionNone));
203         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMajorRadius, pointInfo->pathMajorRadius);
204         IOHIDEventSetFloatValue(subEvent.get(), kIOHIDEventFieldDigitizerMinorRadius, pointInfo->pathMajorRadius);
205         IOHIDEventAppendEvent(eventRef.get(), subEvent.get(), 0);
206     }
207
208     return eventRef.leakRef();
209 }
210
211 - (BOOL)_sendHIDEvent:(IOHIDEventRef)eventRef
212 {
213     if (!_ioSystemClient)
214         _ioSystemClient = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
215
216     if (eventRef) {
217         RetainPtr<IOHIDEventRef> strongEvent = eventRef;
218         dispatch_async(dispatch_get_main_queue(), ^{
219             uint32_t contextID = [UIApplication sharedApplication].keyWindow._contextId;
220             ASSERT(contextID);
221             BKSHIDEventSetDigitizerInfo(strongEvent.get(), contextID, false, false, NULL, 0, 0);
222             [[UIApplication sharedApplication] _enqueueHIDEvent:strongEvent.get()];
223         });
224     }
225     return YES;
226 }
227
228 - (BOOL)_sendMarkerHIDEventWithCompletionBlock:(void (^)(void))completionBlock
229 {
230     unsigned callbackID = [HIDEventGenerator nextEventCallbackID];
231     void (^completionBlockCopy)() = Block_copy(completionBlock);
232     [_eventCallbacks setObject:completionBlockCopy forKey:@(callbackID)];
233
234     uint64_t machTime = mach_absolute_time();
235     RetainPtr<IOHIDEventRef> markerEvent = adoptCF(IOHIDEventCreateVendorDefinedEvent(kCFAllocatorDefault,
236         machTime,
237         kHIDPage_VendorDefinedStart + 100,
238         0,
239         1,
240         (uint8_t*)&callbackID,
241         sizeof(unsigned),
242         kIOHIDEventOptionNone));
243     
244     if (markerEvent) {
245         markerEvent.get();
246         dispatch_async(dispatch_get_main_queue(), ^{
247             uint32_t contextID = [UIApplication sharedApplication].keyWindow._contextId;
248             ASSERT(contextID);
249             BKSHIDEventSetDigitizerInfo(markerEvent.get(), contextID, false, false, NULL, 0, 0);
250             [[UIApplication sharedApplication] _enqueueHIDEvent:markerEvent.get()];
251         });
252     }
253     return YES;
254 }
255
256 - (void)_updateTouchPoints:(CGPoint*)points count:(NSUInteger)count
257 {
258     HandEventType handEventType;
259     
260     // The hand event type is based on previous state.
261     if (!_activePointCount)
262         handEventType = HandEventTouched;
263     else if (!count)
264         handEventType = HandEventLifted;
265     else if (count == _activePointCount)
266         handEventType = HandEventMoved;
267     else
268         handEventType = HandEventChordChanged;
269     
270     // Update previous count for next event.
271     _activePointCount = count;
272
273     // Update point locations.
274     for (NSUInteger i = 0; i < count; ++i)
275         _activePoints[i].point = points[i];
276     
277     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:handEventType]);
278     [self _sendHIDEvent:eventRef.get()];
279 }
280
281 - (void)touchDownAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
282 {
283     touchCount = MIN(touchCount, maxTouchCount);
284
285     _activePointCount = touchCount;
286
287     for (NSUInteger index = 0; index < touchCount; ++index)
288         _activePoints[index].point = locations[index];
289
290     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventTouched]);
291     [self _sendHIDEvent:eventRef.get()];
292 }
293
294 - (void)touchDown:(CGPoint)location touchCount:(NSUInteger)touchCount
295 {
296     touchCount = MIN(touchCount, maxTouchCount);
297
298     CGPoint locations[touchCount];
299
300     for (NSUInteger index = 0; index < touchCount; ++index)
301         locations[index] = location;
302     
303     [self touchDownAtPoints:locations touchCount:touchCount];
304 }
305
306 - (void)touchDown:(CGPoint)location
307 {
308     [self touchDownAtPoints:&location touchCount:1];
309 }
310
311 - (void)liftUpAtPoints:(CGPoint*)locations touchCount:(NSUInteger)touchCount
312 {
313     touchCount = MIN(touchCount, maxTouchCount);
314     touchCount = MIN(touchCount, _activePointCount);
315
316     NSUInteger newPointCount = _activePointCount - touchCount;
317
318     for (NSUInteger index = 0; index < touchCount; ++index)
319         _activePoints[newPointCount + index].point = locations[index];
320     
321     RetainPtr<IOHIDEventRef> eventRef = adoptCF([self _createIOHIDEventType:HandEventLifted]);
322     [self _sendHIDEvent:eventRef.get()];
323     
324     _activePointCount = newPointCount;
325 }
326
327 - (void)liftUp:(CGPoint)location touchCount:(NSUInteger)touchCount
328 {
329     touchCount = MIN(touchCount, maxTouchCount);
330
331     CGPoint locations[touchCount];
332
333     for (NSUInteger index = 0; index < touchCount; ++index)
334         locations[index] = location;
335     
336     [self liftUpAtPoints:locations touchCount:touchCount];
337 }
338
339 - (void)liftUp:(CGPoint)location
340 {
341     [self liftUp:location touchCount:1];
342 }
343
344 - (void)moveToPoints:(CGPoint*)newLocations touchCount:(NSUInteger)touchCount duration:(NSTimeInterval)seconds
345 {
346     touchCount = MIN(touchCount, maxTouchCount);
347
348     CGPoint startLocations[touchCount];
349     CGPoint nextLocations[touchCount];
350
351     CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
352     CFTimeInterval elapsed = 0;
353
354     int eventIndex = 0;
355     while (elapsed < (seconds - fingerMoveInterval)) {
356         elapsed = secondsSinceAbsoluteTime(startTime);
357         CFTimeInterval interval = elapsed / seconds;
358         
359         for (NSUInteger i = 0; i < touchCount; ++i) {
360             if (!eventIndex)
361                 startLocations[i] = _activePoints[i].point;
362
363             nextLocations[i] = calculateNextLocation(startLocations[i], newLocations[i], interval);
364         }
365         [self _updateTouchPoints:nextLocations count:touchCount];
366
367         delayBetweenMove(eventIndex++, elapsed);
368     }
369
370     [self _updateTouchPoints:newLocations count:touchCount];
371 }
372
373 - (void)sendTaps:(int)tapCount location:(CGPoint)location withNumberOfTouches:(int)touchCount completionBlock:(void (^)(void))completionBlock
374 {
375     struct timespec doubleDelay = { 0, static_cast<long>(multiTapInterval) };
376     struct timespec pressDelay = { 0, static_cast<long>(fingerLiftDelay) };
377
378     for (int i = 0; i < tapCount; i++) {
379         [self touchDown:location touchCount:touchCount];
380         nanosleep(&pressDelay, 0);
381         [self liftUp:location touchCount:touchCount];
382         if (i + 1 != tapCount) 
383             nanosleep(&doubleDelay, 0);
384     }
385     
386     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
387 }
388
389 - (void)tap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
390 {
391     [self sendTaps:1 location:location withNumberOfTouches:1 completionBlock:completionBlock];
392 }
393
394 - (void)doubleTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
395 {
396     [self sendTaps:2 location:location withNumberOfTouches:1 completionBlock:completionBlock];
397 }
398
399 - (void)twoFingerTap:(CGPoint)location completionBlock:(void (^)(void))completionBlock
400 {
401     [self sendTaps:1 location:location withNumberOfTouches:2 completionBlock:completionBlock];
402 }
403
404 - (void)dragWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
405 {
406 }
407
408 - (void)pinchCloseWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
409 {
410 }
411
412 - (void)pinchOpenWithStartPoint:(CGPoint)startLocation endPoint:(CGPoint)endLocation duration:(double)seconds completionBlock:(void (^)(void))completionBlock
413 {
414 }
415
416 - (void)markerEventReceived:(IOHIDEventRef)event
417 {
418     if (IOHIDEventGetType(event) != kIOHIDEventTypeVendorDefined)
419         return;
420
421     CFIndex callbackID = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldVendorDefinedData);
422     void (^completionBlock)() = [_eventCallbacks objectForKey:@(callbackID)];
423     if (completionBlock) {
424         [_eventCallbacks removeObjectForKey:@(callbackID)];
425         completionBlock();
426         Block_release(completionBlock);
427     }
428 }
429
430 static inline bool shouldWrapWithShiftKeyEventForCharacter(NSString *key)
431 {
432     if (key.length != 1)
433         return false;
434     int keyCode = [key characterAtIndex:0];
435     if (65 <= keyCode && keyCode <= 90)
436         return true;
437     switch (keyCode) {
438     case '`':
439     case '!':
440     case '@':
441     case '#':
442     case '$':
443     case '%':
444     case '^':
445     case '&':
446     case '*':
447     case '(':
448     case ')':
449     case '_':
450     case '+':
451     case '{':
452     case '}':
453     case '|':
454     case ':':
455     case '"':
456     case '<':
457     case '>':
458     case '?':
459     case '~':
460         return true;
461     }
462     return false;
463 }
464
465 static inline uint32_t hidUsageCodeForCharacter(NSString *key)
466 {
467     const int uppercaseAlphabeticOffset = 'A' - kHIDUsage_KeyboardA;
468     const int lowercaseAlphabeticOffset = 'a' - kHIDUsage_KeyboardA;
469     const int numericNonZeroOffset = '1' - kHIDUsage_Keyboard1;
470     if (key.length == 1) {
471         // Handle alphanumeric characters and basic symbols.
472         int keyCode = [key characterAtIndex:0];
473         if (97 <= keyCode && keyCode <= 122) // Handle a-z.
474             return keyCode - lowercaseAlphabeticOffset;
475
476         if (65 <= keyCode && keyCode <= 90) // Handle A-Z.
477             return keyCode - uppercaseAlphabeticOffset;
478
479         if (49 <= keyCode && keyCode <= 57) // Handle 1-9.
480             return keyCode - numericNonZeroOffset;
481
482         // Handle all other cases.
483         switch (keyCode) {
484         case '`':
485         case '~':
486             return kHIDUsage_KeyboardGraveAccentAndTilde;
487         case '!':
488             return kHIDUsage_Keyboard1;
489         case '@':
490             return kHIDUsage_Keyboard2;
491         case '#':
492             return kHIDUsage_Keyboard3;
493         case '$':
494             return kHIDUsage_Keyboard4;
495         case '%':
496             return kHIDUsage_Keyboard5;
497         case '^':
498             return kHIDUsage_Keyboard6;
499         case '&':
500             return kHIDUsage_Keyboard7;
501         case '*':
502             return kHIDUsage_Keyboard8;
503         case '(':
504             return kHIDUsage_Keyboard9;
505         case ')':
506         case '0':
507             return kHIDUsage_Keyboard0;
508         case '-':
509         case '_':
510             return kHIDUsage_KeyboardHyphen;
511         case '=':
512         case '+':
513             return kHIDUsage_KeyboardEqualSign;
514         case '\t':
515             return kHIDUsage_KeyboardTab;
516         case '[':
517         case '{':
518             return kHIDUsage_KeyboardOpenBracket;
519         case ']':
520         case '}':
521             return kHIDUsage_KeyboardCloseBracket;
522         case '\\':
523         case '|':
524             return kHIDUsage_KeyboardBackslash;
525         case ';':
526         case ':':
527             return kHIDUsage_KeyboardSemicolon;
528         case '\'':
529         case '"':
530             return kHIDUsage_KeyboardQuote;
531         case '\r':
532         case '\n':
533             return kHIDUsage_KeyboardReturnOrEnter;
534         case ',':
535         case '<':
536             return kHIDUsage_KeyboardComma;
537         case '.':
538         case '>':
539             return kHIDUsage_KeyboardPeriod;
540         case '/':
541         case '?':
542             return kHIDUsage_KeyboardSlash;
543         case ' ':
544             return kHIDUsage_KeyboardSpacebar;
545         }
546     }
547     const int functionKeyOffset = kHIDUsage_KeyboardF1;
548     for (int functionKeyIndex = 1; functionKeyIndex <= 12; ++functionKeyIndex) {
549         if ([key isEqualToString:[NSString stringWithFormat:@"F%d", functionKeyIndex]])
550             return functionKeyOffset + functionKeyIndex - 1;
551     }
552     if ([key isEqualToString:@"escape"])
553         return kHIDUsage_KeyboardEscape;
554     if ([key isEqualToString:@"return"] || [key isEqualToString:@"enter"])
555         return kHIDUsage_KeyboardReturnOrEnter;
556     if ([key isEqualToString:@"leftArrow"])
557         return kHIDUsage_KeyboardLeftArrow;
558     if ([key isEqualToString:@"rightArrow"])
559         return kHIDUsage_KeyboardRightArrow;
560     if ([key isEqualToString:@"upArrow"])
561         return kHIDUsage_KeyboardUpArrow;
562     if ([key isEqualToString:@"downArrow"])
563         return kHIDUsage_KeyboardDownArrow;
564     if ([key isEqualToString:@"delete"])
565         return kHIDUsage_KeyboardDeleteOrBackspace;
566     // The simulator keyboard interprets both left and right modifier keys using the left version of the usage code.
567     if ([key isEqualToString:@"leftControl"] || [key isEqualToString:@"rightControl"])
568         return kHIDUsage_KeyboardLeftControl;
569     if ([key isEqualToString:@"leftShift"] || [key isEqualToString:@"rightShift"])
570         return kHIDUsage_KeyboardLeftShift;
571     if ([key isEqualToString:@"leftAlt"] || [key isEqualToString:@"rightAlt"])
572         return kHIDUsage_KeyboardLeftAlt;
573
574     return 0;
575 }
576
577 - (void)keyDown:(NSString *)character completionBlock:(void (^)(void))completionBlock
578 {
579     bool shouldWrapWithShift = shouldWrapWithShiftKeyEventForCharacter(character);
580     uint32_t usage = hidUsageCodeForCharacter(character);
581     uint64_t absoluteMachTime = mach_absolute_time();
582
583     if (shouldWrapWithShift)
584         [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:kHIDUsage_KeyboardLeftShift isKeyDown:true];
585
586     [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:usage isKeyDown:true];
587     [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:usage isKeyDown:false];
588
589     if (shouldWrapWithShift)
590         [self _sendIOHIDKeyboardEvent:absoluteMachTime usage:kHIDUsage_KeyboardLeftShift isKeyDown:false];
591
592     [self _sendMarkerHIDEventWithCompletionBlock:completionBlock];
593 }
594
595 @end