Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,32 @@ The current repo uses the private boot path, private display bridge, and private

## Build and Run

Build the browser bundle:
Build the native CLI and browser bundle:

```sh
./scripts/build-client.sh
npm run build
```

Build the native CLI:
Build individual pieces when needed:

```sh
./scripts/build-cli.sh
npm run build:cli
npm run build:client
npm run build:all
npm run package:vscode
```

This now builds the Rust server in `server/` and copies the resulting binary to `build/simdeck`.

Run the local daemon:

```sh
./build/simdeck
./build/simdeck daemon start --port 4310
```

Running without a subcommand starts a foreground workspace daemon, prints local and LAN browser URLs, and stops when the command exits. Pass a simulator name or UDID as the only argument to select it by default in the UI. Use `./build/simdeck -d`, `./build/simdeck -k`, and `./build/simdeck -r` as detached start, kill, and restart shortcuts.

Use software H.264 when macOS screen recording starves the hardware encoder:

```sh
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ view inside the editor.
## Build

```sh
./scripts/build-client.sh
./scripts/build-cli.sh
npm run build
```

This builds the native CLI and browser client. To build companion packages too, run `npm run build:all`.

Requirements:

- macOS
Expand Down Expand Up @@ -71,11 +72,18 @@ Full documentation lives at [simdeck.nativescript.org](https://simdeck.nativescr
## Run

```sh
simdeck ui --open
simdeck
```

This starts a workspace-local foreground daemon, prints local and LAN browser URLs, and stops when you press Ctrl-C.
To focus a specific simulator by name or UDID, pass it as the only argument:

```sh
simdeck "iPhone 17 Pro Max"
```

This starts or reuses the project daemon, enables the browser UI, and opens the authenticated local URL.
To focus a specific simulator, add `?device=UDID` to the opened URL.
Use `simdeck ui --open` or `simdeck daemon start` when you want a reusable background daemon instead.
The no-subcommand lifecycle shortcuts are `simdeck -d` for detached start, `simdeck -k` to kill the background daemon, and `simdeck -r` to restart it.
SimDeck Cloud uses the same server binary as its GitHub Actions provider. The
provider workflow starts `simdeck serve` on the runner, exposes it through a
tunnel, and lets the hosted control plane connect to the simulator with a
Expand Down Expand Up @@ -257,6 +265,7 @@ npm run package:vscode-extension
```

This writes `build/vscode/simdeck-vscode.vsix`.
The shorter aliases `npm run package:vscode` and `npm run package:vsix` do the same thing.

Install that local package into VS Code:

Expand Down
24 changes: 19 additions & 5 deletions bin/simdeck.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

import { spawnSync } from "node:child_process";
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
Expand All @@ -23,14 +23,28 @@ if (!existsSync(binaryPath)) {
process.exit(1);
}

const result = spawnSync(binaryPath, process.argv.slice(2), {
const child = spawn(binaryPath, process.argv.slice(2), {
cwd: process.cwd(),
stdio: "inherit",
});

if (result.error) {
console.error(result.error.message);
child.on("error", (error) => {
console.error(error.message);
process.exit(1);
});

for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
process.once(signal, () => {
if (!child.killed) {
child.kill(signal);
}
});
}

process.exit(result.status ?? 1);
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 1);
});
14 changes: 11 additions & 3 deletions cli/DFPrivateSimulatorDisplayBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,14 @@ static id DFInitSimDeviceScreen(Class screenClass, id device, uint32_t screenID,
return unwrapped;
}

static NSDictionary<NSNumber *, id> * DFReadAvailableAdapterScreens(id adapterHost, id adapter) {
NSDictionary<NSNumber *, id> *screens = DFReadAdapterScreens(adapterHost);
if (screens.count == 0) {
screens = DFReadAdapterScreens(adapter);
}
return screens;
}

static uint32_t __attribute__((unused)) DFCallSwiftSelfGetterU32(id selfObject, const char *symbolName) {
if (selfObject == nil) {
return 0;
Expand Down Expand Up @@ -2268,11 +2276,11 @@ - (nullable instancetype)initWithUDID:(NSString *)udid
// the bootstrap SimDeviceScreen is allocated — they trickle in over a few
// seconds (or only after Simulator.app primes the device). Poll instead of
// relying on a fixed 0.5s sleep.
NSDictionary<NSNumber *, id> *screens = DFReadAdapterScreens(_screenAdapterHost);
NSDictionary<NSNumber *, id> *screens = DFReadAvailableAdapterScreens(_screenAdapterHost, _screenAdapter);
NSDate *screenDeadline = [NSDate dateWithTimeIntervalSinceNow:10.0];
while (screens.count == 0 && [screenDeadline timeIntervalSinceNow] > 0) {
DFSpinRunLoop(0.1);
screens = DFReadAdapterScreens(_screenAdapterHost);
screens = DFReadAvailableAdapterScreens(_screenAdapterHost, _screenAdapter);
}
if (screens.count == 0) {
DFLog(@"SimulatorKit screen adapter exposed no live screens for %@", udid);
Expand Down Expand Up @@ -2958,7 +2966,7 @@ - (void)disconnect {
((void(*)(id, SEL, id))objc_msgSend)(self->_screenAdapter, sel_registerName("unregisterScreenAdapterCallbacksWithUUID:"), self->_screenAdapterCallbackUUID);
}

NSDictionary<NSNumber *, id> *screens = DFReadAdapterScreens(self->_screenAdapterHost);
NSDictionary<NSNumber *, id> *screens = DFReadAvailableAdapterScreens(self->_screenAdapterHost, self->_screenAdapter);
id rawScreen = screens[@(self->_activeScreenID)];
if (rawScreen != nil && self->_screenCallbackUUID != nil && [rawScreen respondsToSelector:sel_registerName("unregisterScreenCallbacksWithUUID:")]) {
((void(*)(id, SEL, id))objc_msgSend)(rawScreen, sel_registerName("unregisterScreenCallbacksWithUUID:"), self->_screenCallbackUUID);
Expand Down
2 changes: 1 addition & 1 deletion cli/XCWChromeRenderer.m
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ + (nullable NSData *)screenMaskPNGDataForDeviceName:(NSString *)deviceName
CGFloat cornerRadius = chromeCornerRadius;
CGFloat maskCornerRadius = [self framebufferMaskCornerRadiusForChromeInfo:chromeInfo
pointScreenWidth:pointScreenWidth];
if (maskCornerRadius > 0.0) {
if (!phoneProfile && maskCornerRadius > 0.0) {
cornerRadius = maskCornerRadius * radiusScale;
}

Expand Down
1 change: 1 addition & 0 deletions client/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface SimulatorMetadata {
name: string;
runtimeName?: string;
runtimeIdentifier?: string;
deviceTypeName?: string;
deviceTypeIdentifier?: string;
isBooted: boolean;
privateDisplay?: PrivateDisplayInfo;
Expand Down
Loading
Loading