Closed gocal closed 3 years ago
That sounds like a great idea! I'll keep it in mind when I start rewriting the backend code to spoof the location. To my knowledge there is no API to change the location inside the simulator. But it seems like xcrun
can control the simulator using simctl
. I would prefer not calling command line tools from within swift, but if there is no other way I'll have to do it that way.
It looks like someone already wrote a command line tool for that purpose which internally uses xcrun
: https://github.com/MobileNativeFoundation/set-simulator-location/blob/master/sources/simulators.swift
I played a little bit with the CoreSimulator framework. Detecting devices and changing the location of a simulator device is possible without simctl
. It feels a little bit hacky, since reading the pids for all Simulator.app instances is bad coding style. If anybody knows how to do this without the pid stuff, let me know. I don't know when I have the time to integrate the code into LocationSimulator. If anybody sees this and feels up to the task do the following:
Device
to IOSDevice
Device
SimulatorDevice
IOSDevice
and SimulatorDevice
should implement the Device
protocolSimulatorDevice
based on this helper code.#import <Foundation/Foundation.h>
#import <sys/proc_info.h>
#import <libproc.h>
#include <dlfcn.h>
// This might not be accurate
typedef NS_ENUM(NSUInteger, SimBootState) {
SimBootStatusOffline = 1,
SimBootStatusBooting = 2,
SimBootStatusBooted = 3,
SimBootStatusShutdown = 4
};
@interface SimDevice : NSObject {}
@property(copy, nonatomic) NSUUID *UDID;
@property(readonly, nonatomic) NSString *name;
- (mach_port_t)lookup:(NSString *)portName error:(NSError **)error;
@end
@interface SimDeviceSet : NSObject {}
- (NSUInteger)registerNotificationHandler:(void (^_Nonnull)(NSDictionary *))handler;
@property(readonly, nonatomic) NSArray *availableDevices;
@end
@interface SimServiceContext : NSObject {}
+ (id)serviceContextForDeveloperDir:(id)arg1 error:(NSError **)arg2;
- (SimDeviceSet *)defaultDeviceSetWithError:(NSError **)arg1;
@end
@interface SimulatorBridge : NSObject {}
- (void)setLocationWithLatitude:(double)latitude andLongitude:(double)longitude;
@end
void* load_bundle(NSString * _Nonnull path) {
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(@"WARNING: Bundle is not present at path: %@", path);
return nil;
}
void* fw = dlopen(path.UTF8String, RTLD_NOW | RTLD_GLOBAL);
if (!fw) {
NSLog(@"ERROR: %s", dlerror());
abort();
}
return fw;
}
/**
- Parameter simulatorPath: The path to the Simulator.app
- Return: All possible simulator bridge port names for each Simulator.app instance.
*/
NSArray<NSString *>* getSimulatorPortNames(NSString *simulatorPath) {
NSMutableArray<NSString *> *portNames = [[NSMutableArray alloc] init];
// Get all pids for every process
size_t numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
pid_t pids[numberOfProcesses];
bzero(pids, sizeof(pids));
proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
for (int i = 0; i < numberOfProcesses; ++i) {
if (pids[i] == 0) { continue; }
char pathBuffer[PROC_PIDPATHINFO_MAXSIZE];
bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE);
proc_pidpath(pids[i], pathBuffer, sizeof(pathBuffer));
// If the pid belongs to the Simulator.app
if (strlen(pathBuffer) > 0 && strcmp(simulatorPath.UTF8String, pathBuffer) == 0) {
NSString *name = [NSString stringWithFormat:@"com.apple.iphonesimulator.bridge.%d", pids[i]];
[portNames addObject:name];
}
}
return portNames;
}
SimulatorBridge *bridgeForSimDevice(SimDevice *device, NSString* portName) {
NSError *error = nil;
mach_port_t bridgePort = [device lookup:portName error:&error];
if (error == nil && bridgePort != 0) {
NSPort *bridgeMachPort = [NSMachPort portWithMachPort:bridgePort];
NSConnection *bridgeConnection = [NSConnection connectionWithReceivePort:nil sendPort: bridgeMachPort];
NSDistantObject *bridgeDistantObject = [bridgeConnection rootProxy];
if ([bridgeDistantObject respondsToSelector:@selector(setLocationScenarioWithPath:)]) {
return (SimulatorBridge *) bridgeDistantObject;
} else {
NSLog(@"Distant Object for port: '%@' is not a SimulatorBridge", portName);
}
} else {
NSLog(@"Could not get port for name: '%@'", portName);
}
return nil;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *simulatorPath = @"/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/Contents/MacOS/Simulator";
load_bundle(@"/Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator");
NSString *path = @"/Library/Developer/CommandLineTools";
SimServiceContext* context = [NSClassFromString(@"SimServiceContext") serviceContextForDeveloperDir:path error:NULL];
SimDeviceSet *deviceSet = [context defaultDeviceSetWithError:nil];
// Example of how to change the locaiton for all currently available devices.
NSArray<NSString *>* simualtorPorts = getSimulatorPortNames(simulatorPath);
for (SimDevice *simDevice in deviceSet.availableDevices) {
for (NSString *portName in simualtorPorts) {
SimulatorBridge *bridge = bridgeForSimDevice(simDevice, portName);
if (!bridge) break;
[bridge setLocationWithLatitude:49.828386 andLongitude:6.742060];
NSLog(@"Change location");
}
}
// Listen for new devices
[deviceSet registerNotificationHandler:^(NSDictionary* info) {
NSString *notification_name = info[@"notification"];
if ([notification_name isEqualToString: @"device_state"]) {
SimDevice *device = info[@"device"];
SimBootState new_state = [info[@"new_state"] unsignedIntValue];
if (new_state == SimBootStatusBooted) {
NSLog(@"Added: %@", device);
} else if (new_state == SimBootStatusShutdown) {
NSLog(@"Removed: %@", device);
}
}
}];
while (TRUE);
}
return 0;
}
I made a little progress and implemented a simple Objective-C wrapper around this stuff. But there are still a couple of problems:
I implemented a first version with simulator support. Resetting the location is not possible. Sandbox is still intact, I found a workaround (add com.apple.CoreSimulator to the AppGroup). I changed the detection algorithm, so that the simulator device is detected, when the boot process is finished.
Could you test the new version ? Just download the nightly build.
It would be really handy to add the simulator support.
Simulators can be picked from the side list ("Simulator" section)