I spent yesterday trying to get this to work, ended up with a solution. I am not completely happy with this, but it was the best I could do now - if anyone could offer any improvements or a working alternative, I would welcome them ...
Anyway, for someone else trying to do something like that. I based my decision on the API described in detail in this post - I recorded a sequence of events that I wanted to simulate, and then reproduced them. The only problem is that I could not get the built-in playback API to work (I got the same crash mentioned in the comments mentioned below). After some time in the land of ASM, I finished 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 reproduce the events. My implementation is pretty cruel - you will see that I had to stroke the fact that if I blindly use the timer to schedule the next event, then sometimes it does not work - it seems like the delay is too small. The terrible hack you see seems to be doing fine.
In any case, I hope this helps someone.
source share