Skip to main content

Crate smix_simctl

Crate smix_simctl 

Source
Expand description

§smix-simctl

Crates.io docs.rs License

Async Rust wrapper around Apple’s xcrun simctl command-line tool. Boot / shutdown / launch / install / pasteboard / screenshot — every simulator-management subcommand wrapped in a typed tokio::process-backed client.

This crate exists because every Rust project doing iOS simulator automation re-implements the same Command::new("xcrun").arg("simctl")... boilerplate, parses the same JSON outputs, fights the same pbcopy stdin-pipe edge case. smix-simctl does it once, cleanly typed.

§Quickstart

use smix_simctl::{Appearance, SimctlClient, SimctlPermission};
use std::time::Duration;

let simctl = SimctlClient::new();

// 1. Inventory
let runtimes = simctl.list_runtimes().await?;
let devices = simctl.list_devices().await?;
let booted: Vec<_> = devices.iter().filter(|d| d.state == "Booted").collect();

// 2. Lifecycle (idempotent on already-booted)
let udid = "4F0B35D2-03F0-4A8F-B729-09072153E8AE";
simctl.boot_and_wait(udid, Duration::from_secs(60)).await?;
let launched = simctl.launch(udid, "com.example.app").await?;
println!("launched pid={}", launched.pid);

// 3. Settings
simctl.set_appearance(udid, Appearance::Dark).await?;
simctl.grant_permission(udid, SimctlPermission::Camera, "com.example.app").await?;
simctl.set_reduce_motion(udid, true).await?;

// 4. IO
let png = simctl.screenshot(udid).await?;
std::fs::write("/tmp/screen.png", png).unwrap();

// 5. Pasteboard
simctl.pasteboard_set(udid, "hello clipboard").await?;
let got = simctl.pasteboard_get(udid).await?;
assert_eq!(got, "hello clipboard");

§Methods

CategoryMethods
Inventorylist_runtimes, list_devices
Lifecycleboot, boot_and_wait, shutdown, erase, install, uninstall, terminate, launch
Settingsset_appearance, grant_permission, keychain_reset, set_reduce_motion
Pasteboardpasteboard_get, pasteboard_set
URLopen_url
IOscreenshot (returns Vec<u8> PNG bytes)
Provisioningcreate_device, delete_device

§When to reach for this vs. shelling out yourself

Use casePick
Need typed SimctlDevice / SimctlRuntime with is_available filtersmix-simctl
Want boot_and_wait polling logic without re-implementing itsmix-simctl
Need pasteboard_set (handles pbcopy stdin-pipe correctly)smix-simctl
One-off shell script wrapping a single xcrun simctl boot callplain Command
Need real-device control (instruments, devicectl)out of scope — this is sim-only

§Scope

  • ✅ All async, returns typed SimctlError variants
  • ✅ Uses tokio::process::Command for spawn
  • ✅ Parses JSON outputs (list devices -j, etc.) into typed structs
  • screenshot returns raw PNG bytes — no PNG parsing dep needed
  • ❌ No real-device support — this is xcrun simctl only
  • ❌ No instruments / devicectl integration
  • ❌ No XCUITest runner spawning (see smix-runner-client for that)

Originally extracted from smix as a standalone stone. The crate is intentionally smix-business-decoupled — any Rust project doing iOS-Simulator automation can adopt it without pulling in the rest of smix.

§License

Dual-licensed under either:

at your option. smix-simctl — xcrun simctl child_process wrapper (outer crate).

All operations shell out to xcrun simctl <subcommand>; JSON-formatted outputs (list runtimes, list devices, screenshot binary) are parsed with serde_json / raw bytes. Tokio’s process::Command is the async spawn primitive.

This is an outer crate — allowed to depend on the wider tokio ecosystem. Use it from cement (smix-cli / smix-mcp) or from a higher-level driver wrapper.

Modules§

registry
.smix/sims.json device registry — deterministic device addressing.
screenshot_pacer
v1.0.4 — adaptive xcrun simctl io screenshot pacer. See screenshot_pacer::ScreenshotPacer and .claude/rfc/1.0.4-sim-health-and-backpressure.md §D3.

Structs§

LaunchResult
Launched-app result.
RecordingHandle
Handle to an active xcrun simctl io recordVideo child process. Pair with SimctlClient::record_video_stop for SIGINT-and-wait shutdown (so the mp4 trailer is flushed). Dropping the handle without stop would tokio-SIGKILL on Drop and truncate the output file.
SimctlClient
Stateless wrapper around xcrun simctl. Methods are free functions in spirit (no instance state beyond optionally-cached xcrun path); kept as a struct for API ergonomics + future caching.
SimctlDevice
One simulator device known to xcrun simctl.
SimctlRuntime
One iOS / watchOS / tvOS runtime installed on the host.

Enums§

Appearance
UI appearance mode for xcrun simctl ui <udid> appearance.
SimctlError
Failure variants for any xcrun simctl invocation.
SimctlPermission
Permission names accepted by xcrun simctl privacy <udid> grant <name>.

Functions§

compose_child_env
Compose user-provided (key, value) pairs into the SIMCTL_CHILD_* envp that xcrun simctl launch strips and delivers to the launched app. Idempotent: a key that already starts with SIMCTL_CHILD_ is passed through unchanged.
ensure_srgb_chunk
Ensure the PNG carries an sRGB ancillary chunk. Called on the raw bytes returned by xcrun simctl io <udid> screenshot. If the PNG already has an sRGB chunk, returns the input unchanged; otherwise splices in a 13-byte sRGB chunk (rendering intent = 0, perceptual) immediately before the first IDAT. Returns the input unchanged on any structural anomaly (missing magic, malformed chunk) so a corrupted PNG is passed through untouched for the caller to diagnose.