Skip to content

Commit 960a09d

Browse files
committed
fix: restore simulator accessibility and viewport states
1 parent e8e33f1 commit 960a09d

13 files changed

Lines changed: 821 additions & 243 deletions

File tree

cli/XCWAccessibilityBridge.m

Lines changed: 299 additions & 43 deletions
Large diffs are not rendered by default.

cli/XCWPrivateSimulatorBooter.m

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,11 @@ + (BOOL)bootDeviceWithUDID:(NSString *)udid error:(NSError * _Nullable __autorel
219219
booted = ((BOOL(*)(id, SEL, id, NSError **))objc_msgSend)(
220220
targetDevice,
221221
bootWithOptionsSelector,
222-
// Keep the boot session owned by SimDeck's daemon instead of asking
223-
// CoreSimulator for a persistent GUI-visible session.
224-
@{ @"persist": @NO },
222+
// Keep CoreSimulator's normal persistent boot session alive. The
223+
// private display bridge can still attach headlessly, but the
224+
// accessibility translation service needs the persistent boot
225+
// session to resolve a frontmost app reliably.
226+
@{ @"persist": @YES },
225227
&bootError
226228
);
227229
} else {

cli/native/XCWNativeBridge.m

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,74 @@ static void XCWSetErrorMessage(char **errorMessage, NSError *error) {
5050
return XCWCopyCString(string);
5151
}
5252

53+
@interface XCWNativeAccessibilityThreadRunner : NSObject
54+
@end
55+
56+
@implementation XCWNativeAccessibilityThreadRunner
57+
58+
+ (void)run {
59+
@autoreleasepool {
60+
NSThread.currentThread.name = @"com.simdeck.native-accessibility";
61+
NSRunLoop *runLoop = NSRunLoop.currentRunLoop;
62+
[runLoop addPort:NSMachPort.port forMode:NSDefaultRunLoopMode];
63+
while (!NSThread.currentThread.cancelled) {
64+
@autoreleasepool {
65+
[runLoop runMode:NSDefaultRunLoopMode beforeDate:NSDate.distantFuture];
66+
}
67+
}
68+
}
69+
}
70+
71+
@end
72+
73+
@interface XCWNativeAccessibilitySnapshotRequest : NSObject
74+
75+
@property (nonatomic, copy) NSString *udid;
76+
@property (nonatomic, assign) BOOL hasPoint;
77+
@property (nonatomic, assign) double x;
78+
@property (nonatomic, assign) double y;
79+
@property (nonatomic, assign) NSUInteger maxDepth;
80+
@property (nonatomic, assign) char *result;
81+
@property (nonatomic, assign) char *serializationError;
82+
@property (nonatomic, strong) NSError *snapshotError;
83+
84+
- (void)performSnapshot;
85+
86+
@end
87+
88+
@implementation XCWNativeAccessibilitySnapshotRequest
89+
90+
- (void)performSnapshot {
91+
@autoreleasepool {
92+
NSError *error = nil;
93+
NSValue *pointValue = self.hasPoint ? [NSValue valueWithPoint:NSMakePoint(self.x, self.y)] : nil;
94+
NSDictionary *snapshot = [XCWAccessibilityBridge accessibilitySnapshotForSimulatorUDID:self.udid
95+
atPoint:pointValue
96+
maxDepth:self.maxDepth
97+
error:&error];
98+
if (snapshot == nil) {
99+
self.snapshotError = error;
100+
return;
101+
}
102+
self.result = XCWJSONStringFromObject(snapshot, &_serializationError);
103+
}
104+
}
105+
106+
@end
107+
108+
static NSThread *XCWNativeAccessibilityThread(void) {
109+
static NSThread *thread = nil;
110+
static dispatch_once_t onceToken;
111+
dispatch_once(&onceToken, ^{
112+
thread = [[NSThread alloc] initWithTarget:XCWNativeAccessibilityThreadRunner.class
113+
selector:@selector(run)
114+
object:nil];
115+
thread.name = @"com.simdeck.native-accessibility";
116+
[thread start];
117+
});
118+
return thread;
119+
}
120+
53121
static xcw_native_owned_bytes XCWOwnedBytesFromData(NSData *data) {
54122
xcw_native_owned_bytes bytes = {0};
55123
if (data.length == 0) {
@@ -458,17 +526,37 @@ xcw_native_owned_bytes xcw_native_screenshot_png(const char *udid, char **error_
458526

459527
char *xcw_native_accessibility_snapshot(const char *udid, bool has_point, double x, double y, size_t max_depth, char **error_message) {
460528
@autoreleasepool {
461-
NSError *error = nil;
462-
NSValue *pointValue = has_point ? [NSValue valueWithPoint:NSMakePoint(x, y)] : nil;
463-
NSDictionary *snapshot = [XCWAccessibilityBridge accessibilitySnapshotForSimulatorUDID:XCWStringFromCString(udid)
464-
atPoint:pointValue
465-
maxDepth:max_depth
466-
error:&error];
467-
if (snapshot == nil) {
468-
XCWSetErrorMessage(error_message, error);
529+
XCWNativeAccessibilitySnapshotRequest *request = [XCWNativeAccessibilitySnapshotRequest new];
530+
request.udid = XCWStringFromCString(udid);
531+
request.hasPoint = has_point;
532+
request.x = x;
533+
request.y = y;
534+
request.maxDepth = max_depth;
535+
536+
NSThread *accessibilityThread = XCWNativeAccessibilityThread();
537+
if (NSThread.currentThread == accessibilityThread) {
538+
[request performSnapshot];
539+
} else {
540+
[request performSelector:@selector(performSnapshot)
541+
onThread:accessibilityThread
542+
withObject:nil
543+
waitUntilDone:YES
544+
modes:@[NSDefaultRunLoopMode]];
545+
}
546+
547+
if (request.result != NULL) {
548+
return request.result;
549+
}
550+
if (request.serializationError != NULL) {
551+
if (error_message != NULL) {
552+
*error_message = request.serializationError;
553+
} else {
554+
free(request.serializationError);
555+
}
469556
return NULL;
470557
}
471-
return XCWJSONStringFromObject(snapshot, error_message);
558+
XCWSetErrorMessage(error_message, request.snapshotError);
559+
return NULL;
472560
}
473561
}
474562

0 commit comments

Comments
 (0)