Skip to content

Add wallpaper and icon-layout commands#714

Open
aluedeke wants to merge 1 commit into
mainfrom
feature/wallpaper-and-icon-layout
Open

Add wallpaper and icon-layout commands#714
aluedeke wants to merge 1 commit into
mainfrom
feature/wallpaper-and-icon-layout

Conversation

@aluedeke
Copy link
Copy Markdown
Collaborator

@aluedeke aluedeke commented May 6, 2026

Summary

Adds four CLI commands for inspecting and modifying iOS home-screen state on supervised devices, reverse-engineered from Apple Configurator's cfgutil set-wallpaper and the com.apple.springboardservices RPC.

  • ios set-wallpaper <image> [--screen=lock|home|both] --p12file=... [--password=...] — sets the device wallpaper. Sends the MDM Settings command with item Wallpaper over the post-Escalate com.apple.mobile.MCInstall channel, exactly matching cfgutil's wire format (verified by DTrace).
  • ios get-wallpaper [--output=wallpaper.png] — pulls the home-screen wallpaper PNG via springboardservices. No supervision required.
  • ios get-icon-layout [--output=…] / ios set-icon-layout <file> — round-trippable JSON capture/restore of the home-screen layout (getIconState / setIconState).

The supervised auth handshake (Escalate / EscalateResponse / ProceedWithKeybagMigration) reuses the existing mcinstall.EscalateWithCertAndKey path; no new crypto is added.

Behavior caveats (documented in CLI help and Go doc-comments)

  • iOS 16+ unified Lock + Home wallpaper: the device applies the image to both screens regardless of the Where field. Apple's own cfgutil exhibits the same behavior. The --screen flag is preserved for older iOS / forward-compat.
  • get-wallpaper may EOF on iOS 18: known iOS-side regression also seen in pymobiledevice3 (doronz88/pymobiledevice3#1450). Code is correct.
  • set-icon-layout constraint: iOS requires every installed app to occupy a slot. Per cfgutil docs: "unexpected behavior may occur if the given layout does not contain every icon on the device". Missing apps are re-paginated, not hidden.
  • Edit-Pages hidden-page bit (iOS 14+) is not exposed by springboardservices, so a fetched layout will not include hidden pages, and pushing a layout cannot reproduce a hidden page.

Test plan

  • go build ./... clean (only my touched packages; existing untracked ios/resign/ WIP excluded)
  • go vet ./ios/mcinstall ./ios/springboard ./ clean
  • set-wallpaper end-to-end on a real supervised iPhone SE (iOS 18.7.1) — wallpaper changes verified visually
  • --screen=lock|home|both all return "ok"; behavior matches cfgutil exactly (both screens get set on iOS 16+, unavoidable)
  • get-icon-layoutset-icon-layout round-trip on real device returns "ok", layout preserved
  • Negative tests: invalid --screen, missing --p12file, P12_PASSWORD env-var fallback, all behave correctly
  • get-wallpaper on iOS < 18 (no test device available)

🤖 Generated with Claude Code

Adds four CLI commands for managing iOS home-screen state on supervised
devices, reverse-engineered from cfgutil set-wallpaper and the
springboardservices RPC.

- ios set-wallpaper <image> [--screen=lock|home|both] --p12file=...
  Sends the MDM "Settings" command with item "Wallpaper" over the post-
  Escalate MCInstall channel, matching cfgutil's wire format. iOS 16+
  unifies lock and home wallpapers as a paired set, so --screen is
  preserved for older-iOS / forward-compat (cfgutil behaves identically).

- ios get-wallpaper [--output=wallpaper.png]
  Pulls the home-screen wallpaper PNG via springboardservices. Does not
  require supervision; lock screen is not exposed by iOS. May EOF on
  iOS 18 (matches pymobiledevice3 #1450).

- ios get-icon-layout [--output=...] / ios set-icon-layout <file>
  Round-trippable JSON capture/restore of the home-screen layout via
  springboardservices getIconState/setIconState. Documented constraint:
  every installed app must occupy a slot (per cfgutil docs) and the
  iOS 14+ Edit-Pages hidden-page bit is not exposed by the RPC.

The supervised handshake (Escalate / EscalateResponse /
ProceedWithKeybagMigration) reuses the existing
mcinstall.EscalateWithCertAndKey path, so no new crypto is added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@aluedeke aluedeke requested a review from gmegidish May 6, 2026 09:13
@aluedeke aluedeke self-assigned this May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant