Skip to content

Commit bcc2f25

Browse files
committed
Improve Apple Watch chrome input
1 parent f7f4a1b commit bcc2f25

24 files changed

Lines changed: 302 additions & 7 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Private simulator behavior is implemented locally in:
7878
- Accessibility bridge: `cli/XCWAccessibilityBridge.*`
7979

8080
The current repo uses the private boot path, private display bridge, and private accessibility translation bridge directly. The browser streams frames from that bridge, injects touch and keyboard events through the same native session layer, inspects accessibility through `AccessibilityPlatformTranslation`, and renders device chrome from `cli/XCWChromeRenderer.*`.
81-
Physical chrome button support uses DeviceKit `chrome.json` input geometry for browser hit targets. Volume, action, mute, Apple Watch digital crown, Watch side button, and Watch left-side button dispatch through `IndigoHIDMessageForHIDArbitrary` with consumer/telephony/vendor HID usage pairs from the device chrome metadata; home, lock, and app-switcher remain on the existing SimulatorKit button paths.
81+
Physical chrome button support uses DeviceKit `chrome.json` input geometry for browser hit targets. Volume, action, mute, Apple Watch digital crown, Watch side button, and Watch left-side button dispatch through `IndigoHIDMessageForHIDArbitrary` with consumer/telephony/vendor HID usage pairs from the device chrome metadata; home, lock, and app-switcher remain on the existing SimulatorKit button paths. Apple Watch Digital Crown rotation dispatches through `IndigoHIDMessageForScrollEvent` with the same digitizer target as touch input.
8282
WebKit inspection uses the simulator `webinspectord` Unix socket named `com.apple.webinspectord_sim.socket` and WebKit's binary-plist Remote Inspector selectors. It lists only WebKit content that the runtime exposes as inspectable. For app-owned `WKWebView` on iOS 16.4 and newer, the app must set `isInspectable = true`.
8383

8484
## Build and Run

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ simdeck button <udid> lock --duration-ms 1000
148148
simdeck button <udid> volume-up
149149
simdeck button <udid> action --duration-ms 1000
150150
simdeck button <udid> digital-crown
151+
simdeck crown <udid> --delta 50
151152
simdeck button <udid> left-side-button
152153
simdeck batch <udid> --step "tap --label Continue" --step "type 'hello'" --step "wait-for --label hello"
153154
simdeck dismiss-keyboard <udid>

cli/DFPrivateSimulatorDisplayBridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ NS_SWIFT_NAME(PrivateSimulatorDisplayBridge)
8585
usagePage:(nullable NSNumber *)usagePage
8686
usage:(nullable NSNumber *)usage
8787
error:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(sendHardwareButton(named:pressed:usagePage:usage:));
88+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
89+
error:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(rotateDigitalCrown(delta:));
8890

8991
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(rotateRight());
9092
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error NS_SWIFT_NAME(rotateLeft());

cli/DFPrivateSimulatorDisplayBridge.m

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
typedef IndigoHIDMessage *(*DFIndigoHIDMessageForKeyboardNSEventFn)(NSEvent *event);
3939
typedef IndigoHIDMessage *(*DFIndigoHIDMessageForButtonFn)(uint32_t buttonCode, uint32_t operation, uint32_t target);
4040
typedef IndigoHIDMessage *(*DFIndigoHIDMessageForHIDArbitraryFn)(uint32_t target, uint32_t page, uint32_t usage, uint32_t operation);
41+
typedef IndigoHIDMessage *(*DFIndigoHIDMessageForScrollEventFn)(uint32_t target, double deltaX, double deltaY, double momentumPhase);
4142
typedef IndigoHIDMessage *(*DFIndigoHIDServiceMessageFn)(void);
4243

4344
#pragma pack(push, 4)
@@ -1431,6 +1432,29 @@ static void DFWarmIndigoHIDServices(id hidClient) {
14311432
return message;
14321433
}
14331434

1435+
static IndigoHIDMessage *DFCreateScrollHIDMessage(uint32_t target, double deltaX, double deltaY, NSError **error) {
1436+
DFIndigoHIDMessageForScrollEventFn scrollMessage = (DFIndigoHIDMessageForScrollEventFn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageForScrollEvent");
1437+
if (scrollMessage == NULL) {
1438+
if (error != NULL) {
1439+
*error = DFMakeError(
1440+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
1441+
@"SimulatorKit did not expose IndigoHIDMessageForScrollEvent."
1442+
);
1443+
}
1444+
return NULL;
1445+
}
1446+
1447+
IndigoHIDMessage *message = scrollMessage(target, deltaX, deltaY, 0);
1448+
if (message == NULL && error != NULL) {
1449+
*error = DFMakeError(
1450+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
1451+
[NSString stringWithFormat:@"SimulatorKit could not construct scroll HID for delta %.3f.", deltaY]
1452+
);
1453+
}
1454+
1455+
return message;
1456+
}
1457+
14341458
static BOOL DFCallSwiftUnitAngleMeasurementGetterByFunction(id selfObject, void *function, DFUnitAngleMeasurement *measurement) {
14351459
if (selfObject == nil || function == NULL || measurement == NULL) {
14361460
return NO;
@@ -3497,6 +3521,57 @@ - (BOOL)sendHardwareButtonNamed:(NSString *)buttonName
34973521
return success;
34983522
}
34993523

3524+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
3525+
error:(NSError * _Nullable __autoreleasing *)error {
3526+
if (!isfinite(delta)) {
3527+
if (error != NULL) {
3528+
*error = DFMakeError(
3529+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
3530+
@"Digital Crown delta must be finite."
3531+
);
3532+
}
3533+
return NO;
3534+
}
3535+
3536+
__block BOOL success = NO;
3537+
__block NSError *dispatchError = nil;
3538+
3539+
dispatch_block_t work = ^{
3540+
if (self->_hidClient == nil) {
3541+
dispatchError = DFMakeError(
3542+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
3543+
@"SimulatorKit did not provide a headless HID client for Digital Crown rotation."
3544+
);
3545+
return;
3546+
}
3547+
3548+
NSError *messageError = nil;
3549+
IndigoHIDMessage *message = DFCreateScrollHIDMessage(DFIndigoTouchTarget, 0, delta, &messageError);
3550+
if (message == NULL || !DFSendHIDMessage(self->_hidClient, message, YES, &messageError)) {
3551+
dispatchError = messageError;
3552+
return;
3553+
}
3554+
3555+
DFLog(@"Sending Digital Crown rotation delta=%.3f", delta);
3556+
success = YES;
3557+
};
3558+
3559+
if (dispatch_get_specific(DFPrivateSimulatorCallbackQueueKey) != NULL) {
3560+
work();
3561+
} else {
3562+
dispatch_sync(_callbackQueue, work);
3563+
}
3564+
3565+
if (!success && error != NULL) {
3566+
*error = dispatchError ?: DFMakeError(
3567+
DFPrivateSimulatorErrorCodeTouchDispatchFailed,
3568+
@"SimulatorKit rejected Digital Crown rotation."
3569+
);
3570+
}
3571+
3572+
return success;
3573+
}
3574+
35003575
- (BOOL)rotateRight:(NSError * _Nullable __autoreleasing *)error {
35013576
return [self rotateByDegrees:90.0 error:error];
35023577
}

cli/XCWPrivateSimulatorSession.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ typedef void (^XCWPrivateSimulatorEncodedFrameHandler)(NSData *sampleData,
6666
usagePage:(nullable NSNumber *)usagePage
6767
usage:(nullable NSNumber *)usage
6868
error:(NSError * _Nullable * _Nullable)error;
69+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
70+
error:(NSError * _Nullable * _Nullable)error;
6971
- (BOOL)openAppSwitcher:(NSError * _Nullable * _Nullable)error;
7072
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error;
7173
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error;

cli/XCWPrivateSimulatorSession.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,11 @@ - (BOOL)sendHardwareButtonNamed:(NSString *)buttonName
361361
error:error];
362362
}
363363

364+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
365+
error:(NSError * _Nullable __autoreleasing *)error {
366+
return [_displayBridge rotateDigitalCrownByDelta:delta error:error];
367+
}
368+
364369
- (BOOL)openAppSwitcher:(NSError * _Nullable __autoreleasing *)error {
365370
return [_displayBridge openAppSwitcher:error];
366371
}

cli/native/XCWNativeBridge.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ bool xcw_native_press_home(const char * _Nonnull udid, char * _Nullable * _Nulla
5353
bool xcw_native_open_app_switcher(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
5454
bool xcw_native_press_button(const char * _Nonnull udid, const char * _Nonnull button_name, uint32_t duration_ms, char * _Nullable * _Nullable error_message);
5555
bool xcw_native_send_button(const char * _Nonnull udid, const char * _Nonnull button_name, bool pressed, bool has_usage, uint32_t usage_page, uint32_t usage, char * _Nullable * _Nullable error_message);
56+
bool xcw_native_rotate_crown(const char * _Nonnull udid, double delta, char * _Nullable * _Nullable error_message);
5657
bool xcw_native_rotate_right(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
5758
bool xcw_native_rotate_left(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
5859
bool xcw_native_erase_simulator(const char * _Nonnull udid, char * _Nullable * _Nullable error_message);
@@ -84,6 +85,7 @@ bool xcw_native_session_send_key(void * _Nonnull handle, uint16_t key_code, uint
8485
bool xcw_native_session_press_home(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
8586
bool xcw_native_session_press_button(void * _Nonnull handle, const char * _Nonnull button_name, uint32_t duration_ms, char * _Nullable * _Nullable error_message);
8687
bool xcw_native_session_send_button(void * _Nonnull handle, const char * _Nonnull button_name, bool pressed, bool has_usage, uint32_t usage_page, uint32_t usage, char * _Nullable * _Nullable error_message);
88+
bool xcw_native_session_rotate_crown(void * _Nonnull handle, double delta, char * _Nullable * _Nullable error_message);
8789
bool xcw_native_session_open_app_switcher(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
8890
bool xcw_native_session_rotate_right(void * _Nonnull handle, char * _Nullable * _Nullable error_message);
8991
bool xcw_native_session_rotate_left(void * _Nonnull handle, char * _Nullable * _Nullable error_message);

cli/native/XCWNativeBridge.m

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,22 @@ bool xcw_native_send_button(const char *udid, const char *button_name, bool pres
579579
}
580580
}
581581

582+
bool xcw_native_rotate_crown(const char *udid, double delta, char **error_message) {
583+
@autoreleasepool {
584+
DFPrivateSimulatorDisplayBridge *bridge = XCWInputBridgeForUDID(udid, error_message);
585+
if (bridge == nil) {
586+
return false;
587+
}
588+
NSError *error = nil;
589+
BOOL ok = [bridge rotateDigitalCrownByDelta:delta error:&error];
590+
[bridge disconnect];
591+
if (!ok) {
592+
XCWSetErrorMessage(error_message, error);
593+
}
594+
return ok;
595+
}
596+
}
597+
582598
bool xcw_native_rotate_right(const char *udid, char **error_message) {
583599
@autoreleasepool {
584600
DFPrivateSimulatorDisplayBridge *bridge = XCWInputBridgeForUDID(udid, error_message);
@@ -850,6 +866,17 @@ bool xcw_native_session_send_button(void *handle, const char *button_name, bool
850866
}
851867
}
852868

869+
bool xcw_native_session_rotate_crown(void *handle, double delta, char **error_message) {
870+
@autoreleasepool {
871+
NSError *error = nil;
872+
BOOL ok = [XCWNativeSessionFromHandle(handle) rotateDigitalCrownByDelta:delta error:&error];
873+
if (!ok) {
874+
XCWSetErrorMessage(error_message, error);
875+
}
876+
return ok;
877+
}
878+
}
879+
853880
bool xcw_native_session_open_app_switcher(void *handle, char **error_message) {
854881
@autoreleasepool {
855882
NSError *error = nil;

cli/native/XCWNativeSession.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN
4444
usagePage:(nullable NSNumber *)usagePage
4545
usage:(nullable NSNumber *)usage
4646
error:(NSError * _Nullable * _Nullable)error;
47+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
48+
error:(NSError * _Nullable * _Nullable)error;
4749
- (BOOL)openAppSwitcher:(NSError * _Nullable * _Nullable)error;
4850
- (BOOL)rotateRight:(NSError * _Nullable * _Nullable)error;
4951
- (BOOL)rotateLeft:(NSError * _Nullable * _Nullable)error;

cli/native/XCWNativeSession.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ - (BOOL)sendHardwareButtonNamed:(NSString *)buttonName
171171
error:error];
172172
}
173173

174+
- (BOOL)rotateDigitalCrownByDelta:(double)delta
175+
error:(NSError * _Nullable __autoreleasing *)error {
176+
return [self.session rotateDigitalCrownByDelta:delta error:error];
177+
}
178+
174179
- (BOOL)openAppSwitcher:(NSError * _Nullable __autoreleasing *)error {
175180
return [self.session openAppSwitcher:error];
176181
}

0 commit comments

Comments
 (0)