Skip to content

Commit f305903

Browse files
committed
Stabilize WebRTC Safari setup
1 parent b704f9a commit f305903

20 files changed

Lines changed: 329 additions & 45 deletions

cli/DFPrivateSimulatorDisplayBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ NS_SWIFT_NAME(PrivateSimulatorDisplayBridge)
6161
error:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(sendKey(keyCode:down:));
6262

6363
- (BOOL)pressHomeButton:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(pressHomeButton());
64+
- (BOOL)openAppSwitcher:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(openAppSwitcher());
6465
- (BOOL)pressHardwareButtonNamed:(NSString *)buttonName
6566
durationMs:(NSUInteger)durationMs
6667
error:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(pressHardwareButton(named:durationMs:));

cli/DFPrivateSimulatorDisplayBridge.m

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,6 +2015,67 @@ static BOOL DFPressHomeViaHIDClient(id hidClient, NSError **error) {
20152015
return NO;
20162016
}
20172017

2018+
static BOOL DFOpenAppSwitcherViaHIDClient(id hidClient, NSError **error) {
2019+
if (hidClient == nil) {
2020+
if (error != NULL) {
2021+
*error = DFMakeError(
2022+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
2023+
@"SimulatorKit did not provide a headless HID client for App Switcher."
2024+
);
2025+
}
2026+
return NO;
2027+
}
2028+
2029+
static const DFHomeButtonHIDStrategy strategies[] = {
2030+
{ "IndigoHIDMessageForHIDArbitrary page=0x0c usage=0x40 (Menu) target=0x32", NO, 0, DFConsumerControlUsagePage, 0x40, DFIndigoTouchTarget },
2031+
{ "IndigoHIDMessageForHIDArbitrary page=0x0c usage=0x65 (Home) target=0x32", NO, 0, DFConsumerControlUsagePage, DFHomeConsumerUsage, DFIndigoTouchTarget },
2032+
{ "IndigoHIDMessageForButton code=0x191 target=0x2", YES, DFHomeButtonCode, 0, 0, 0x2 },
2033+
{ "IndigoHIDMessageForButton code=0x191 target=0x32", YES, DFHomeButtonCode, 0, 0, DFIndigoTouchTarget },
2034+
};
2035+
2036+
NSError *lastError = nil;
2037+
for (size_t index = 0; index < sizeof(strategies) / sizeof(strategies[0]); index++) {
2038+
const DFHomeButtonHIDStrategy *strategy = &strategies[index];
2039+
BOOL dispatched = YES;
2040+
for (NSUInteger pressIndex = 0; pressIndex < 2; pressIndex += 1) {
2041+
NSError *downError = nil;
2042+
if (!DFSendHomeStrategyEdge(hidClient, strategy, DFButtonDirectionDown, &downError)) {
2043+
DFLog(@"App Switcher strategy rejected (down): %s%@", strategy->label, downError.localizedDescription ?: @"no error");
2044+
lastError = downError;
2045+
dispatched = NO;
2046+
break;
2047+
}
2048+
2049+
[NSThread sleepForTimeInterval:0.06];
2050+
2051+
NSError *upError = nil;
2052+
if (!DFSendHomeStrategyEdge(hidClient, strategy, DFButtonDirectionUp, &upError)) {
2053+
DFLog(@"App Switcher strategy rejected (up): %s%@", strategy->label, upError.localizedDescription ?: @"no error");
2054+
lastError = upError;
2055+
dispatched = NO;
2056+
break;
2057+
}
2058+
2059+
if (pressIndex == 0) {
2060+
[NSThread sleepForTimeInterval:0.12];
2061+
}
2062+
}
2063+
2064+
if (dispatched) {
2065+
DFLog(@"App Switcher dispatched via %s", strategy->label);
2066+
return YES;
2067+
}
2068+
}
2069+
2070+
if (error != NULL) {
2071+
*error = lastError ?: DFMakeError(
2072+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
2073+
@"SimulatorKit rejected every App Switcher HID strategy."
2074+
);
2075+
}
2076+
return NO;
2077+
}
2078+
20182079
@interface DFPrivateSimulatorDisplayBridge ()
20192080

20202081
@property (nonatomic, strong) NSView *displayView;
@@ -2912,6 +2973,37 @@ - (BOOL)pressHomeButton:(NSError * _Nullable __autoreleasing *)error {
29122973
return success;
29132974
}
29142975

2976+
- (BOOL)openAppSwitcher:(NSError * _Nullable __autoreleasing *)error {
2977+
__block BOOL success = NO;
2978+
__block NSError *dispatchError = nil;
2979+
2980+
dispatch_block_t work = ^{
2981+
NSError *hidError = nil;
2982+
if (DFOpenAppSwitcherViaHIDClient(self->_hidClient, &hidError)) {
2983+
success = YES;
2984+
return;
2985+
}
2986+
2987+
DFLog(@"HID App Switcher path failed: %@", hidError.localizedDescription ?: @"unknown error");
2988+
dispatchError = hidError ?: DFMakeError(
2989+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
2990+
@"No App Switcher path succeeded."
2991+
);
2992+
};
2993+
2994+
if (dispatch_get_specific(DFPrivateSimulatorCallbackQueueKey) != NULL) {
2995+
work();
2996+
} else {
2997+
dispatch_sync(_callbackQueue, work);
2998+
}
2999+
3000+
if (!success && error != NULL) {
3001+
*error = dispatchError;
3002+
}
3003+
3004+
return success;
3005+
}
3006+
29153007
- (BOOL)rotateByDegrees:(double)deltaDegrees error:(NSError * _Nullable __autoreleasing *)error {
29163008
__block BOOL success = NO;
29173009
__block NSError *dispatchError = nil;

cli/XCWPrivateSimulatorSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ typedef void (^XCWPrivateSimulatorEncodedFrameHandler)(NSData *sampleData,
4949
error:(NSError * _Nullable * _Nullable)error;
5050

5151
- (BOOL)pressHomeButton:(NSError * _Nullable * _Nullable)error;
52+
- (BOOL)openAppSwitcher:(NSError * _Nullable * _Nullable)error;
5253
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error;
5354
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error;
5455
- (void)disconnect;

cli/XCWPrivateSimulatorSession.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ - (BOOL)pressHomeButton:(NSError * _Nullable __autoreleasing *)error {
302302
return [_displayBridge pressHomeButton:error];
303303
}
304304

305+
- (BOOL)openAppSwitcher:(NSError * _Nullable __autoreleasing *)error {
306+
return [_displayBridge openAppSwitcher:error];
307+
}
308+
305309
- (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
306310
return [_displayBridge rotateRight:error];
307311
}

cli/native/XCWNativeBridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ bool xcw_native_send_touch(const char * _Nonnull udid, double x, double y, const
4949
bool xcw_native_send_key(const char * _Nonnull udid, uint16_t key_code, uint32_t modifiers, char * _Nullable * _Nullable error_message);
5050
bool xcw_native_send_key_event(const char * _Nonnull udid, uint16_t key_code, bool down, char * _Nullable * _Nullable error_message);
5151
bool xcw_native_press_home(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
52+
bool xcw_native_open_app_switcher(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
5253
bool xcw_native_press_button(const char * _Nonnull udid, const char * _Nonnull button_name, uint32_t duration_ms, char * _Nullable * _Nullable error_message);
5354
bool xcw_native_rotate_right(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
5455
bool xcw_native_rotate_left(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
@@ -75,6 +76,7 @@ bool xcw_native_session_send_touch(void * _Nonnull handle, double x, double y, c
7576
bool xcw_native_session_send_multitouch(void * _Nonnull handle, double x1, double y1, double x2, double y2, const char * _Nonnull phase, char * _Nullable * _Nullable error_message);
7677
bool xcw_native_session_send_key(void * _Nonnull handle, uint16_t key_code, uint32_t modifiers, char * _Nullable * _Nullable error_message);
7778
bool xcw_native_session_press_home(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
79+
bool xcw_native_session_open_app_switcher(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
7880
bool xcw_native_session_rotate_right(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
7981
bool xcw_native_session_rotate_left(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
8082
void xcw_native_session_set_frame_callback(void * _Nonnull handle, xcw_native_frame_callback _Nullable callback, void * _Nullable user_data);

cli/native/XCWNativeBridge.m

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,22 @@ bool xcw_native_press_home(const char *udid, char **error_message) {
502502
}
503503
}
504504

505+
bool xcw_native_open_app_switcher(const char *udid, char **error_message) {
506+
@autoreleasepool {
507+
DFPrivateSimulatorDisplayBridge *bridge = XCWInputBridgeForUDID(udid, error_message);
508+
if (bridge == nil) {
509+
return false;
510+
}
511+
NSError *error = nil;
512+
BOOL ok = [bridge openAppSwitcher:&error];
513+
[bridge disconnect];
514+
if (!ok) {
515+
XCWSetErrorMessage(error_message, error);
516+
}
517+
return ok;
518+
}
519+
}
520+
505521
bool xcw_native_press_button(const char *udid, const char *button_name, uint32_t duration_ms, char **error_message) {
506522
@autoreleasepool {
507523
DFPrivateSimulatorDisplayBridge *bridge = XCWInputBridgeForUDID(udid, error_message);
@@ -728,6 +744,17 @@ bool xcw_native_session_press_home(void *handle, char **error_message) {
728744
}
729745
}
730746

747+
bool xcw_native_session_open_app_switcher(void *handle, char **error_message) {
748+
@autoreleasepool {
749+
NSError *error = nil;
750+
BOOL ok = [XCWNativeSessionFromHandle(handle) openAppSwitcher:&error];
751+
if (!ok) {
752+
XCWSetErrorMessage(error_message, error);
753+
}
754+
return ok;
755+
}
756+
}
757+
731758
bool xcw_native_session_rotate_right(void *handle, char **error_message) {
732759
@autoreleasepool {
733760
NSError *error = nil;

cli/native/XCWNativeSession.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
2828
modifiers:(uint32_t)modifiers
2929
error:(NSError * _Nullable * _Nullable)error;
3030
- (BOOL)pressHome:(NSError * _Nullable * _Nullable)error;
31+
- (BOOL)openAppSwitcher:(NSError * _Nullable * _Nullable)error;
3132
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error;
3233
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error;
3334
- (void)setFrameCallback:(xcw_native_frame_callback _Nullable)callback

cli/native/XCWNativeSession.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ - (BOOL)pressHome:(NSError * _Nullable __autoreleasing *)error {
129129
return [self.session pressHomeButton:error];
130130
}
131131

132+
- (BOOL)openAppSwitcher:(NSError * _Nullable __autoreleasing *)error {
133+
return [self.session openAppSwitcher:error];
134+
}
135+
132136
- (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
133137
return [self.session rotateRight:error];
134138
}

client/src/api/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export interface SimulatorsResponse {
2424
export interface HealthResponse {
2525
ok: boolean;
2626
videoCodec?: string;
27+
webRtc?: {
28+
iceServers?: RTCIceServer[];
29+
iceTransportPolicy?: RTCIceTransportPolicy;
30+
};
2731
}
2832

2933
export interface SimulatorResponse {

client/src/features/stream/streamWorkerClient.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { apiHeaders, fetchHealth } from "../../api/client";
2+
import type { HealthResponse } from "../../api/types";
23
import { createEmptyStreamStats } from "./stats";
34
import type {
45
StreamConnectTarget,
@@ -87,9 +88,13 @@ class WebRtcStreamClient implements StreamClientBackend {
8788
});
8889

8990
try {
91+
const health = await fetchHealth().catch(() => null);
92+
if (generation !== this.connectGeneration) {
93+
return;
94+
}
9095
const peerConnection = new RTCPeerConnection({
91-
iceServers: iceServers(),
92-
iceTransportPolicy: iceTransportPolicy(),
96+
iceServers: iceServers(health),
97+
iceTransportPolicy: iceTransportPolicy(health),
9398
});
9499
this.peerConnection = peerConnection;
95100
this.attachDiagnostics(peerConnection, target, generation);
@@ -619,27 +624,47 @@ function configureReceiverCodecPreferences(transceiver: RTCRtpTransceiver) {
619624
]);
620625
}
621626

622-
function iceServers(): RTCIceServer[] {
627+
function iceServers(health?: HealthResponse | null): RTCIceServer[] {
623628
const params = new URLSearchParams(window.location.search);
624-
const raw = params.get("iceServers") ?? "stun:stun.l.google.com:19302";
629+
const queryValue = params.get("iceServers");
630+
const raw = queryValue ?? "";
625631
if (raw === "none") {
626632
return [];
627633
}
628-
return [
629-
{
634+
if (raw.trim()) {
635+
const server: RTCIceServer = {
630636
urls: raw
631637
.split(",")
632638
.map((value) => value.trim())
633639
.filter(Boolean),
634-
},
635-
];
640+
};
641+
const username = params.get("iceUsername");
642+
const credential = params.get("iceCredential");
643+
if (username) {
644+
server.username = username;
645+
}
646+
if (credential) {
647+
server.credential = credential;
648+
}
649+
return [server];
650+
}
651+
if (health?.webRtc?.iceServers?.length) {
652+
return health.webRtc.iceServers;
653+
}
654+
return [{ urls: ["stun:stun.l.google.com:19302"] }];
636655
}
637656

638-
function iceTransportPolicy(): RTCIceTransportPolicy {
657+
function iceTransportPolicy(
658+
health?: HealthResponse | null,
659+
): RTCIceTransportPolicy {
639660
const value = new URLSearchParams(window.location.search).get(
640661
"iceTransportPolicy",
641662
);
642-
return value === "relay" || value === "all" ? value : "all";
663+
if (value === "relay" || value === "all") {
664+
return value;
665+
}
666+
const healthValue = health?.webRtc?.iceTransportPolicy;
667+
return healthValue === "relay" || healthValue === "all" ? healthValue : "all";
643668
}
644669

645670
interface WebRtcDiagnostics {

0 commit comments

Comments
 (0)