/* * Copyright (C) 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "EventSerializerMac.h" #if PLATFORM(MAC) #import "CoreGraphicsTestSPI.h" #import #import #import #import #define MOUSE_EVENT_TYPES \ (CGSEventType)kCGEventLeftMouseDown, \ (CGSEventType)kCGEventLeftMouseUp, \ (CGSEventType)kCGEventRightMouseDown, \ (CGSEventType)kCGEventRightMouseUp, \ (CGSEventType)kCGEventMouseMoved, \ (CGSEventType)kCGEventLeftMouseDragged, \ (CGSEventType)kCGEventRightMouseDragged, \ (CGSEventType)kCGEventOtherMouseDown, \ (CGSEventType)kCGEventOtherMouseUp, \ (CGSEventType)kCGEventOtherMouseDragged #define KEY_EVENT_TYPES \ (CGSEventType)kCGEventKeyDown, \ (CGSEventType)kCGEventKeyUp, \ (CGSEventType)kCGEventFlagsChanged #define GESTURE_EVENT_TYPES \ kCGSEventGesture, \ kCGSEventFluidTouchGesture, \ kCGSEventDockControl bool eventIsOfType(CGEventRef event, CGSEventType type) { return (CGSEventType)CGEventGetType(event) == type; } bool eventIsOfTypes(CGEventRef) { return false; } template bool eventIsOfTypes(CGEventRef event, CGSEventType first, Types ... rest) { return eventIsOfType(event, first) || eventIsOfTypes(event, rest...); } bool eventIsOfGestureType(CGEventRef event, IOHIDEventType type) { return (IOHIDEventType)CGEventGetIntegerValueField(event, kCGEventGestureHIDType) == type; } bool eventIsOfGestureTypes(CGEventRef) { return false; } template bool eventIsOfGestureTypes(CGEventRef event, IOHIDEventType first, Types ... rest) { if (!eventIsOfTypes(event, GESTURE_EVENT_TYPES)) return false; return eventIsOfGestureType(event, first) || eventIsOfGestureTypes(event, rest...); } #define FOR_EACH_CGEVENT_INTEGER_FIELD(macro) \ macro(true, kCGSEventTypeField) \ \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis1) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis2) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventDeltaAxis3) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventIsContinuous) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventMomentumPhase) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis1) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis2) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventPointDeltaAxis3) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventScrollCount) \ macro(eventIsOfTypes(rawEvent, kCGSEventScrollWheel, kCGSEventZoom), kCGScrollWheelEventScrollPhase) \ \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventButtonNumber) \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventClickState) \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventDeltaX) \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventDeltaY) \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventSubtype) \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventNumber) \ \ macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventAutorepeat) \ macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventKeyboardType) \ macro(eventIsOfTypes(rawEvent, KEY_EVENT_TYPES), kCGKeyboardEventKeycode) \ \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureHIDType) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureBehavior) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureFlavor) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureMask) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGesturePhase) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureStage) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventScrollGestureFlagBits) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventSwipeGestureFlagBits) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeMask) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeMotion) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeValue) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureFlavor) \ \ macro(eventIsOfGestureTypes(rawEvent, kCGHIDEventTypeGestureStarted, kCGHIDEventTypeGestureEnded), kCGEventGestureStartEndSeriesType) #define FOR_EACH_CGEVENT_DOUBLE_FIELD(macro) \ macro(eventIsOfTypes(rawEvent, GESTURE_EVENT_TYPES), kCGEventGestureProgress) \ \ macro(eventIsOfTypes(rawEvent, MOUSE_EVENT_TYPES), kCGMouseEventPressure) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomDeltaX) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomDeltaY) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeZoom), kCGEventGestureZoomValue) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeRotation), kCGEventGestureRotationValue) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollX) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollY) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeScroll), kCGEventGestureScrollZ) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipePositionX) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipePositionY) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeProgress) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityX) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityY) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeNavigationSwipe), kCGEventGestureSwipeVelocityZ) \ \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeForce), kCGEventTransitionProgress) \ macro(eventIsOfGestureType(rawEvent, kIOHIDEventTypeForce), kCGEventStagePressure) #define LOAD_INTEGER_FIELD_FROM_EVENT(eventTypeFilter, field) \ ^ { \ if (!(eventTypeFilter)) \ return; \ int64_t value = CGEventGetIntegerValueField(rawEvent, field); \ int64_t plainValue = CGEventGetIntegerValueField(rawPlainEvent, field); \ if (value != plainValue) \ dict.get()[@#field] = @(value); \ }(); #define LOAD_DOUBLE_FIELD_FROM_EVENT(eventTypeFilter, field) \ ^ { \ if (!(eventTypeFilter)) \ return; \ double value = CGEventGetDoubleValueField(rawEvent, field); \ if (!isnan(value)) { \ double plainValue = CGEventGetDoubleValueField(rawPlainEvent, field); \ if (fabs(value - plainValue) >= FLT_EPSILON) \ dict.get()[@#field] = @(value); \ } \ }(); #define STORE_INTEGER_FIELD_TO_EVENT(eventTypeFilter, field) \ ^ { \ if (!(eventTypeFilter)) \ return; \ NSNumber *value = dict[@#field]; \ if (value) \ CGEventSetIntegerValueField(rawEvent, field, value.unsignedLongLongValue); \ }(); #define STORE_DOUBLE_FIELD_TO_EVENT(eventTypeFilter, field) \ ^ { \ if (!(eventTypeFilter)) \ return; \ NSNumber *value = dict[@#field]; \ if (value) \ CGEventSetDoubleValueField(rawEvent, field, value.doubleValue); \ }(); @implementation EventSerializer + (NSDictionary *)dictionaryForEvent:(CGEventRef)rawEvent relativeToTime:(CGEventTimestamp)referenceTimestamp { auto plainEvent = adoptCF(CGEventCreate(NULL)); CGEventRef rawPlainEvent = plainEvent.get(); auto dict = adoptNS([[NSMutableDictionary alloc] init]); FOR_EACH_CGEVENT_INTEGER_FIELD(LOAD_INTEGER_FIELD_FROM_EVENT); FOR_EACH_CGEVENT_DOUBLE_FIELD(LOAD_DOUBLE_FIELD_FROM_EVENT); CGEventTimestamp timestamp = CGEventGetTimestamp(rawEvent); dict.get()[@"relativeTimeMS"] = @(std::max(static_cast(timestamp - referenceTimestamp) / NSEC_PER_MSEC, 0.0)); CGSEventType eventType = (CGSEventType)CGEventGetIntegerValueField(rawEvent, kCGSEventTypeField); if (eventType == kCGSEventGesture || eventType == kCGSEventFluidTouchGesture || eventType == kCGSEventDockControl) { if (CGEventGetIntegerValueField(rawEvent, kCGEventGestureIsPreflight)) { dict.get()[@"kCGEventGestureIsPreflight"] = @YES; dict.get()[@"kCGEventGesturePreflightProgress"] = @(CGEventGetDoubleValueField(rawEvent, kCGEventGesturePreflightProgress)); } } dict.get()[@"windowLocation"] = NSStringFromPoint(NSPointFromCGPoint(CGEventGetWindowLocation(rawEvent))); auto flags = static_cast(CGEventGetFlags(rawEvent) & ~NX_NONCOALSESCEDMASK); auto plainFlags = static_cast(CGEventGetFlags(rawPlainEvent) & ~NX_NONCOALSESCEDMASK); if (flags != plainFlags) dict.get()[@"flags"] = @(flags); return dict.autorelease(); } + (RetainPtr)createEventForDictionary:(NSDictionary *)dict inWindow:(NSWindow *)window relativeToTime:(CGEventTimestamp)referenceTimestamp { auto event = adoptCF(CGEventCreate(NULL)); CGEventRef rawEvent = event.get(); FOR_EACH_CGEVENT_INTEGER_FIELD(STORE_INTEGER_FIELD_TO_EVENT); FOR_EACH_CGEVENT_DOUBLE_FIELD(STORE_DOUBLE_FIELD_TO_EVENT); if (dict[@"relativeTimeMS"]) CGEventSetTimestamp(event.get(), referenceTimestamp + static_cast(([dict[@"relativeTimeMS"] doubleValue] * NSEC_PER_MSEC))); if ([dict[@"kCGEventGestureIsPreflight"] boolValue]) { CGEventSetIntegerValueField(rawEvent, kCGEventGestureIsPreflight, 1); CGEventSetDoubleValueField(rawEvent, kCGEventGesturePreflightProgress, [dict[@"kCGEventGesturePreflightProgress"] doubleValue]); } if (dict[@"windowLocation"]) { CGPoint windowLocation = NSPointToCGPoint(NSPointFromString(dict[@"windowLocation"])); CGEventSetWindowLocation(rawEvent, windowLocation); NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowLocation.x, windowLocation.y, 1, 1)].origin; CGEventSetLocation(rawEvent, CGPointMake(screenPoint.x, NSScreen.screens.firstObject.frame.size.height - screenPoint.y)); } if (dict[@"flags"]) CGEventSetFlags(rawEvent, static_cast([dict[@"flags"] unsignedLongLongValue])); CGEventSetIntegerValueField(rawEvent, kCGSEventWindowIDField, window.windowNumber); return event; } @end @implementation EventStreamPlayer const float eventDispatchTimerRate = 1. / 120.; + (void)playStream:(NSArray *)eventDicts window:(NSWindow *)window completionHandler:(void(^)())completionHandler { auto player = adoptNS([[EventStreamPlayer alloc] init]); player->_remainingEventDictionaries = adoptNS([eventDicts mutableCopy]); player->_window = window; if (completionHandler) player->_completionHandler = makeBlockPtr(completionHandler); player->_startTime = mach_absolute_time(); [NSTimer scheduledTimerWithTimeInterval:eventDispatchTimerRate target:player.get() selector:@selector(playbackTimerFired:) userInfo:nil repeats:YES]; } - (void)playbackTimerFired:(NSTimer *)timer { auto removeList = adoptNS([[NSMutableArray alloc] init]); NSEvent *nsEvent = nil; for (id eventDict in _remainingEventDictionaries.get()) { auto event = [EventSerializer createEventForDictionary:eventDict inWindow:_window.get() relativeToTime:_startTime]; if (CGEventGetTimestamp(event.get()) < mach_absolute_time()) { nsEvent = [NSEvent eventWithCGEvent:event.get()]; [NSApp postEvent:nsEvent atStart:NO]; [removeList addObject:eventDict]; } } [_remainingEventDictionaries removeObjectsInArray:removeList.get()]; if ([_remainingEventDictionaries count]) return; NSEventType applicationDefinedEventType = NSEventTypeApplicationDefined; [NSApp postEvent:[NSEvent otherEventWithType:applicationDefinedEventType location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:0 subtype:0 data1:42 data2:0] atStart:NO]; // Block until we send the last event we posted. while (true) { NSEvent *nextEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate dateWithTimeIntervalSinceNow:eventDispatchTimerRate] inMode:NSDefaultRunLoopMode dequeue:YES]; if (nextEvent.type == applicationDefinedEventType && nextEvent.data1 == 42) break; if (nextEvent) [NSApp sendEvent:nextEvent]; } if (_completionHandler) _completionHandler(); [timer invalidate]; } @end #endif