2 * Copyright (C) 2016 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #import "EventSerializerMac.h"
31 #import "CoreGraphicsTestSPI.h"
34 #import <mach/mach_time.h>
35 #import <wtf/RetainPtr.h>
37 #define MOUSE_EVENT_TYPES \
38 (CGSEventType)kCGEventLeftMouseDown, \
39 (CGSEventType)kCGEventLeftMouseUp, \
40 (CGSEventType)kCGEventRightMouseDown, \
41 (CGSEventType)kCGEventRightMouseUp, \
42 (CGSEventType)kCGEventMouseMoved, \
43 (CGSEventType)kCGEventLeftMouseDragged, \
44 (CGSEventType)kCGEventRightMouseDragged, \
45 (CGSEventType)kCGEventOtherMouseDown, \
46 (CGSEventType)kCGEventOtherMouseUp, \
47 (CGSEventType)kCGEventOtherMouseDragged
49 #define KEY_EVENT_TYPES \
50 (CGSEventType)kCGEventKeyDown, \
51 (CGSEventType)kCGEventKeyUp, \
52 (CGSEventType)kCGEventFlagsChanged
54 #define GESTURE_EVENT_TYPES \
56 kCGSEventFluidTouchGesture, \
59 bool eventIsOfType(CGEventRef event, CGSEventType type)
61 return (CGSEventType)CGEventGetType(event) == type;
64 bool eventIsOfTypes(CGEventRef) { return false; }
66 template<typename ... Types>
67 bool eventIsOfTypes(CGEventRef event, CGSEventType first, Types ... rest)
69 return eventIsOfType(event, first) || eventIsOfTypes(event, rest...);
72 bool eventIsOfGestureType(CGEventRef event, IOHIDEventType type)
74 return (IOHIDEventType)CGEventGetIntegerValueField(event, kCGEventGestureHIDType) == type;
77 bool eventIsOfGestureTypes(CGEventRef) { return false; }
79 template<typename ... Types>
80 bool eventIsOfGestureTypes(CGEventRef event, IOHIDEventType first, Types ... rest)
82 if (!eventIsOfTypes(event, GESTURE_EVENT_TYPES))
84 return eventIsOfGestureType(event, first) || eventIsOfGestureTypes(event, rest...);
87 #define FOR_EACH_CGEVENT_INTEGER_FIELD(macro) \
88 macro(true, kCGSEventTypeField) \
90 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis1) \
91 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis2) \
92 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis3) \
93 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventIsContinuous) \
94 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventMomentumPhase) \
95 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis1) \
96 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis2) \
97 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis3) \
98 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventScrollCount) \
99 macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventScrollPhase) \
101 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventButtonNumber) \
102 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventClickState) \
103 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventDeltaX) \
104 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventDeltaY) \
105 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventSubtype) \
106 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventNumber) \
108 macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventAutorepeat) \
109 macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventKeyboardType) \
110 macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventKeycode) \
112 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureHIDType) \
113 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureBehavior) \
114 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureFlavor) \
115 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureMask) \
116 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGesturePhase) \
117 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureStage) \
119 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventScrollGestureFlagBits) \
121 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventSwipeGestureFlagBits) \
122 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeMask) \
123 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeMotion) \
124 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeValue) \
125 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureFlavor) \
127 macro(eventIsOfGestureTypes(rawEvent, kCGHIDEventTypeGestureStarted, kCGHIDEventTypeGestureEnded), kCGEventGestureStartEndSeriesType)
129 #define FOR_EACH_CGEVENT_DOUBLE_FIELD(macro) \
130 macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureProgress) \
132 macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventPressure) \
134 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomDeltaX) \
135 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomDeltaY) \
136 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomValue) \
138 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeRotation), kCGEventGestureRotationValue) \
140 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollX) \
141 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollY) \
142 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollZ) \
144 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipePositionX) \
145 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipePositionY) \
146 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeProgress) \
147 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityX) \
148 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityY) \
149 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityZ) \
151 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeForce), kCGEventTransitionProgress) \
152 macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeForce), kCGEventStagePressure)
154 #define LOAD_INTEGER_FIELD_FROM_EVENT(eventTypeFilter, field) \
156 if (!(eventTypeFilter)) \
158 int64_t value = CGEventGetIntegerValueField(rawEvent, field); \
159 int64_t plainValue = CGEventGetIntegerValueField(rawPlainEvent, field); \
160 if (value != plainValue) \
161 dict[@#field] = @(value); \
164 #define LOAD_DOUBLE_FIELD_FROM_EVENT(eventTypeFilter, field) \
166 if (!(eventTypeFilter)) \
168 double value = CGEventGetDoubleValueField(rawEvent, field); \
169 if (!isnan(value)) { \
170 double plainValue = CGEventGetDoubleValueField(rawPlainEvent, field); \
171 if (fabs(value - plainValue) >= FLT_EPSILON) \
172 dict[@#field] = @(value); \
176 #define STORE_INTEGER_FIELD_TO_EVENT(eventTypeFilter, field) \
178 if (!(eventTypeFilter)) \
180 NSNumber *value = dict[@#field]; \
182 CGEventSetIntegerValueField(rawEvent, field, value.unsignedLongLongValue); \
185 #define STORE_DOUBLE_FIELD_TO_EVENT(eventTypeFilter, field) \
187 if (!(eventTypeFilter)) \
189 NSNumber *value = dict[@#field]; \
191 CGEventSetDoubleValueField(rawEvent, field, value.doubleValue); \
194 @implementation EventSerializer
196 + (NSDictionary *)dictionaryForEvent:(CGEventRef)rawEvent relativeToTime:(CGEventTimestamp)referenceTimestamp
198 RetainPtr<CGEventRef> plainEvent = adoptCF(CGEventCreate(NULL));
199 CGEventRef rawPlainEvent = plainEvent.get();
201 NSMutableDictionary *dict = [[[NSMutableDictionary alloc] init] autorelease];
203 FOR_EACH_CGEVENT_INTEGER_FIELD(LOAD_INTEGER_FIELD_FROM_EVENT);
204 FOR_EACH_CGEVENT_DOUBLE_FIELD(LOAD_DOUBLE_FIELD_FROM_EVENT);
206 CGEventTimestamp timestamp = CGEventGetTimestamp(rawEvent);
207 dict[@"relativeTimeMS"] = @(std::max(static_cast<double>(timestamp - referenceTimestamp) / NSEC_PER_MSEC, 0.0));
209 CGSEventType eventType = (CGSEventType)CGEventGetIntegerValueField(rawEvent, kCGSEventTypeField);
210 if (eventType == kCGSEventGesture || eventType == kCGSEventFluidTouchGesture || eventType == kCGSEventDockControl) {
211 if (CGEventGetIntegerValueField(rawEvent, kCGEventGestureIsPreflight)) {
212 dict[@"kCGEventGestureIsPreflight"] = @YES;
213 dict[@"kCGEventGesturePreflightProgress"] = @(CGEventGetDoubleValueField(rawEvent, kCGEventGesturePreflightProgress));
217 dict[@"windowLocation"] = NSStringFromPoint(NSPointFromCGPoint(CGEventGetWindowLocation(rawEvent)));
219 auto flags = static_cast<CGEventFlags>(CGEventGetFlags(rawEvent) & ~NX_NONCOALSESCEDMASK);
220 auto plainFlags = static_cast<CGEventFlags>(CGEventGetFlags(rawPlainEvent) & ~NX_NONCOALSESCEDMASK);
221 if (flags != plainFlags)
222 dict[@"flags"] = @(flags);
227 + (RetainPtr<CGEventRef>)createEventForDictionary:(NSDictionary *)dict inWindow:(NSWindow *)window relativeToTime:(CGEventTimestamp)referenceTimestamp
229 RetainPtr<CGEventRef> event = adoptCF(CGEventCreate(NULL));
230 CGEventRef rawEvent = event.get();
232 FOR_EACH_CGEVENT_INTEGER_FIELD(STORE_INTEGER_FIELD_TO_EVENT);
233 FOR_EACH_CGEVENT_DOUBLE_FIELD(STORE_DOUBLE_FIELD_TO_EVENT);
235 if (dict[@"relativeTimeMS"])
236 CGEventSetTimestamp(event.get(), referenceTimestamp + static_cast<CGEventTimestamp>(([dict[@"relativeTimeMS"] doubleValue] * NSEC_PER_MSEC)));
238 if ([dict[@"kCGEventGestureIsPreflight"] boolValue]) {
239 CGEventSetIntegerValueField(rawEvent, kCGEventGestureIsPreflight, 1);
240 CGEventSetDoubleValueField(rawEvent, kCGEventGesturePreflightProgress, [dict[@"kCGEventGesturePreflightProgress"] doubleValue]);
243 if (dict[@"windowLocation"]) {
244 CGPoint windowLocation = NSPointToCGPoint(NSPointFromString(dict[@"windowLocation"]));
245 CGEventSetWindowLocation(rawEvent, windowLocation);
247 NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowLocation.x, windowLocation.y, 1, 1)].origin;
248 CGEventSetLocation(rawEvent, CGPointMake(screenPoint.x, NSScreen.screens.firstObject.frame.size.height - screenPoint.y));
252 CGEventSetFlags(rawEvent, static_cast<CGEventFlags>([dict[@"flags"] unsignedLongLongValue]));
254 CGEventSetIntegerValueField(rawEvent, kCGSEventWindowIDField, window.windowNumber);
261 @implementation EventStreamPlayer
263 const float eventDispatchTimerRate = 1. / 120.;
265 + (void)playStream:(NSArray<NSDictionary *> *)eventDicts window:(NSWindow *)window completionHandler:(void(^)())completionHandler
267 RetainPtr<EventStreamPlayer> player = adoptNS([[EventStreamPlayer alloc] init]);
269 player->_remainingEventDictionaries = adoptNS([eventDicts mutableCopy]);
270 player->_window = window;
272 if (completionHandler)
273 player->_completionHandler = makeBlockPtr(completionHandler);
275 player->_startTime = mach_absolute_time();
277 [NSTimer scheduledTimerWithTimeInterval:eventDispatchTimerRate target:player.get() selector:@selector(playbackTimerFired:) userInfo:nil repeats:YES];
280 - (void)playbackTimerFired:(NSTimer *)timer
282 auto removeList = adoptNS([[NSMutableArray alloc] init]);
283 NSEvent *nsEvent = nil;
284 for (id eventDict in _remainingEventDictionaries.get()) {
285 RetainPtr<CGEventRef> event = [EventSerializer createEventForDictionary:eventDict inWindow:_window.get() relativeToTime:_startTime];
286 if (CGEventGetTimestamp(event.get()) < mach_absolute_time()) {
287 nsEvent = [NSEvent eventWithCGEvent:event.get()];
288 [NSApp postEvent:nsEvent atStart:NO];
289 [removeList addObject:eventDict];
292 [_remainingEventDictionaries removeObjectsInArray:removeList.get()];
294 if ([_remainingEventDictionaries count])
297 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
298 NSEventType applicationDefinedEventType = NSEventTypeApplicationDefined;
300 NSEventType applicationDefinedEventType = NSApplicationDefined;
302 [NSApp postEvent:[NSEvent otherEventWithType:applicationDefinedEventType location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:0 subtype:0 data1:42 data2:0] atStart:NO];
303 // Block until we send the last event we posted.
305 NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:eventDispatchTimerRate] inMode:NSDefaultRunLoopMode dequeue:YES];
306 if (nextEvent.type == applicationDefinedEventType && nextEvent.data1 == 42)
309 [NSApp sendEvent:nextEvent];
312 if (_completionHandler)
313 _completionHandler();