Skip to content

Commit aafb748

Browse files
committed
feat(ios): harden SimDeck Studio streaming
1 parent b854077 commit aafb748

25 files changed

Lines changed: 2697 additions & 192 deletions

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@ simdeck "iPhone 17 Pro Max"
6868

6969
The served loopback browser UI receives the generated API access token automatically.
7070
LAN clients should pair with the printed code before receiving the API cookie.
71+
For native iOS pairing, run:
72+
73+
```sh
74+
simdeck pair
75+
```
76+
77+
This starts or refreshes the global LaunchAgent-backed SimDeck service, prints
78+
local, LAN, and Tailscale URLs when available, and shows a QR code with a
79+
`simdeck://pair` link. The QR contains the pairing code plus all detected
80+
non-loopback addresses, so pairing once can save both the LAN and Tailscale
81+
routes with the same service token.
7182

7283
CLI commands automatically use the same warm daemon:
7384

cli/XCWChromeRenderer.m

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,6 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
289289
BOOL hasModernPhoneSensor = [self shouldRenderPhoneChromeFromSlices:plist sensorName:sensorName];
290290
BOOL hasComposite = !hasModernPhoneSensor && [self compositeAssetPathForChromeInfo:chromeInfo].length > 0;
291291
CGFloat screenScale = MAX([self numberValue:plist[@"mainScreenScale"]], 1.0);
292-
CGFloat profileScreenWidth = [self numberValue:plist[@"mainScreenWidth"]];
293-
CGFloat profileScreenHeight = [self numberValue:plist[@"mainScreenHeight"]];
294292
CGSize profileScreenSize = [self screenSizeForChromeInfo:chromeInfo
295293
chromeSize:compositeSize
296294
screenScale:screenScale];
@@ -301,17 +299,17 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
301299
CGFloat screenHeight;
302300
CGFloat screenX;
303301
CGFloat screenY;
304-
if (hasComposite && pointScreenWidth > 0.0 && pointScreenHeight > 0.0) {
302+
if (watchProfile) {
303+
screenX = sizingLeft;
304+
screenY = sizingTop;
305+
screenWidth = MAX(compositeSize.width - sizingLeft - sizingRight, 1.0);
306+
screenHeight = MAX(compositeSize.height - standHeight - sizingTop - sizingBottom, 1.0);
307+
} else if (hasComposite && pointScreenWidth > 0.0 && pointScreenHeight > 0.0) {
305308
screenWidth = pointScreenWidth;
306309
screenHeight = pointScreenHeight;
307310
screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0);
308311
CGFloat usableHeight = compositeSize.height - standHeight;
309312
screenY = MAX((usableHeight - screenHeight) / 2.0, bezelTop);
310-
} else if (watchProfile) {
311-
screenWidth = profileScreenWidth;
312-
screenHeight = profileScreenHeight;
313-
screenX = MAX((compositeSize.width - screenWidth) / 2.0, 0.0);
314-
screenY = MAX((compositeSize.height - screenHeight) / 2.0, 0.0);
315313
} else {
316314
screenX = bezelLeft;
317315
screenY = bezelTop;
@@ -320,7 +318,7 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
320318
}
321319

322320
CGFloat innerRadius = MAX(rawCornerRadius - MAX(screenX, screenY), 0.0);
323-
CGFloat radiusScale = pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0;
321+
CGFloat radiusScale = !watchProfile && pointScreenWidth > 0.0 ? screenWidth / pointScreenWidth : 1.0;
324322
CGFloat chromeCornerRadius = innerRadius * radiusScale;
325323
CGFloat cornerRadius = chromeCornerRadius;
326324

client/package-lock.json

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/app/AppShell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ export function AppShell({
904904
? buildChromeUrl(
905905
selectedSimulator.udid,
906906
chromeAssetStamp,
907-
!chromeHasInteractiveButtons || chromeHasCrown,
907+
!chromeHasInteractiveButtons,
908908
)
909909
: "";
910910
const chromeButtonUrl = useCallback(

docs/cli/commands.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Replace `simdeck` with `./build/simdeck` when running from a source checkout.
1212
| `simdeck -k` | Stop the detached project daemon |
1313
| `simdeck -r` | Restart the detached project daemon |
1414
| `simdeck ui --open` | Open the browser UI from a daemon |
15+
| `simdeck pair` | Show native iOS pairing code and QR |
1516
| `simdeck daemon status` | Show daemon URL, PID, token, and log path |
1617
| `simdeck daemon stop` | Stop the current project daemon |
1718
| `simdeck daemon killall` | Stop all project daemons |
@@ -22,9 +23,15 @@ Examples:
2223
```sh
2324
simdeck ui --port 4320 --open
2425
simdeck ui --open
26+
simdeck pair
2527
simdeck daemon restart --video-codec software --stream-quality low
2628
```
2729

30+
`simdeck pair` uses the global LaunchAgent-backed service instead of a
31+
project-local daemon. It binds the service for LAN access, preserves an existing
32+
service token and pairing code when present, detects LAN and Tailscale IPv4
33+
addresses, and prints a `simdeck://pair` QR for the native iOS app.
34+
2835
## Device Lifecycle
2936

3037
```sh

docs/guide/troubleshooting.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,18 @@ Start SimDeck with a LAN bind and reachable advertised host:
189189
simdeck ui --bind 0.0.0.0 --advertise-host 192.168.1.50 --open
190190
```
191191

192+
For native iOS pairing, prefer:
193+
194+
```sh
195+
simdeck pair
196+
```
197+
192198
Then check:
193199

194200
- The remote browser opens `http://192.168.1.50:4310`.
195201
- macOS Firewall allows the port.
196-
- The pairing code matches the current daemon.
197-
- API scripts send the daemon token.
202+
- The pairing code matches the current daemon or global service.
203+
- API scripts send the daemon or service token.
198204

199205
See [LAN Access](/guide/lan-access).
200206

ios/SimDeckStudio.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@
363363
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
364364
CODE_SIGN_ENTITLEMENTS = SimDeckStudio/SimDeckStudio.entitlements;
365365
CODE_SIGN_STYLE = Automatic;
366-
CURRENT_PROJECT_VERSION = 1;
366+
CURRENT_PROJECT_VERSION = 202605170407;
367367
DEVELOPMENT_ASSET_PATHS = "";
368368
DEVELOPMENT_TEAM = CS838V553Y;
369369
ENABLE_PREVIEWS = YES;
@@ -392,7 +392,7 @@
392392
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
393393
CODE_SIGN_ENTITLEMENTS = SimDeckStudio/SimDeckStudio.entitlements;
394394
CODE_SIGN_STYLE = Automatic;
395-
CURRENT_PROJECT_VERSION = 1;
395+
CURRENT_PROJECT_VERSION = 202605170407;
396396
DEVELOPMENT_ASSET_PATHS = "";
397397
DEVELOPMENT_TEAM = CS838V553Y;
398398
ENABLE_PREVIEWS = YES;

0 commit comments

Comments
 (0)