|
32 | 32 | typedef uint32_t IndigoHIDEdge; |
33 | 33 |
|
34 | 34 | typedef IndigoHIDMessage *(*DFIndigoHIDMessageForMouseNSEventFn)(CGPoint *location, CGPoint *windowLocation, uint32_t target, NSEventType type, NSSize displaySize, IndigoHIDEdge edge); |
| 35 | +typedef IndigoHIDMessage *(*DFIndigoHIDMessageForMouseNSEvent9Fn)(CGPoint *location, CGPoint *windowLocation, uint32_t target, uint32_t eventType, uint32_t direction, double unused1, double unused2, double widthPoints, double heightPoints); |
35 | 36 | typedef IndigoHIDMessage *(*DFIndigoHIDMessageForKeyboardArbitraryFn)(int keyCode, int op); |
36 | 37 | typedef IndigoHIDMessage *(*DFIndigoHIDMessageForKeyboardNSEventFn)(NSEvent *event); |
37 | 38 | typedef IndigoHIDMessage *(*DFIndigoHIDMessageForButtonFn)(uint32_t buttonCode, uint32_t operation, uint32_t target); |
38 | 39 | typedef IndigoHIDMessage *(*DFIndigoHIDMessageForHIDArbitraryFn)(uint32_t target, uint32_t page, uint32_t usage, uint32_t operation); |
| 40 | +typedef IndigoHIDMessage *(*DFIndigoHIDServiceMessageFn)(void); |
39 | 41 |
|
40 | 42 | #pragma pack(push, 4) |
41 | 43 | typedef struct { |
|
91 | 93 | static const uint32_t DFIndigoTouchTarget = 0x32; |
92 | 94 | static const uint8_t DFIndigoEventTypeTouch = 0x02; |
93 | 95 | static const uint32_t DFIndigoTouchEventKind = 0x0b; |
| 96 | +static const uint32_t DFIndigoMouseEventDown = 1; |
| 97 | +static const uint32_t DFIndigoMouseEventUp = 2; |
| 98 | +static const uint32_t DFIndigoMouseEventDragged = 6; |
| 99 | +static const uint32_t DFIndigoMouseDirectionDown = 1; |
| 100 | +static const uint32_t DFIndigoMouseDirectionMove = 0; |
| 101 | +static const uint32_t DFIndigoMouseDirectionUp = 2; |
94 | 102 | static const int DFKeyboardDirectionDown = 1; |
95 | 103 | static const int DFKeyboardDirectionUp = 2; |
96 | 104 | static const uint32_t DFButtonDirectionDown = 1; |
|
128 | 136 | return @"/Applications/Xcode.app/Contents/Developer/Library/PrivateFrameworks/SimulatorKit.framework/SimulatorKit"; |
129 | 137 | } |
130 | 138 |
|
| 139 | +static NSInteger DFXcodeMajorVersion(void) { |
| 140 | + NSString *developerPath = nil; |
| 141 | + const char *developerDir = getenv("DEVELOPER_DIR"); |
| 142 | + if (developerDir != NULL && developerDir[0] != '\0') { |
| 143 | + developerPath = [NSString stringWithUTF8String:developerDir]; |
| 144 | + } else { |
| 145 | + FILE *pipe = popen("/usr/bin/xcode-select -p 2>/dev/null", "r"); |
| 146 | + if (pipe != NULL) { |
| 147 | + char buffer[PATH_MAX] = {0}; |
| 148 | + if (fgets(buffer, sizeof(buffer), pipe) != NULL) { |
| 149 | + developerPath = [[NSString stringWithUTF8String:buffer] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| 150 | + } |
| 151 | + pclose(pipe); |
| 152 | + } |
| 153 | + } |
| 154 | + if (developerPath.length == 0) { |
| 155 | + return 0; |
| 156 | + } |
| 157 | + |
| 158 | + NSString *contentsPath = developerPath; |
| 159 | + if ([contentsPath.lastPathComponent isEqualToString:@"Developer"]) { |
| 160 | + contentsPath = contentsPath.stringByDeletingLastPathComponent; |
| 161 | + } |
| 162 | + |
| 163 | + NSDictionary *versionInfo = [NSDictionary dictionaryWithContentsOfFile:[contentsPath stringByAppendingPathComponent:@"version.plist"]]; |
| 164 | + NSString *version = versionInfo[@"CFBundleShortVersionString"]; |
| 165 | + if (version.length == 0) { |
| 166 | + NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[contentsPath stringByAppendingPathComponent:@"Info.plist"]]; |
| 167 | + version = info[@"CFBundleShortVersionString"]; |
| 168 | + } |
| 169 | + return [[version componentsSeparatedByString:@"."].firstObject integerValue]; |
| 170 | +} |
| 171 | + |
131 | 172 | typedef struct { |
132 | 173 | __unsafe_unretained id unit; |
133 | 174 | double value; |
@@ -187,6 +228,23 @@ static BOOL DFVerboseTouchLoggingEnabled(void) { |
187 | 228 | return enabled; |
188 | 229 | } |
189 | 230 |
|
| 231 | +static BOOL DFShouldUseIndigoMouse9Path(void) { |
| 232 | + static BOOL enabled = NO; |
| 233 | + static dispatch_once_t onceToken; |
| 234 | + dispatch_once(&onceToken, ^{ |
| 235 | + NSString *override = NSProcessInfo.processInfo.environment[@"SIMDECK_INDIGO_MOUSE_9ARG"]; |
| 236 | + if (override.length > 0) { |
| 237 | + enabled = [override isEqualToString:@"1"] || |
| 238 | + [override caseInsensitiveCompare:@"true"] == NSOrderedSame || |
| 239 | + [override caseInsensitiveCompare:@"yes"] == NSOrderedSame; |
| 240 | + return; |
| 241 | + } |
| 242 | + |
| 243 | + enabled = DFXcodeMajorVersion() >= 26; |
| 244 | + }); |
| 245 | + return enabled; |
| 246 | +} |
| 247 | + |
190 | 248 | #pragma mark - SimulatorKit Swift symbol resolver |
191 | 249 | // |
192 | 250 | // We call into SimulatorKit's private Swift API by dlsym'ing mangled symbol |
@@ -1002,6 +1060,109 @@ static BOOL DFSendHIDMessage(id hidClient, IndigoHIDMessage *message, BOOL freeW |
1002 | 1060 | return YES; |
1003 | 1061 | } |
1004 | 1062 |
|
| 1063 | +static uint32_t DFIndigoMouseEventTypeForPhase(DFPrivateSimulatorTouchPhase phase) { |
| 1064 | + switch (phase) { |
| 1065 | + case DFPrivateSimulatorTouchPhaseBegan: |
| 1066 | + return DFIndigoMouseEventDown; |
| 1067 | + case DFPrivateSimulatorTouchPhaseMoved: |
| 1068 | + return DFIndigoMouseEventDragged; |
| 1069 | + case DFPrivateSimulatorTouchPhaseEnded: |
| 1070 | + case DFPrivateSimulatorTouchPhaseCancelled: |
| 1071 | + return DFIndigoMouseEventUp; |
| 1072 | + } |
| 1073 | +} |
| 1074 | + |
| 1075 | +static uint32_t DFIndigoMouseDirectionForPhase(DFPrivateSimulatorTouchPhase phase) { |
| 1076 | + switch (phase) { |
| 1077 | + case DFPrivateSimulatorTouchPhaseBegan: |
| 1078 | + return DFIndigoMouseDirectionDown; |
| 1079 | + case DFPrivateSimulatorTouchPhaseMoved: |
| 1080 | + return DFIndigoMouseDirectionMove; |
| 1081 | + case DFPrivateSimulatorTouchPhaseEnded: |
| 1082 | + case DFPrivateSimulatorTouchPhaseCancelled: |
| 1083 | + return DFIndigoMouseDirectionUp; |
| 1084 | + } |
| 1085 | +} |
| 1086 | + |
| 1087 | +static IndigoHIDMessage *DFCreateIndigoTouchMessage9(CGPoint normalizedPoint, NSSize displaySize, DFPrivateSimulatorTouchPhase phase) { |
| 1088 | + if (!DFShouldUseIndigoMouse9Path()) { |
| 1089 | + return NULL; |
| 1090 | + } |
| 1091 | + DFIndigoHIDMessageForMouseNSEvent9Fn mouseMessage = (DFIndigoHIDMessageForMouseNSEvent9Fn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageForMouseNSEvent"); |
| 1092 | + if (mouseMessage == NULL) { |
| 1093 | + return NULL; |
| 1094 | + } |
| 1095 | + |
| 1096 | + CGPoint ratioPoint = CGPointMake( |
| 1097 | + fmax(0.0, fmin(1.0, normalizedPoint.x)), |
| 1098 | + fmax(0.0, fmin(1.0, normalizedPoint.y)) |
| 1099 | + ); |
| 1100 | + return mouseMessage(&ratioPoint, |
| 1101 | + NULL, |
| 1102 | + DFIndigoTouchTarget, |
| 1103 | + DFIndigoMouseEventTypeForPhase(phase), |
| 1104 | + DFIndigoMouseDirectionForPhase(phase), |
| 1105 | + 1.0, |
| 1106 | + 1.0, |
| 1107 | + displaySize.width, |
| 1108 | + displaySize.height); |
| 1109 | +} |
| 1110 | + |
| 1111 | +static IndigoHIDMessage *DFCreateIndigoMultiTouchMessage9(CGPoint normalizedPoint1, CGPoint normalizedPoint2, NSSize displaySize, DFPrivateSimulatorTouchPhase phase) { |
| 1112 | + if (!DFShouldUseIndigoMouse9Path()) { |
| 1113 | + return NULL; |
| 1114 | + } |
| 1115 | + DFIndigoHIDMessageForMouseNSEvent9Fn mouseMessage = (DFIndigoHIDMessageForMouseNSEvent9Fn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageForMouseNSEvent"); |
| 1116 | + if (mouseMessage == NULL) { |
| 1117 | + return NULL; |
| 1118 | + } |
| 1119 | + |
| 1120 | + CGPoint ratioPoint = CGPointMake( |
| 1121 | + fmax(0.0, fmin(1.0, normalizedPoint1.x)), |
| 1122 | + fmax(0.0, fmin(1.0, normalizedPoint1.y)) |
| 1123 | + ); |
| 1124 | + CGPoint secondRatioPoint = CGPointMake( |
| 1125 | + fmax(0.0, fmin(1.0, normalizedPoint2.x)), |
| 1126 | + fmax(0.0, fmin(1.0, normalizedPoint2.y)) |
| 1127 | + ); |
| 1128 | + return mouseMessage(&ratioPoint, |
| 1129 | + &secondRatioPoint, |
| 1130 | + DFIndigoTouchTarget, |
| 1131 | + DFIndigoMouseEventTypeForPhase(phase), |
| 1132 | + DFIndigoMouseDirectionForPhase(phase), |
| 1133 | + 1.0, |
| 1134 | + 1.0, |
| 1135 | + displaySize.width, |
| 1136 | + displaySize.height); |
| 1137 | +} |
| 1138 | + |
| 1139 | +static void DFWarmIndigoHIDServices(id hidClient) { |
| 1140 | + if (hidClient == nil) { |
| 1141 | + return; |
| 1142 | + } |
| 1143 | + |
| 1144 | + DFIndigoHIDServiceMessageFn createPointer = (DFIndigoHIDServiceMessageFn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageToCreatePointerService"); |
| 1145 | + DFIndigoHIDServiceMessageFn createMouse = (DFIndigoHIDServiceMessageFn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageToCreateMouseService"); |
| 1146 | + NSError *error = nil; |
| 1147 | + if (createPointer != NULL) { |
| 1148 | + IndigoHIDMessage *message = createPointer(); |
| 1149 | + if (message != NULL) { |
| 1150 | + (void)DFSendHIDMessage(hidClient, message, YES, &error); |
| 1151 | + usleep(20 * 1000); |
| 1152 | + } |
| 1153 | + } |
| 1154 | + if (createMouse != NULL) { |
| 1155 | + IndigoHIDMessage *message = createMouse(); |
| 1156 | + if (message != NULL) { |
| 1157 | + (void)DFSendHIDMessage(hidClient, message, YES, &error); |
| 1158 | + usleep(20 * 1000); |
| 1159 | + } |
| 1160 | + } |
| 1161 | + if (error != nil) { |
| 1162 | + DFLog(@"Indigo HID service warm-up reported: %@", error.localizedDescription ?: @"unknown error"); |
| 1163 | + } |
| 1164 | +} |
| 1165 | + |
1005 | 1166 | static DFIndigoMessage *DFCreateIndigoTouchMessage(CGPoint normalizedPoint, NSSize displaySize, BOOL touchDown, NSError **error) { |
1006 | 1167 | DFIndigoHIDMessageForMouseNSEventFn mouseMessage = (DFIndigoHIDMessageForMouseNSEventFn)dlsym(RTLD_DEFAULT, "IndigoHIDMessageForMouseNSEvent"); |
1007 | 1168 | if (mouseMessage == NULL) { |
@@ -2378,6 +2539,7 @@ - (nullable instancetype)initWithUDID:(NSString *)udid |
2378 | 2539 |
|
2379 | 2540 | if (_hidClient != nil) { |
2380 | 2541 | DFLog(@"Created private SimulatorKit HID client for %@", udid); |
| 2542 | + DFWarmIndigoHIDServices(_hidClient); |
2381 | 2543 | } else { |
2382 | 2544 | DFLog(@"Failed to create private SimulatorKit HID client for %@: %@", udid, hidClientError.localizedDescription ?: @"unknown error"); |
2383 | 2545 | } |
@@ -3311,14 +3473,18 @@ - (BOOL)sendTouchAtNormalizedX:(double)normalizedX |
3311 | 3473 | phaseLabel = phase == DFPrivateSimulatorTouchPhaseEnded ? @"ended" : @"cancelled"; |
3312 | 3474 | break; |
3313 | 3475 | } |
3314 | | - BOOL touchDown = phase == DFPrivateSimulatorTouchPhaseBegan || phase == DFPrivateSimulatorTouchPhaseMoved; |
3315 | | - DFIndigoMessage *message = DFCreateIndigoTouchMessage(CGPointMake(clampedX, clampedY), displaySize, touchDown, &dispatchError); |
| 3476 | + IndigoHIDMessage *message = DFCreateIndigoTouchMessage9(CGPointMake(clampedX, clampedY), displaySize, phase); |
| 3477 | + BOOL freeWhenDone = YES; |
3316 | 3478 | if (message == NULL) { |
3317 | | - return; |
| 3479 | + BOOL touchDown = phase == DFPrivateSimulatorTouchPhaseBegan || phase == DFPrivateSimulatorTouchPhaseMoved; |
| 3480 | + message = (IndigoHIDMessage *)DFCreateIndigoTouchMessage(CGPointMake(clampedX, clampedY), displaySize, touchDown, &dispatchError); |
| 3481 | + if (message == NULL) { |
| 3482 | + return; |
| 3483 | + } |
3318 | 3484 | } |
3319 | 3485 |
|
3320 | 3486 | NSError *messageError = nil; |
3321 | | - if (!DFSendHIDMessage(self->_hidClient, (IndigoHIDMessage *)message, YES, &messageError)) { |
| 3487 | + if (!DFSendHIDMessage(self->_hidClient, message, freeWhenDone, &messageError)) { |
3322 | 3488 | dispatchError = messageError ?: DFMakeError( |
3323 | 3489 | DFPrivateSimulatorErrorCodeTouchDispatchFailed, |
3324 | 3490 | @"SimulatorKit rejected the Indigo HID touch packet." |
@@ -3394,14 +3560,25 @@ - (BOOL)sendMultiTouchAtNormalizedX1:(double)normalizedX1 |
3394 | 3560 | break; |
3395 | 3561 | } |
3396 | 3562 |
|
3397 | | - BOOL touchDown = phase == DFPrivateSimulatorTouchPhaseBegan || phase == DFPrivateSimulatorTouchPhaseMoved; |
3398 | | - DFIndigoMessage *message = DFCreateIndigoMultiTouchMessage(CGPointMake(x1, y1), CGPointMake(x2, y2), displaySize, touchDown, &dispatchError); |
| 3563 | + IndigoHIDMessage *message = NULL; |
| 3564 | + const NSUInteger maxAttempts = phase == DFPrivateSimulatorTouchPhaseMoved ? 12 : 3; |
| 3565 | + for (NSUInteger attempt = 0; attempt < maxAttempts; attempt++) { |
| 3566 | + message = DFCreateIndigoMultiTouchMessage9(CGPointMake(x1, y1), CGPointMake(x2, y2), displaySize, phase); |
| 3567 | + if (message != NULL) { |
| 3568 | + break; |
| 3569 | + } |
| 3570 | + usleep(5 * 1000); |
| 3571 | + } |
3399 | 3572 | if (message == NULL) { |
3400 | | - return; |
| 3573 | + BOOL touchDown = phase == DFPrivateSimulatorTouchPhaseBegan || phase == DFPrivateSimulatorTouchPhaseMoved; |
| 3574 | + message = (IndigoHIDMessage *)DFCreateIndigoMultiTouchMessage(CGPointMake(x1, y1), CGPointMake(x2, y2), displaySize, touchDown, &dispatchError); |
| 3575 | + if (message == NULL) { |
| 3576 | + return; |
| 3577 | + } |
3401 | 3578 | } |
3402 | 3579 |
|
3403 | 3580 | NSError *messageError = nil; |
3404 | | - if (!DFSendHIDMessage(self->_hidClient, (IndigoHIDMessage *)message, YES, &messageError)) { |
| 3581 | + if (!DFSendHIDMessage(self->_hidClient, message, YES, &messageError)) { |
3405 | 3582 | dispatchError = messageError ?: DFMakeError( |
3406 | 3583 | DFPrivateSimulatorErrorCodeTouchDispatchFailed, |
3407 | 3584 | @"SimulatorKit rejected the Indigo HID multi-touch packet." |
|
0 commit comments