serve-sim 0.1.49-beta.141.1 → 0.1.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/Sources/SimCameraInjector/SimCamFakes.m +12 -0
- package/Sources/SimCameraInjector/SimCamMovieOutput.h +17 -0
- package/Sources/SimCameraInjector/SimCamMovieOutput.m +211 -0
- package/Sources/SimCameraInjector/SimCamMovieRecorder.h +30 -0
- package/Sources/SimCameraInjector/SimCamMovieRecorder.m +270 -0
- package/Sources/SimCameraInjector/SimCamSwizzles.h +11 -0
- package/Sources/SimCameraInjector/SimCamSwizzles.m +211 -64
- package/Sources/SimCameraInjector/build.sh +2 -0
- package/Sources/SimNative/DuoHIDBridge.swift +2 -1
- package/Sources/SimNative/FrameCapture.swift +11 -3
- package/Sources/SimNative/HIDInjector.swift +1 -1
- package/Sources/SimNative/sim-module.swift +5 -0
- package/Sources/SimNativeSupport/DeviceSet.swift +34 -0
- package/Tests/SimNativeSupportTests/DeviceSetTests.swift +57 -0
- package/dist/middleware.js +49 -49
- package/dist/native/serve-sim-native.node +0 -0
- package/dist/serve-sim.js +83 -82
- package/dist/simcam/libSimCameraInjector.dylib +0 -0
- package/package.json +1 -1
- package/src/device-hub-input.ts +4 -3
- package/src/device.ts +15 -3
- package/src/doctor.ts +2 -1
- package/src/duo-state.ts +4 -3
- package/src/event-log.ts +4 -2
- package/src/helper-lifecycle.ts +6 -1
- package/src/host-rpc.ts +9 -8
- package/src/middleware.ts +22 -14
- package/src/native.ts +8 -1
- package/src/simctl.ts +83 -2
- package/src/state.ts +9 -0
- package/src/ui-settings.ts +3 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "SimCamFakes.h"
|
|
3
3
|
#import "SimCamFrameSource.h"
|
|
4
4
|
#import "SimCamLog.h"
|
|
5
|
+
#import "SimCamMovieOutput.h"
|
|
5
6
|
|
|
6
7
|
#import <AVFoundation/AVFoundation.h>
|
|
7
8
|
#import <CoreImage/CoreImage.h>
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
#include <stdatomic.h>
|
|
15
16
|
#include <execinfo.h>
|
|
16
17
|
#include <dlfcn.h>
|
|
18
|
+
#include <mach-o/getsect.h>
|
|
17
19
|
#include <string.h>
|
|
18
20
|
|
|
19
21
|
#pragma mark - Swizzling helpers
|
|
@@ -35,7 +37,7 @@ static BOOL SwizzleClassMethod(Class cls, SEL orig, SEL swiz) {
|
|
|
35
37
|
method_exchangeImplementations(o, s);
|
|
36
38
|
return YES;
|
|
37
39
|
}
|
|
38
|
-
|
|
40
|
+
BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
|
|
39
41
|
Method o = class_getInstanceMethod(cls, orig);
|
|
40
42
|
Method s = class_getInstanceMethod(cls, swiz);
|
|
41
43
|
if (!o) {
|
|
@@ -120,6 +122,13 @@ static BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
|
|
|
120
122
|
}
|
|
121
123
|
return [self simcam_defaultDeviceWithDeviceType:t mediaType:m position:p];
|
|
122
124
|
}
|
|
125
|
+
+ (AVCaptureDevice *)simcam_defaultDeviceWithMediaType:(AVMediaType)m {
|
|
126
|
+
if ([m isEqualToString:AVMediaTypeVideo]) {
|
|
127
|
+
simcam_log(@"defaultDeviceWithMediaType: video → fake back camera");
|
|
128
|
+
return SimCamFakeDeviceForPosition(AVCaptureDevicePositionBack);
|
|
129
|
+
}
|
|
130
|
+
return [self simcam_defaultDeviceWithMediaType:m];
|
|
131
|
+
}
|
|
123
132
|
+ (NSArray<AVCaptureDevice *> *)simcam_devicesWithMediaType:(AVMediaType)m {
|
|
124
133
|
if ([m isEqualToString:AVMediaTypeVideo]) {
|
|
125
134
|
return @[
|
|
@@ -197,12 +206,53 @@ static BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
|
|
|
197
206
|
|
|
198
207
|
#pragma mark - AVCaptureSession swizzles
|
|
199
208
|
|
|
209
|
+
// AVFoundation reads a session's and an output's own lists when it rebuilds
|
|
210
|
+
// the native graph (a preview layer attached or detached, a configuration
|
|
211
|
+
// committed), and the fake objects have none of the private state it reads
|
|
212
|
+
// there. So a getter whose caller is AVFoundation returns the native list
|
|
213
|
+
// only, and the app sees the tracked objects too.
|
|
214
|
+
//
|
|
215
|
+
// The caller is told apart by its return address: AVFoundation's call when
|
|
216
|
+
// it falls in the __TEXT of the image that defines AVCaptureSession
|
|
217
|
+
// (AVFCapture on current iOS), resolved once at install. This assumes every
|
|
218
|
+
// AVFoundation-internal reader of these lists lives in that image and calls
|
|
219
|
+
// the getter directly. A reader in another image (AVFCore, CMCapture, a Swift
|
|
220
|
+
// overlay), or one that reaches the getter through a tail call, a trampoline
|
|
221
|
+
// or key-value coding, gets the fakes and crashes in their missing private
|
|
222
|
+
// state as before. The install log names the image and its range, and a
|
|
223
|
+
// getter logs each time it answers AVFoundation, so such a crash shows which
|
|
224
|
+
// reads were told apart before it.
|
|
225
|
+
static uintptr_t gSimCamCaptureTextStart;
|
|
226
|
+
static uintptr_t gSimCamCaptureTextSize;
|
|
227
|
+
|
|
228
|
+
static void SimCamRecordCaptureImage(void) {
|
|
229
|
+
Dl_info info;
|
|
230
|
+
if (!dladdr((__bridge const void *)[AVCaptureSession class], &info)) {
|
|
231
|
+
simcam_log(@"caller check: no image for AVCaptureSession; AVFoundation will read the fakes");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
unsigned long size = 0;
|
|
235
|
+
uint8_t *text = getsegmentdata((const struct mach_header_64 *)info.dli_fbase, "__TEXT", &size);
|
|
236
|
+
gSimCamCaptureTextStart = (uintptr_t)text;
|
|
237
|
+
gSimCamCaptureTextSize = size;
|
|
238
|
+
simcam_log(@"caller check: AVFoundation's reads come from %s __TEXT [%p, +0x%lx)",
|
|
239
|
+
info.dli_fname, text, size);
|
|
240
|
+
}
|
|
241
|
+
static BOOL SimCamCalledFromCaptureImage(const void *returnAddress, id receiver, SEL selector) {
|
|
242
|
+
if ((uintptr_t)returnAddress - gSimCamCaptureTextStart >= gSimCamCaptureTextSize) return NO;
|
|
243
|
+
simcam_log(@"caller check: -[%@ %@] answered AVFoundation with the native list only",
|
|
244
|
+
NSStringFromClass([receiver class]), NSStringFromSelector(selector));
|
|
245
|
+
return YES;
|
|
246
|
+
}
|
|
247
|
+
|
|
200
248
|
static char kSimCamSessionRunningKey;
|
|
201
249
|
static char kSimCamSessionInputsKey;
|
|
202
250
|
static char kSimCamSessionOutputsKey;
|
|
203
251
|
static char kSimCamSessionConnectionsKey;
|
|
252
|
+
static char kSimCamSessionSentinelKey;
|
|
204
253
|
static char kSimCamOutputAttachedToFakeSessionKey;
|
|
205
254
|
static char kSimCamOutputSessionKey;
|
|
255
|
+
static char kSimCamOutputOwnerKey;
|
|
206
256
|
static char kSimCamMaxPhotoDimensionsKey;
|
|
207
257
|
|
|
208
258
|
static NSMutableArray *SimCamSessionTrackedInputs(AVCaptureSession *s) {
|
|
@@ -213,11 +263,62 @@ static NSMutableArray *SimCamSessionTrackedInputs(AVCaptureSession *s) {
|
|
|
213
263
|
}
|
|
214
264
|
return arr;
|
|
215
265
|
}
|
|
266
|
+
// AVFoundation keeps an output in one session at a time: canAddOutput: says
|
|
267
|
+
// NO while another session holds it, and adding it throws. So one session's
|
|
268
|
+
// teardown, stop or release never reaches another's recording. Sessions may
|
|
269
|
+
// be configured on different queues, so a change of owner holds the output's
|
|
270
|
+
// lock.
|
|
271
|
+
static AVCaptureSession *SimCamOutputOwner(AVCaptureOutput *output) {
|
|
272
|
+
return ((SimCamWeakRef *)objc_getAssociatedObject(output, &kSimCamOutputOwnerKey)).target;
|
|
273
|
+
}
|
|
274
|
+
static void SimCamTakeOutput(AVCaptureSession *s, AVCaptureOutput *output, SEL adding) {
|
|
275
|
+
@synchronized (output) {
|
|
276
|
+
AVCaptureSession *owner = SimCamOutputOwner(output);
|
|
277
|
+
if (owner == s) return;
|
|
278
|
+
if (owner) {
|
|
279
|
+
[NSException raise:NSInvalidArgumentException
|
|
280
|
+
format:@"*** -[AVCaptureSession %@] An AVCaptureOutput instance may not be added to more than one session",
|
|
281
|
+
NSStringFromSelector(adding)];
|
|
282
|
+
}
|
|
283
|
+
SimCamWeakRef *ref = [SimCamWeakRef new];
|
|
284
|
+
ref.target = s;
|
|
285
|
+
objc_setAssociatedObject(output, &kSimCamOutputOwnerKey, ref, OBJC_ASSOCIATION_RETAIN);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// For a session that stopped or went away: the fake recordings of the outputs
|
|
289
|
+
// it still owns end as a stop. A deallocating session reads as nil through an
|
|
290
|
+
// output's weak owner, so the sentinel passes nil, and an output another
|
|
291
|
+
// session took meanwhile keeps its recording.
|
|
292
|
+
static void SimCamStopOutputRecordings(NSArray<AVCaptureOutput *> *outputs, AVCaptureSession *session) {
|
|
293
|
+
for (AVCaptureOutput *output in [outputs copy]) {
|
|
294
|
+
@synchronized (output) {
|
|
295
|
+
if (SimCamOutputOwner(output) == session) SimCamFakeMovieOutputSessionStopped(output);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Associated with a session beside its tracked outputs, so it goes when the
|
|
301
|
+
// session does: a session the app lets go without stopping it ends its fake
|
|
302
|
+
// recordings, as AVFoundation finishes the file when the session goes away.
|
|
303
|
+
// It holds the outputs itself, since the session's other associated objects
|
|
304
|
+
// may be released first, and never messages the session.
|
|
305
|
+
@interface SimCamSessionSentinel : NSObject
|
|
306
|
+
@property (nonatomic, strong) NSMutableArray<AVCaptureOutput *> *outputs;
|
|
307
|
+
@end
|
|
308
|
+
@implementation SimCamSessionSentinel
|
|
309
|
+
- (void)dealloc {
|
|
310
|
+
SimCamStopOutputRecordings(_outputs, nil);
|
|
311
|
+
}
|
|
312
|
+
@end
|
|
313
|
+
|
|
216
314
|
static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
217
315
|
NSMutableArray *arr = objc_getAssociatedObject(s, &kSimCamSessionOutputsKey);
|
|
218
316
|
if (!arr) {
|
|
219
317
|
arr = [NSMutableArray new];
|
|
220
318
|
objc_setAssociatedObject(s, &kSimCamSessionOutputsKey, arr, OBJC_ASSOCIATION_RETAIN);
|
|
319
|
+
SimCamSessionSentinel *sentinel = [SimCamSessionSentinel new];
|
|
320
|
+
sentinel.outputs = arr;
|
|
321
|
+
objc_setAssociatedObject(s, &kSimCamSessionSentinelKey, sentinel, OBJC_ASSOCIATION_RETAIN);
|
|
221
322
|
}
|
|
222
323
|
return arr;
|
|
223
324
|
}
|
|
@@ -242,6 +343,7 @@ static AVCaptureInput *SimCamFirstFakeInputForSession(AVCaptureSession *s) {
|
|
|
242
343
|
// look disconnected during client-side session configuration checks.
|
|
243
344
|
static void SimCamUnmarkOutputAttachedToFakeSession(AVCaptureOutput *output) {
|
|
244
345
|
if (!output) return;
|
|
346
|
+
SimCamFakeMovieOutputLeftSession(output);
|
|
245
347
|
objc_setAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
246
348
|
objc_setAssociatedObject(output, &kSimCamOutputSessionKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
247
349
|
objc_setAssociatedObject(output, &kSimCamMaxPhotoDimensionsKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
@@ -260,7 +362,7 @@ static void SimCamMarkOutputAttachedToFakeSession(AVCaptureSession *s, AVCapture
|
|
|
260
362
|
objc_setAssociatedObject(output, &kSimCamOutputSessionKey, sessionRef, OBJC_ASSOCIATION_RETAIN);
|
|
261
363
|
SimCamSetOutputInput(output, input);
|
|
262
364
|
}
|
|
263
|
-
|
|
365
|
+
BOOL SimCamOutputAttachedToFakeSession(AVCaptureOutput *output) {
|
|
264
366
|
if (!output) return NO;
|
|
265
367
|
return [objc_getAssociatedObject(output, &kSimCamOutputAttachedToFakeSessionKey) boolValue];
|
|
266
368
|
}
|
|
@@ -269,7 +371,7 @@ static void SimCamRefreshAttachedOutputInputsForSession(AVCaptureSession *s) {
|
|
|
269
371
|
SimCamMarkOutputAttachedToFakeSession(s, output);
|
|
270
372
|
}
|
|
271
373
|
}
|
|
272
|
-
|
|
374
|
+
AVCaptureSession *SimCamFakeSessionForOutput(AVCaptureOutput *output) {
|
|
273
375
|
SimCamWeakRef *ref = objc_getAssociatedObject(output, &kSimCamOutputSessionKey);
|
|
274
376
|
return ref.target;
|
|
275
377
|
}
|
|
@@ -279,9 +381,22 @@ static BOOL SimCamConnectionUsesInput(AVCaptureConnection *connection, AVCapture
|
|
|
279
381
|
}
|
|
280
382
|
return NO;
|
|
281
383
|
}
|
|
282
|
-
|
|
384
|
+
// A connection the session keeps itself: one from a fake input, or from an
|
|
385
|
+
// input the session tracks instead of adding natively (the microphone beside
|
|
386
|
+
// a fake camera).
|
|
387
|
+
static BOOL SimCamConnectionUsesTrackedInput(
|
|
388
|
+
AVCaptureConnection *connection,
|
|
389
|
+
AVCaptureSession *session
|
|
390
|
+
) {
|
|
391
|
+
NSArray<AVCaptureInput *> *inputs = SimCamSessionTrackedInputs(session);
|
|
392
|
+
for (AVCaptureInputPort *port in connection.inputPorts) {
|
|
393
|
+
if (SimCamIsFakeInput(port.input) || [inputs containsObject:port.input]) return YES;
|
|
394
|
+
}
|
|
395
|
+
return NO;
|
|
396
|
+
}
|
|
397
|
+
BOOL SimCamConnectionCarries(AVCaptureConnection *connection, AVMediaType mediaType) {
|
|
283
398
|
for (AVCaptureInputPort *port in connection.inputPorts) {
|
|
284
|
-
if (
|
|
399
|
+
if ([port.mediaType isEqualToString:mediaType]) return YES;
|
|
285
400
|
}
|
|
286
401
|
return NO;
|
|
287
402
|
}
|
|
@@ -298,7 +413,7 @@ static BOOL SimCamConnectionBelongsToSession(
|
|
|
298
413
|
AVCaptureOutput *output = connection.output;
|
|
299
414
|
return !output || [SimCamSessionTrackedOutputs(session) containsObject:output];
|
|
300
415
|
}
|
|
301
|
-
|
|
416
|
+
NSArray<AVCaptureConnection *> *SimCamConnectionsForOutput(AVCaptureOutput *output) {
|
|
302
417
|
AVCaptureSession *session = SimCamFakeSessionForOutput(output);
|
|
303
418
|
if (!session) return @[];
|
|
304
419
|
NSMutableArray *result = [NSMutableArray new];
|
|
@@ -332,60 +447,59 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
332
447
|
}
|
|
333
448
|
}
|
|
334
449
|
|
|
450
|
+
// startRunning never reaches AVFoundation, so no session in the injected
|
|
451
|
+
// process runs a native graph, and every input is tracked rather than added
|
|
452
|
+
// natively. A native input, such as expo-camera's microphone, would make the
|
|
453
|
+
// session build its graph, which reads the fake connections' private state
|
|
454
|
+
// and crashes.
|
|
455
|
+
static void SimCamTrackInput(AVCaptureSession *s, AVCaptureInput *input,
|
|
456
|
+
BOOL automaticConnections, NSString *via) {
|
|
457
|
+
NSMutableArray *tracked = SimCamSessionTrackedInputs(s);
|
|
458
|
+
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
459
|
+
if (!SimCamIsFakeInput(input)) {
|
|
460
|
+
simcam_log(@"%@: %@ — tracked (count=%lu), skipping native add",
|
|
461
|
+
via, NSStringFromClass([input class]), (unsigned long)tracked.count);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
AVCaptureDevicePosition p = SimCamPositionOf(input);
|
|
465
|
+
SimCamSetPosition(s, p);
|
|
466
|
+
SimCamMarkCameraInUse();
|
|
467
|
+
SimCamMarkSessionUsingFakeCamera(s, YES);
|
|
468
|
+
SimCamRefreshAttachedOutputInputsForSession(s);
|
|
469
|
+
if (automaticConnections) SimCamAddAutomaticConnections(s);
|
|
470
|
+
simcam_log(@"%@: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
471
|
+
via, p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
472
|
+
(unsigned long)tracked.count);
|
|
473
|
+
}
|
|
474
|
+
|
|
335
475
|
@interface AVCaptureSession (SimCam)
|
|
336
476
|
@end
|
|
337
477
|
@implementation AVCaptureSession (SimCam)
|
|
338
478
|
- (void)simcam_addInput:(AVCaptureInput *)input {
|
|
339
|
-
|
|
340
|
-
AVCaptureDevicePosition p = SimCamPositionOf(input);
|
|
341
|
-
SimCamSetPosition(self, p);
|
|
342
|
-
SimCamMarkCameraInUse();
|
|
343
|
-
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
344
|
-
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
345
|
-
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
346
|
-
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
347
|
-
SimCamAddAutomaticConnections(self);
|
|
348
|
-
simcam_log(@"addInput: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
349
|
-
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
350
|
-
(unsigned long)tracked.count);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
[self simcam_addInput:input];
|
|
479
|
+
SimCamTrackInput(self, input, YES, @"addInput");
|
|
354
480
|
}
|
|
355
481
|
- (BOOL)simcam_canAddInput:(AVCaptureInput *)input {
|
|
356
482
|
if (SimCamIsFakeInput(input)) return YES;
|
|
357
483
|
return [self simcam_canAddInput:input];
|
|
358
484
|
}
|
|
359
485
|
- (void)simcam_addInputWithNoConnections:(AVCaptureInput *)input {
|
|
360
|
-
|
|
361
|
-
AVCaptureDevicePosition p = SimCamPositionOf(input);
|
|
362
|
-
SimCamSetPosition(self, p);
|
|
363
|
-
SimCamMarkCameraInUse();
|
|
364
|
-
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
365
|
-
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
366
|
-
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
367
|
-
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
368
|
-
simcam_log(@"addInputWithNoConnections: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
369
|
-
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
370
|
-
(unsigned long)tracked.count);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
[self simcam_addInputWithNoConnections:input];
|
|
486
|
+
SimCamTrackInput(self, input, NO, @"addInputWithNoConnections");
|
|
374
487
|
}
|
|
375
488
|
- (void)simcam_removeInput:(AVCaptureInput *)input {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
[tracked removeObject:input];
|
|
380
|
-
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
381
|
-
if (tracked.count == 0) SimCamMarkSessionUsingFakeCamera(self, NO);
|
|
382
|
-
simcam_log(@"removeInput: fake input — untracked (count=%lu)",
|
|
383
|
-
(unsigned long)tracked.count);
|
|
489
|
+
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
490
|
+
if (![tracked containsObject:input]) {
|
|
491
|
+
[self simcam_removeInput:input];
|
|
384
492
|
return;
|
|
385
493
|
}
|
|
386
|
-
|
|
494
|
+
SimCamRemoveConnectionsForInput(self, input);
|
|
495
|
+
[tracked removeObject:input];
|
|
496
|
+
SimCamRefreshAttachedOutputInputsForSession(self);
|
|
497
|
+
if (!SimCamFirstFakeInputForSession(self)) SimCamMarkSessionUsingFakeCamera(self, NO);
|
|
498
|
+
simcam_log(@"removeInput: %@ — untracked (count=%lu)",
|
|
499
|
+
NSStringFromClass([input class]), (unsigned long)tracked.count);
|
|
387
500
|
}
|
|
388
501
|
- (void)simcam_addOutput:(AVCaptureOutput *)output {
|
|
502
|
+
SimCamTakeOutput(self, output, _cmd);
|
|
389
503
|
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
390
504
|
SimCamMarkCameraInUse();
|
|
391
505
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
@@ -399,8 +513,12 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
399
513
|
(unsigned long)tracked.count,
|
|
400
514
|
(int)SimCamPositionOf(self));
|
|
401
515
|
}
|
|
402
|
-
- (BOOL)simcam_canAddOutput:(AVCaptureOutput *)output {
|
|
516
|
+
- (BOOL)simcam_canAddOutput:(AVCaptureOutput *)output {
|
|
517
|
+
AVCaptureSession *owner = SimCamOutputOwner(output);
|
|
518
|
+
return !owner || owner == self;
|
|
519
|
+
}
|
|
403
520
|
- (void)simcam_addOutputWithNoConnections:(AVCaptureOutput *)output {
|
|
521
|
+
SimCamTakeOutput(self, output, _cmd);
|
|
404
522
|
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
405
523
|
SimCamMarkCameraInUse();
|
|
406
524
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
@@ -412,8 +530,14 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
412
530
|
(int)SimCamPositionOf(self));
|
|
413
531
|
}
|
|
414
532
|
- (void)simcam_removeOutput:(AVCaptureOutput *)output {
|
|
415
|
-
|
|
416
|
-
|
|
533
|
+
// The owner goes last, so no other session takes the output until it is
|
|
534
|
+
// unmarked here.
|
|
535
|
+
@synchronized (output) {
|
|
536
|
+
if (SimCamOutputOwner(output) != self) return;
|
|
537
|
+
SimCamRemoveConnectionsForOutput(self, output);
|
|
538
|
+
SimCamUnmarkOutputAttachedToFakeSession(output);
|
|
539
|
+
objc_setAssociatedObject(output, &kSimCamOutputOwnerKey, nil, OBJC_ASSOCIATION_RETAIN);
|
|
540
|
+
}
|
|
417
541
|
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
418
542
|
[tracked removeObject:output];
|
|
419
543
|
simcam_log(@"removeOutput: %@ — untracked (count=%lu)",
|
|
@@ -427,18 +551,18 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
427
551
|
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionInputsKey)).count;
|
|
428
552
|
NSUInteger outCount =
|
|
429
553
|
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionOutputsKey)).count;
|
|
430
|
-
simcam_log(@"commitConfiguration intercepted (session=%p,
|
|
554
|
+
simcam_log(@"commitConfiguration intercepted (session=%p, trackedInputs=%lu, trackedOutputs=%lu)",
|
|
431
555
|
self, (unsigned long)inCount, (unsigned long)outCount);
|
|
432
556
|
}
|
|
433
557
|
- (BOOL)simcam_canAddConnection:(AVCaptureConnection *)connection {
|
|
434
|
-
if (
|
|
558
|
+
if (SimCamConnectionUsesTrackedInput(connection, self)) {
|
|
435
559
|
return SimCamConnectionBelongsToSession(connection, self) &&
|
|
436
560
|
![SimCamSessionTrackedConnections(self) containsObject:connection];
|
|
437
561
|
}
|
|
438
562
|
return [self simcam_canAddConnection:connection];
|
|
439
563
|
}
|
|
440
564
|
- (void)simcam_addConnection:(AVCaptureConnection *)c {
|
|
441
|
-
if (!
|
|
565
|
+
if (!SimCamConnectionUsesTrackedInput(c, self)) {
|
|
442
566
|
[self simcam_addConnection:c];
|
|
443
567
|
return;
|
|
444
568
|
}
|
|
@@ -448,19 +572,20 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
448
572
|
}
|
|
449
573
|
AVCaptureOutput *output = c.output;
|
|
450
574
|
SimCamRecordConnection(self, c);
|
|
451
|
-
simcam_log(@"addConnection:
|
|
575
|
+
simcam_log(@"addConnection: tracked input (session=%p, conn=%p, output=%p)", self, c, output);
|
|
452
576
|
}
|
|
453
577
|
- (void)simcam_removeConnection:(AVCaptureConnection *)c {
|
|
454
|
-
if (!
|
|
578
|
+
if (!SimCamConnectionUsesTrackedInput(c, self)) {
|
|
455
579
|
[self simcam_removeConnection:c];
|
|
456
580
|
return;
|
|
457
581
|
}
|
|
458
582
|
[SimCamSessionTrackedConnections(self) removeObject:c];
|
|
459
|
-
simcam_log(@"removeConnection:
|
|
583
|
+
simcam_log(@"removeConnection: tracked input (session=%p, conn=%p, output=%p)", self, c, c.output);
|
|
460
584
|
}
|
|
461
585
|
- (NSArray<AVCaptureInput *> *)simcam_inputs {
|
|
462
|
-
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionInputsKey);
|
|
463
586
|
NSArray *native = [self simcam_inputs];
|
|
587
|
+
if (SimCamCalledFromCaptureImage(__builtin_return_address(0), self, _cmd)) return native;
|
|
588
|
+
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionInputsKey);
|
|
464
589
|
if (tracked.count == 0) return native ?: @[];
|
|
465
590
|
if (native.count == 0) return [tracked copy];
|
|
466
591
|
NSMutableArray *merged = [tracked mutableCopy];
|
|
@@ -470,8 +595,9 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
470
595
|
return [merged copy];
|
|
471
596
|
}
|
|
472
597
|
- (NSArray<AVCaptureOutput *> *)simcam_outputs {
|
|
473
|
-
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionOutputsKey);
|
|
474
598
|
NSArray *native = [self simcam_outputs];
|
|
599
|
+
if (SimCamCalledFromCaptureImage(__builtin_return_address(0), self, _cmd)) return native;
|
|
600
|
+
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionOutputsKey);
|
|
475
601
|
if (tracked.count == 0) return native ?: @[];
|
|
476
602
|
if (native.count == 0) return [tracked copy];
|
|
477
603
|
NSMutableArray *merged = [tracked mutableCopy];
|
|
@@ -481,8 +607,9 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
481
607
|
return [merged copy];
|
|
482
608
|
}
|
|
483
609
|
- (NSArray<AVCaptureConnection *> *)simcam_connections {
|
|
484
|
-
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionConnectionsKey);
|
|
485
610
|
NSArray *native = [self simcam_connections];
|
|
611
|
+
if (SimCamCalledFromCaptureImage(__builtin_return_address(0), self, _cmd)) return native;
|
|
612
|
+
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionConnectionsKey);
|
|
486
613
|
if (tracked.count == 0) return native ?: @[];
|
|
487
614
|
NSMutableArray *merged = [tracked mutableCopy];
|
|
488
615
|
for (AVCaptureConnection *n in native) {
|
|
@@ -497,7 +624,7 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
497
624
|
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionInputsKey)).count;
|
|
498
625
|
NSUInteger outCount =
|
|
499
626
|
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionOutputsKey)).count;
|
|
500
|
-
simcam_log(@"startRunning intercepted (
|
|
627
|
+
simcam_log(@"startRunning intercepted (tracked inputs=%lu outputs=%lu)",
|
|
501
628
|
(unsigned long)inCount, (unsigned long)outCount);
|
|
502
629
|
[[SimCamRegistry shared] startPumpingIfNeeded];
|
|
503
630
|
[self willChangeValueForKey:@"running"];
|
|
@@ -511,6 +638,7 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
511
638
|
}
|
|
512
639
|
- (void)simcam_stopRunning {
|
|
513
640
|
objc_setAssociatedObject(self, &kSimCamSessionRunningKey, @NO, OBJC_ASSOCIATION_RETAIN);
|
|
641
|
+
SimCamStopOutputRecordings(objc_getAssociatedObject(self, &kSimCamSessionOutputsKey), self);
|
|
514
642
|
simcam_log(@"stopRunning intercepted");
|
|
515
643
|
[self willChangeValueForKey:@"running"];
|
|
516
644
|
[self didChangeValueForKey:@"running"];
|
|
@@ -547,15 +675,22 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
547
675
|
|
|
548
676
|
#pragma mark - AVCaptureVideoPreviewLayer swizzle
|
|
549
677
|
|
|
678
|
+
static void SimCamRegisterPreviewLayer(AVCaptureVideoPreviewLayer *layer, AVCaptureSession *session) {
|
|
679
|
+
SimCamSetPosition(layer, SimCamPositionOf(session));
|
|
680
|
+
SimCamMarkCameraInUse();
|
|
681
|
+
[[SimCamRegistry shared] addPreviewLayer:layer];
|
|
682
|
+
}
|
|
683
|
+
|
|
550
684
|
@interface AVCaptureVideoPreviewLayer (SimCam)
|
|
551
685
|
@end
|
|
552
686
|
@implementation AVCaptureVideoPreviewLayer (SimCam)
|
|
553
687
|
- (void)simcam_setSession:(AVCaptureSession *)session {
|
|
554
688
|
[self simcam_setSession:session];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
[
|
|
689
|
+
SimCamRegisterPreviewLayer(self, session);
|
|
690
|
+
}
|
|
691
|
+
- (void)simcam_setSessionWithNoConnection:(AVCaptureSession *)session {
|
|
692
|
+
[self simcam_setSessionWithNoConnection:session];
|
|
693
|
+
SimCamRegisterPreviewLayer(self, session);
|
|
559
694
|
}
|
|
560
695
|
@end
|
|
561
696
|
|
|
@@ -581,16 +716,19 @@ static void SimCamAddAutomaticConnections(AVCaptureSession *session) {
|
|
|
581
716
|
@implementation AVCaptureOutput (SimCamConn)
|
|
582
717
|
- (AVCaptureConnection *)simcam_connectionWithMediaType:(AVMediaType)mediaType {
|
|
583
718
|
AVCaptureConnection *real = [self simcam_connectionWithMediaType:mediaType];
|
|
584
|
-
if (real) return real;
|
|
719
|
+
if (real || SimCamCalledFromCaptureImage(__builtin_return_address(0), self, _cmd)) return real;
|
|
585
720
|
if (!SimCamOutputAttachedToFakeSession(self)) return nil;
|
|
586
|
-
|
|
587
|
-
AVCaptureConnection *
|
|
721
|
+
AVCaptureConnection *connection = nil;
|
|
722
|
+
for (AVCaptureConnection *candidate in SimCamConnectionsForOutput(self)) {
|
|
723
|
+
if (SimCamConnectionCarries(candidate, mediaType)) { connection = candidate; break; }
|
|
724
|
+
}
|
|
588
725
|
simcam_log(@"connectionWithMediaType:%@ → tracked %p for %@ %p",
|
|
589
726
|
mediaType, connection, NSStringFromClass([self class]), self);
|
|
590
727
|
return connection;
|
|
591
728
|
}
|
|
592
729
|
- (NSArray<AVCaptureConnection *> *)simcam_connections {
|
|
593
730
|
NSArray *real = [self simcam_connections];
|
|
731
|
+
if (SimCamCalledFromCaptureImage(__builtin_return_address(0), self, _cmd)) return real;
|
|
594
732
|
if (!SimCamOutputAttachedToFakeSession(self)) return real ?: @[];
|
|
595
733
|
NSArray *tracked = SimCamConnectionsForOutput(self);
|
|
596
734
|
if (tracked.count == 0) return real ?: @[];
|
|
@@ -1071,10 +1209,14 @@ static void InstallCoreMotionSwizzles(void) {
|
|
|
1071
1209
|
static void SimCamInstallPickerSwizzles(void); // defined below
|
|
1072
1210
|
|
|
1073
1211
|
void SimCamInstallSwizzles(void) {
|
|
1212
|
+
SimCamRecordCaptureImage();
|
|
1074
1213
|
Class dev = [AVCaptureDevice class];
|
|
1075
1214
|
SwizzleClassMethod(dev,
|
|
1076
1215
|
@selector(defaultDeviceWithDeviceType:mediaType:position:),
|
|
1077
1216
|
@selector(simcam_defaultDeviceWithDeviceType:mediaType:position:));
|
|
1217
|
+
SwizzleClassMethod(dev,
|
|
1218
|
+
@selector(defaultDeviceWithMediaType:),
|
|
1219
|
+
@selector(simcam_defaultDeviceWithMediaType:));
|
|
1078
1220
|
SwizzleClassMethod(dev,
|
|
1079
1221
|
@selector(devicesWithMediaType:),
|
|
1080
1222
|
@selector(simcam_devicesWithMediaType:));
|
|
@@ -1166,6 +1308,9 @@ void SimCamInstallSwizzles(void) {
|
|
|
1166
1308
|
|
|
1167
1309
|
Class pl = [AVCaptureVideoPreviewLayer class];
|
|
1168
1310
|
SwizzleInstanceMethod(pl, @selector(setSession:), @selector(simcam_setSession:));
|
|
1311
|
+
SwizzleInstanceMethod(pl,
|
|
1312
|
+
@selector(setSessionWithNoConnection:),
|
|
1313
|
+
@selector(simcam_setSessionWithNoConnection:));
|
|
1169
1314
|
|
|
1170
1315
|
Class fmtClass = [AVCaptureDeviceFormat class];
|
|
1171
1316
|
SEL figFmtSel = NSSelectorFromString(@"figCaptureSourceVideoFormat");
|
|
@@ -1201,6 +1346,8 @@ void SimCamInstallSwizzles(void) {
|
|
|
1201
1346
|
@selector(capturePhotoWithSettings:delegate:),
|
|
1202
1347
|
@selector(simcam_capturePhotoWithSettings:delegate:));
|
|
1203
1348
|
|
|
1349
|
+
SimCamInstallMovieOutputSwizzles();
|
|
1350
|
+
|
|
1204
1351
|
Class data = [NSData class];
|
|
1205
1352
|
SwizzleInstanceMethod(data,
|
|
1206
1353
|
@selector(writeToURL:options:error:),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Darwin
|
|
3
|
+
import SimNativeSupport
|
|
3
4
|
|
|
4
5
|
/// A lazily started guest service. Calls are serialized by HIDInjector's actor;
|
|
5
6
|
/// replies acknowledge dispatch, so a failed command never switches capture.
|
|
@@ -34,7 +35,7 @@ final class DuoHIDBridge {
|
|
|
34
35
|
let child = Process()
|
|
35
36
|
let stdin = Pipe(), stdout = Pipe()
|
|
36
37
|
child.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
|
|
37
|
-
child.arguments = ["
|
|
38
|
+
child.arguments = DeviceSet.simctlArguments(["spawn", udid, executable])
|
|
38
39
|
child.standardInput = stdin
|
|
39
40
|
child.standardOutput = stdout
|
|
40
41
|
child.standardError = FileHandle.standardError
|
|
@@ -592,9 +592,17 @@ actor FrameCapture {
|
|
|
592
592
|
let sharedSel = NSSelectorFromString("sharedServiceContextForDeveloperDir:error:")
|
|
593
593
|
guard let context = contextClass.perform(sharedSel, with: developerDir, with: nil)?
|
|
594
594
|
.takeUnretainedValue() as? NSObject else { return nil }
|
|
595
|
-
let
|
|
596
|
-
|
|
597
|
-
|
|
595
|
+
guard let deviceSet = DeviceSet.lookUp({ selectorName, path -> NSObject? in
|
|
596
|
+
let deviceSetSel = NSSelectorFromString(selectorName)
|
|
597
|
+
guard context.responds(to: deviceSetSel) else { return nil }
|
|
598
|
+
let deviceSetResult: Unmanaged<AnyObject>?
|
|
599
|
+
if let path {
|
|
600
|
+
deviceSetResult = context.perform(deviceSetSel, with: path, with: nil)
|
|
601
|
+
} else {
|
|
602
|
+
deviceSetResult = context.perform(deviceSetSel, with: nil)
|
|
603
|
+
}
|
|
604
|
+
return deviceSetResult?.takeUnretainedValue() as? NSObject
|
|
605
|
+
}) else { return nil }
|
|
598
606
|
guard let devices = deviceSet.value(forKey: "devices") as? [NSObject] else { return nil }
|
|
599
607
|
return devices.first(where: {
|
|
600
608
|
($0.value(forKey: "UDID") as? NSUUID)?.uuidString == udid
|
|
@@ -621,7 +621,7 @@ actor HIDInjector {
|
|
|
621
621
|
private func launchSpringBoard(deviceUDID: String) {
|
|
622
622
|
let process = Process()
|
|
623
623
|
process.executableURL = URL(fileURLWithPath: "/usr/bin/xcrun")
|
|
624
|
-
process.arguments = ["
|
|
624
|
+
process.arguments = DeviceSet.simctlArguments(["launch", deviceUDID, "com.apple.springboard"])
|
|
625
625
|
try? process.run()
|
|
626
626
|
}
|
|
627
627
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import NodeAPI
|
|
3
|
+
import SimNativeSupport
|
|
3
4
|
|
|
4
5
|
/// Safe Int→UInt32 for HID codes coming from JS. Plain `UInt32(x)` traps on
|
|
5
6
|
/// negative or too-large values, which would crash the in-process server on
|
|
@@ -278,6 +279,10 @@ private func axQuery(
|
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
#NodeModule(exports: [
|
|
282
|
+
// setDeviceSet(path | null): the `--device-set` lookups and simctl calls use.
|
|
283
|
+
"setDeviceSet": try NodeFunction { (path: String?) in
|
|
284
|
+
DeviceSet.configure(path: path)
|
|
285
|
+
},
|
|
281
286
|
"SimHID": SimHID.deferredConstructor,
|
|
282
287
|
"SimCapture": SimCapture.deferredConstructor,
|
|
283
288
|
// axDescribe(udid): Promise<string> — axe-shaped accessibility JSON.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// The CoreSimulator device set serve-sim targets: Xcode's default set, or
|
|
4
|
+
/// the one `serve-sim --device-set` names (`simctl --set`). The JS side
|
|
5
|
+
/// resolves it once and hands it to the addon with `setDeviceSet` as it
|
|
6
|
+
/// loads the addon, before any capture, input or accessibility call.
|
|
7
|
+
public enum DeviceSet {
|
|
8
|
+
private static let lock = NSLock()
|
|
9
|
+
nonisolated(unsafe) private static var configuredPath: String?
|
|
10
|
+
|
|
11
|
+
public static func configure(path: String?) {
|
|
12
|
+
lock.withLock { configuredPath = path }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// The configured set's path, or nil for Xcode's default set.
|
|
16
|
+
public static func path() -> String? {
|
|
17
|
+
lock.withLock { configuredPath }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// The device set serve-sim targets, found by `lookup`, which sends
|
|
21
|
+
/// CoreSimulator's `selector` with `path`: `deviceSetWithPath:error:` and
|
|
22
|
+
/// the path for a configured set, `defaultDeviceSetWithError:` and nil for
|
|
23
|
+
/// Xcode's default. Nil when `lookup` finds no set.
|
|
24
|
+
public static func lookUp<Found>(_ lookup: (_ selector: String, _ path: String?) -> Found?) -> Found? {
|
|
25
|
+
let path = path()
|
|
26
|
+
return lookup(path == nil ? "defaultDeviceSetWithError:" : "deviceSetWithPath:error:", path)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// `xcrun` arguments that run `simctl <arguments>` against the configured set.
|
|
30
|
+
public static func simctlArguments(_ arguments: [String]) -> [String] {
|
|
31
|
+
guard let path = path() else { return ["simctl"] + arguments }
|
|
32
|
+
return ["simctl", "--set", path] + arguments
|
|
33
|
+
}
|
|
34
|
+
}
|