I spent yesterday trying to get this working at ended up at a solution of sorts. I'm not entirely happy with it but it was the best I could do for now - if anyone can suggest any improvements or a working alternative I'd welcome them...
Anyway, for anyone else trying to do something similar. I based my solution on the API detailed in this post - I recorded the sequence of events I wanted to simulate, then played them back. The only hitch is that I couldn't get the built-in playback API to work (I got the same crash mentioned in the comments mentioned at the bottom). After some time digging around in ASM land, I ended up writing my own version.
@implementation UIApplication (EventReplay)
///
/// - replayEventsFromFile:
///
- (void)replayEventsFromFile:(NSString *)filename
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:filename];
NSArray* eventList = [[NSArray arrayWithContentsOfFile:filePath] retain];
[self replayEvents:eventList];
}
///
/// - replayEvents:
///
- (void)replayEvents:(NSArray *)events
{
if (!events.count)
return;
NSDictionary *eventDict = [events objectAtIndex:0U];
GSEventRef thisEvent = GSEventCreateWithPlist((CFDictionaryRef)eventDict);
uint64_t eventTime = thisEvent->record.timestamp;
thisEvent->record.timestamp = mach_absolute_time();
mach_port_t appPort = GSCopyPurpleNamedPort([[[NSBundle mainBundle] bundleIdentifier] UTF8String]);
GSSendEvent(&thisEvent->record, appPort);
mach_port_deallocate(mach_task_self(), appPort);
if (events.count <= 1)
return;
NSIndexSet *remainderIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, events.count - 1)];
NSArray *remainingEvents = [events objectsAtIndexes:remainderIndexes];
GSEventRef nextEvent = GSEventCreateWithPlist((CFDictionaryRef)[remainingEvents objectAtIndex:0U]);
NSTimeInterval nextEventDelay = GetTimeDelta(nextEvent->record.timestamp, eventTime);
if (nextEventDelay > 0.05)
[self performSelector:@selector(replayEvents:) withObject:remainingEvents afterDelay:nextEventDelay];
else
[self replayEvents:remainingEvents];
CFRelease(nextEvent);
CFRelease(thisEvent);
}
@end
The snippet above shows how I'm playing back the events. My implementation is fairly crufty - you'll see I had to fudge around the fact that if I blindly use a timer to schedule the next event sometimes it doesn't fire - seems to be when the delay is too small. The horrible hack you see seems to make things work OK
Anyway, hope this helps someone else in some way.