blob: 21f737268c781c6f83e490a99f738a0cca314c6c [file] [log] [blame]
/*
* 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 <algorithm>
#import <mach/mach_time.h>
#import <pal/spi/cocoa/IOKitSPI.h>
#import <wtf/RetainPtr.h>
#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<typename ... Types>
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<typename ... Types>
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<double>(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<CGEventFlags>(CGEventGetFlags(rawEvent) & ~NX_NONCOALSESCEDMASK);
auto plainFlags = static_cast<CGEventFlags>(CGEventGetFlags(rawPlainEvent) & ~NX_NONCOALSESCEDMASK);
if (flags != plainFlags)
dict.get()[@"flags"] = @(flags);
return dict.autorelease();
}
+ (RetainPtr<CGEventRef>)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<CGEventTimestamp>(([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<CGEventFlags>([dict[@"flags"] unsignedLongLongValue]));
CGEventSetIntegerValueField(rawEvent, kCGSEventWindowIDField, window.windowNumber);
return event;
}
@end
@implementation EventStreamPlayer
const float eventDispatchTimerRate = 1. / 120.;
+ (void)playStream:(NSArray<NSDictionary *> *)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