408 lines
15 KiB
C
Executable File
408 lines
15 KiB
C
Executable File
/*
|
|
* TouchEvents.c
|
|
* TouchSynthesis
|
|
*
|
|
* Created by Nathan Vander Wilt on 1/13/10.
|
|
* Copyright 2010 Calf Trail Software, LLC. All rights reserved.
|
|
*
|
|
*/
|
|
#include "TouchEvents.h"
|
|
#include <mach/mach_time.h>
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wzero-length-array"
|
|
#include "IOHIDEventData.h"
|
|
#pragma clang diagnostic pop
|
|
|
|
const CFStringRef kTLInfoKeyDeviceID = CFSTR("deviceID");
|
|
const CFStringRef kTLInfoKeyTimestamp = CFSTR("timestamp");
|
|
const CFStringRef kTLInfoKeyGestureSubtype = CFSTR("gestureSubtype");
|
|
const CFStringRef kTLInfoKeyGesturePhase = CFSTR("gesturePhase");
|
|
const CFStringRef kTLInfoKeyMagnification = CFSTR("magnification");
|
|
const CFStringRef kTLInfoKeyRotation = CFSTR("rotation");
|
|
const CFStringRef kTLInfoKeySwipeDirection = CFSTR("swipeDirection");
|
|
const CFStringRef kTLInfoKeyNextSubtype = CFSTR("nextSubtype");
|
|
|
|
|
|
const CFStringRef kTLEventKeyType = CFSTR("type");
|
|
const CFStringRef kTLEventKeyTimestamp = CFSTR("timestamp");
|
|
const CFStringRef kTLEventKeyOptions = CFSTR("options");
|
|
|
|
const CFStringRef kTLEventKeyPositionX = CFSTR("position.x");
|
|
const CFStringRef kTLEventKeyPositionY = CFSTR("position.y");
|
|
const CFStringRef kTLEventKeyPositionZ = CFSTR("position.z");
|
|
|
|
const CFStringRef kTLEventKeyTransducerIndex = CFSTR("transducerIndex");
|
|
const CFStringRef kTLEventKeyTransducerType = CFSTR("transducerType");
|
|
const CFStringRef kTLEventKeyIdentity = CFSTR("identity");
|
|
const CFStringRef kTLEventKeyEventMask = CFSTR("eventMask");
|
|
|
|
const CFStringRef kTLEventKeyButtonMask = CFSTR("buttonMask");
|
|
const CFStringRef kTLEventKeyTipPressure = CFSTR("tipPressure");
|
|
const CFStringRef kTLEventKeyBarrelPressure = CFSTR("barrelPressure");
|
|
const CFStringRef kTLEventKeyTwist = CFSTR("twist");
|
|
|
|
const CFStringRef kTLEventKeyQuality = CFSTR("quality");
|
|
const CFStringRef kTLEventKeyDensity = CFSTR("density");
|
|
const CFStringRef kTLEventKeyIrregularity = CFSTR("irregularity");
|
|
const CFStringRef kTLEventKeyMajorRadius = CFSTR("majorRadius");
|
|
const CFStringRef kTLEventKeyMinorRadius = CFSTR("minorRadius");
|
|
|
|
|
|
static inline IOFixed tl_float2fixed(double f) { return (IOFixed)(f * 65536.0); }
|
|
|
|
static inline uint64_t tl_uptime() {
|
|
mach_timebase_info_data_t timebase ;
|
|
mach_timebase_info(&timebase) ;
|
|
uint64_t absTime = mach_absolute_time() ;
|
|
return ((absTime * timebase.numer) / timebase.denom);
|
|
}
|
|
|
|
static inline void setVendorData(IOHIDVendorDefinedEventData* vd, const void* data) {
|
|
memmove(vd->data, data, vd->length);
|
|
}
|
|
|
|
static void appendHeader(CFMutableDataRef data, uint8_t field, uint8_t type, uint16_t count) {
|
|
// serialize header
|
|
uint16_t swappedCount = CFSwapInt16HostToBig(count);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedCount, sizeof(uint16_t));
|
|
CFDataAppendBytes(data, &type, 1);
|
|
CFDataAppendBytes(data, &field, 1);
|
|
}
|
|
|
|
static void appendField(CFMutableDataRef data, uint8_t field, uint8_t type, uint16_t count, void* fieldData) {
|
|
appendHeader(data, field, type, count);
|
|
switch (type) {
|
|
case 0x00: // uint64_t as UnsignedWide
|
|
for (uint16_t i = 0; i < count; ++i) {
|
|
uint64_t val = ((uint64_t*)fieldData)[i];
|
|
uint32_t loVal = (uint32_t)val;
|
|
uint32_t swappedLoVal = CFSwapInt32HostToBig(loVal);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedLoVal, sizeof(uint32_t));
|
|
uint32_t hiVal = (uint32_t)(val >> 32);
|
|
uint32_t swappedHiVal = CFSwapInt32HostToBig(hiVal);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedHiVal, sizeof(uint32_t));
|
|
}
|
|
break;
|
|
case 0x10: // uint8_t
|
|
for (uint16_t i = 0; i < count; ++i) {
|
|
uint8_t val = ((uint8_t*)fieldData)[i];
|
|
CFDataAppendBytes(data, &val, 1);
|
|
}
|
|
break;
|
|
case 0x40:
|
|
for (uint16_t i = 0; i < count; ++i) {
|
|
uint32_t val = ((uint32_t*)fieldData)[i];
|
|
uint32_t swappedVal = CFSwapInt32HostToBig(val);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedVal, sizeof(uint32_t));
|
|
}
|
|
break;
|
|
case 0xC0:
|
|
for (uint16_t i = 0; i < count; ++i) {
|
|
Float32 val = ((Float32*)fieldData)[i];
|
|
CFSwappedFloat32 swappedVal = CFConvertFloat32HostToSwapped(val);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedVal, sizeof(CFSwappedFloat32));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void appendIntegerField(CFMutableDataRef data, uint8_t field, uint32_t value) {
|
|
(void)appendField;
|
|
appendHeader(data, field, 0x40, 1);
|
|
uint32_t swappedValue = CFSwapInt32HostToBig(value);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedValue, sizeof(uint32_t));
|
|
}
|
|
|
|
static void appendFloatField(CFMutableDataRef data, uint8_t field, Float32 value) {
|
|
appendHeader(data, field, 0xC0, 1);
|
|
CFSwappedFloat32 swappedValue = CFConvertFloat32HostToSwapped(value);
|
|
CFDataAppendBytes(data, (UInt8*)&swappedValue, sizeof(CFSwappedFloat32));
|
|
}
|
|
|
|
static void fillOutBase(CFDictionaryRef info, IOHIDEventData* event) {
|
|
CFNumberRef val;
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyTimestamp))) {
|
|
CFNumberGetValue(val, kCFNumberSInt64Type, &event->timestamp);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyOptions))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->options);
|
|
}
|
|
}
|
|
|
|
static void fillOutDigitizer(CFDictionaryRef info, IOHIDDigitizerEventData* event) {
|
|
event->size = (uint32_t)sizeof(IOHIDDigitizerEventData);
|
|
event->type = kIOHIDEventTypeDigitizer;
|
|
CFNumberRef val;
|
|
double d;
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyPositionX))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->position.x = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyPositionY))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->position.y = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyPositionZ))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->position.z = tl_float2fixed(d);
|
|
}
|
|
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyTransducerIndex))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->transducerIndex);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyTransducerType))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->transducerType);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyIdentity))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->identity);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyEventMask))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->eventMask);
|
|
}
|
|
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyButtonMask))) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &event->buttonMask);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyTipPressure))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->tipPressure = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyBarrelPressure))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->barrelPressure = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyTwist))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->twist = tl_float2fixed(d);
|
|
}
|
|
|
|
event->orientationType = kIOHIDDigitizerOrientationTypeQuality;
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyQuality))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->orientation.quality.quality = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyDensity))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->orientation.quality.density = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyIrregularity))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->orientation.quality.irregularity = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyMajorRadius))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->orientation.quality.majorRadius = tl_float2fixed(d);
|
|
}
|
|
if ((val = CFDictionaryGetValue(info, kTLEventKeyMinorRadius))) {
|
|
CFNumberGetValue(val, kCFNumberDoubleType, &d);
|
|
event->orientation.quality.minorRadius = tl_float2fixed(d);
|
|
}
|
|
}
|
|
|
|
CGEventRef tl_CGEventCreateFromGesture(CFDictionaryRef info, CFArrayRef touches) {
|
|
assert(info != NULL);
|
|
assert(touches != NULL);
|
|
typedef struct {
|
|
uint32_t count;
|
|
SInt64 sumX;
|
|
SInt64 sumY;
|
|
SInt64 sumZ;
|
|
} AvgPosition;
|
|
AvgPosition avgTouch = {0};
|
|
AvgPosition avgRange = {0};
|
|
AvgPosition avgOther = {0};
|
|
CFNumberRef val;
|
|
|
|
uint64_t timestamp;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyTimestamp);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt64Type, ×tamp);
|
|
}
|
|
else {
|
|
timestamp = tl_uptime();
|
|
}
|
|
|
|
IOHIDDigitizerEventData parent = {0};
|
|
parent.size = (uint32_t)sizeof(IOHIDDigitizerEventData);
|
|
parent.type = kIOHIDEventTypeDigitizer;
|
|
parent.timestamp = timestamp;
|
|
parent.options = kIOHIDEventOptionIsCollection;
|
|
parent.transducerType = kIOHIDDigitizerTransducerTypeHand;
|
|
|
|
CFMutableDataRef serializedTouches = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
|
CFIndex numTouches = CFArrayGetCount(touches);
|
|
for (CFIndex touchIdx = 0; touchIdx < numTouches; ++touchIdx) {
|
|
CFDictionaryRef touchInfo = CFArrayGetValueAtIndex(touches, touchIdx);
|
|
CFNumberRef typeVal = CFDictionaryGetValue(touchInfo, kTLEventKeyType);
|
|
if (!typeVal) continue;
|
|
|
|
int32_t type;
|
|
CFNumberGetValue(typeVal, kCFNumberSInt32Type, &type);
|
|
assert(type == kIOHIDEventTypeDigitizer); // only digitizer events currently supported
|
|
|
|
IOHIDDigitizerEventData touch = {0};
|
|
fillOutBase(touchInfo, (IOHIDEventData*)&touch);
|
|
fillOutDigitizer(touchInfo, &touch);
|
|
CFDataAppendBytes(serializedTouches, (UInt8*)&touch, touch.size);
|
|
|
|
if (touch.identity) parent.options |= touch.options;
|
|
parent.childEventMask |= touch.eventMask;
|
|
if (touch.options & kIOHIDTransducerTouch) {
|
|
++avgTouch.count;
|
|
avgTouch.sumX += touch.position.x;
|
|
avgTouch.sumY += touch.position.y;
|
|
avgTouch.sumZ += touch.position.z;
|
|
}
|
|
else if (touch.options & kIOHIDTransducerRange) {
|
|
++avgRange.count;
|
|
avgRange.sumX += touch.position.x;
|
|
avgRange.sumY += touch.position.y;
|
|
avgRange.sumZ += touch.position.z;
|
|
}
|
|
else {
|
|
++avgOther.count;
|
|
avgOther.sumX += touch.position.x;
|
|
avgOther.sumY += touch.position.y;
|
|
avgOther.sumZ += touch.position.z;
|
|
}
|
|
}
|
|
|
|
// calculate parent position
|
|
if (avgTouch.count) {
|
|
parent.position.x = (IOFixed)(avgTouch.sumX / avgTouch.count);
|
|
parent.position.y = (IOFixed)(avgTouch.sumY / avgTouch.count);
|
|
parent.position.z = (IOFixed)(avgTouch.sumZ / avgTouch.count);
|
|
}
|
|
else if (avgRange.count) {
|
|
parent.position.x = (IOFixed)(avgRange.sumX / avgRange.count);
|
|
parent.position.y = (IOFixed)(avgRange.sumY / avgRange.count);
|
|
parent.position.z = (IOFixed)(avgRange.sumZ / avgRange.count);
|
|
}
|
|
else if (avgOther.count) {
|
|
parent.position.x = (IOFixed)(avgOther.sumX / avgOther.count);
|
|
parent.position.y = (IOFixed)(avgOther.sumY / avgOther.count);
|
|
parent.position.z = (IOFixed)(avgOther.sumZ / avgOther.count);
|
|
}
|
|
|
|
// create vendor token
|
|
uint64_t deviceID = 0;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyDeviceID);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt64Type, &deviceID);
|
|
}
|
|
UInt8 vendorPayload[40] = {0};
|
|
*(uint64_t*)vendorPayload = CFSwapInt64HostToLittle(deviceID);
|
|
const size_t vendorPayloadLen = sizeof(vendorPayload);
|
|
const size_t vendorDataSize = sizeof(IOHIDVendorDefinedEventData) + vendorPayloadLen;
|
|
IOHIDVendorDefinedEventData* vendorData = malloc(vendorDataSize);
|
|
vendorData->size = (uint32_t)vendorDataSize;
|
|
vendorData->type = kIOHIDEventTypeVendorDefined;
|
|
vendorData->usagePage = 0xFF00;
|
|
vendorData->usage = 0x1777;
|
|
vendorData->version = 1;
|
|
vendorData->length = (uint32_t)vendorPayloadLen;
|
|
setVendorData(vendorData, vendorPayload);
|
|
|
|
// create base event
|
|
CGEventRef protoEvent = CGEventCreate(NULL);
|
|
CGEventSetType(protoEvent, 29); // NSEventTypeGesture
|
|
CGEventSetFlags(protoEvent, 256); // magic
|
|
CGEventSetTimestamp(protoEvent, timestamp);
|
|
|
|
// serialize base event
|
|
CFDataRef baseData = CGEventCreateData(kCFAllocatorDefault, protoEvent);
|
|
CFRelease(protoEvent);
|
|
CFMutableDataRef gestureData = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, baseData);
|
|
CFRelease(baseData);
|
|
|
|
// remove gesture fields CGEvent has added before the missing event data (it expects to find them after)
|
|
if (CFDataGetLength(gestureData) >= 24) {
|
|
CFDataDeleteBytes(gestureData, CFRangeMake(CFDataGetLength(gestureData) - 24, 24));
|
|
}
|
|
|
|
// serialize CGEvent field header for IOHID event queue
|
|
uint16_t totalSize = (sizeof(IOHIDSystemQueueElement) + vendorDataSize +
|
|
(numTouches + 1) * sizeof(IOHIDDigitizerEventData));
|
|
uint16_t swappedTotalSize = CFSwapInt16HostToBig((uint16_t)totalSize);
|
|
CFDataAppendBytes(gestureData, (UInt8*)&swappedTotalSize, 2);
|
|
CFDataAppendBytes(gestureData, (UInt8[]){0x10, 0x6D}, 2);
|
|
|
|
// serialize event queue collection header
|
|
IOHIDSystemQueueElement queueElement = {0};
|
|
queueElement.timeStamp = timestamp;
|
|
queueElement.options = parent.options;
|
|
queueElement.eventCount = (uint32_t)numTouches + 2;
|
|
CFDataAppendBytes(gestureData, (UInt8*)&queueElement, sizeof(queueElement));
|
|
|
|
// serialize touch event data
|
|
CFDataAppendBytes(gestureData, (UInt8*)&parent, parent.size);
|
|
CFDataAppendBytes(gestureData, CFDataGetBytePtr(serializedTouches), CFDataGetLength(serializedTouches));
|
|
CFRelease(serializedTouches);
|
|
|
|
// serialize vendor data
|
|
CFDataAppendBytes(gestureData, (UInt8*)vendorData, vendorDataSize);
|
|
free(vendorData);
|
|
|
|
// serialize gesture event fields
|
|
int32_t gestureSubtype = kTLInfoSubtypeGesture;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyGestureSubtype);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &gestureSubtype);
|
|
}
|
|
appendIntegerField(gestureData, 0x6E, gestureSubtype);
|
|
appendIntegerField(gestureData, 0x6F, 0); // magic
|
|
appendIntegerField(gestureData, 0x70, 0); // magic
|
|
|
|
int32_t gesturePhase = 0; // c.f. IOHIDEventPhaseBits
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyGesturePhase);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &gesturePhase);
|
|
}
|
|
appendIntegerField(gestureData, 0x84, gesturePhase);
|
|
appendIntegerField(gestureData, 0x85, 0); // magic?
|
|
|
|
|
|
if (gestureSubtype == kTLInfoSubtypeMagnify) {
|
|
Float32 magnification = 0.0f;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyMagnification);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberFloat32Type, &magnification);
|
|
}
|
|
appendFloatField(gestureData, 0x71, magnification);
|
|
}
|
|
else if (gestureSubtype == kTLInfoSubtypeRotate) {
|
|
Float32 rotation = 0.0f;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyRotation);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberFloat32Type, &rotation);
|
|
}
|
|
appendFloatField(gestureData, 0x72, rotation);
|
|
}
|
|
else if (gestureSubtype == kTLInfoSubtypeSwipe) {
|
|
int32_t swipeDirection = 0;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeySwipeDirection);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &swipeDirection);
|
|
}
|
|
appendIntegerField(gestureData, 0x73, swipeDirection);
|
|
}
|
|
else if (gestureSubtype == kTLInfoSubtypeBeginGesture ||
|
|
gestureSubtype == kTLInfoSubtypeEndGesture)
|
|
{
|
|
int32_t nextSubtype = 0;
|
|
val = CFDictionaryGetValue(info, kTLInfoKeyNextSubtype);
|
|
if (val) {
|
|
CFNumberGetValue(val, kCFNumberSInt32Type, &nextSubtype);
|
|
}
|
|
appendIntegerField(gestureData, 0x75, nextSubtype);
|
|
}
|
|
|
|
appendFloatField(gestureData, 0x8B, 0.0f); // magic?
|
|
appendFloatField(gestureData, 0x8C, 0.0f); // magic?
|
|
|
|
CGEventRef synthEvent = CGEventCreateFromData(kCFAllocatorDefault, gestureData);
|
|
CFRelease(gestureData);
|
|
return synthEvent;
|
|
}
|