Skip to content

Commit 0b4ad26

Browse files
renegadelinkclaude
andcommitted
services: add libusb backend for ValeriaScreenCapture (Linux/Windows)
Speaks Apple's QuickTime USB protocol directly via pyusb — the same protocol the macOS DAL plugin uses internally. Selects devices by USB serial number (= UDID), so multiple identical-model devices on one host are unambiguous. Linux uses our usbmuxd fork's `dynamic-config-switch` branch (https://github.com/renegadelink/usbmuxd) which adds a `SetActiveConfiguration` plist IPC, plus `USBMUXD_DEFAULT_DEVICE_MODE=2`. Each capture toggles the iDevice into a QT-enabled USB configuration just for the duration of the capture, then back to a QT-free configuration when done — mirroring macOS's `iOSScreenCaptureAssistant`. Verified across immediate / 30s / 120s / 300s / 600s idle gaps. Added: * `pymobiledevice3.services.valeria_libusb.ValeriaLibusb` - the backend * `pymobiledevice3.services.valeria_mode_switch` - qt_capture_mode context manager + plist IPC client * `docs/guides/valeria-linux-setup.md` - build chain, mode-2 env var, fork-branch URL, troubleshooting; also notes that multi-device farms can use stock usbmuxd (config 5 permanently) instead of our fork to avoid per-capture lockdown disconnects. Skipped on macOS: libusb cannot claim Apple USB interfaces under IOKit/DriverKit on macOS 15+; the CMIO sibling backend is used there. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 54e216c commit 0b4ad26

3 files changed

Lines changed: 1635 additions & 0 deletions

File tree

docs/guides/valeria-linux-setup.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Valeria - Linux setup
2+
3+
This guide is required for **`pymobiledevice3 valeria`** (iOS H.264
4+
screen capture) on Linux. The distro `usbmuxd` package may not include
5+
the `DEVICE_MODE` feature needed to enable QuickTime capture mode, so
6+
we build upstream `usbmuxd` master from source.
7+
8+
## Why this is needed
9+
10+
The screen-capture path uses Apple's QuickTime alt-config protocol over
11+
USB. On macOS the OS-level `usbmuxd` handles the lockdown handshake on
12+
the device's USBMux interface (the one that's normally used for sync,
13+
backups, etc.) - and the iDevice will not begin streaming H.264 until
14+
that handshake has happened.
15+
16+
Distribution `usbmuxd` packages don't yet support driving a device into
17+
QuickTime alt-config - the upstream feature for this (env var
18+
`USBMUXD_DEFAULT_DEVICE_MODE`) was added to upstream master in 2022 but
19+
predates the latest tagged release, so it hasn't shipped in any distro
20+
package. We need to build upstream `usbmuxd` from source and run it
21+
with that env var set.
22+
23+
## What this changes about your machine
24+
25+
- The distro `usbmuxd` is replaced with our fork under `/usr/local/`
26+
(the `dynamic-config-switch` branch on top of upstream master).
27+
- Any iDevice you plug in starts in QuickTime mode (USB configuration
28+
5). Each `pmd3 valeria` or `pmd3 screen-mirror` invocation toggles
29+
it between config 5 (capture active) and config 4 (idle, USBMux only)
30+
for the duration of that capture, then back. Mirrors macOS's
31+
`iOSScreenCaptureAssistant` behaviour.
32+
- Standard things - file transfer, lockdown queries, app installation,
33+
iDevice-as-network-modem tethering - keep working. They go through
34+
the same `/var/run/usbmuxd` socket and the same iface 1 USBMux
35+
endpoints, which are present in both configurations.
36+
37+
If you need to revert later, see [Reverting](#reverting) below.
38+
39+
## Build and install
40+
41+
Tested on Ubuntu 22.04. The build chain is **libplist → libimobiledevice-glue
42+
→ usbmuxd**. Ubuntu's `libplist-dev` is too old (2.2.0; we need 2.3+),
43+
which is why the chain starts there.
44+
45+
```bash
46+
# 1. Build dependencies
47+
sudo apt-get update && sudo apt-get install -y \
48+
autoconf automake libtool pkg-config build-essential git \
49+
libusb-1.0-0-dev
50+
51+
# 2. Remove distro usbmuxd (it'll conflict with upstream master)
52+
sudo systemctl stop usbmuxd 2>/dev/null || true
53+
sudo apt-get remove -y usbmuxd
54+
55+
# 3. Build the libimobiledevice stack from upstream
56+
cd /tmp
57+
for proj in libplist libimobiledevice-glue; do
58+
git clone --depth 1 https://github.com/libimobiledevice/$proj
59+
(cd $proj && ./autogen.sh && ./configure && make -j && sudo make install)
60+
done
61+
sudo ldconfig
62+
63+
# 3a. usbmuxd from our fork's dynamic-config-switch branch (adds the
64+
# SetActiveConfiguration IPC that pmd3's per-capture config switch
65+
# uses; pending upstream PR).
66+
git clone -b dynamic-config-switch \
67+
https://github.com/renegadelink/usbmuxd
68+
(cd usbmuxd && ./autogen.sh && ./configure --without-preflight \
69+
&& make -j && sudo make install)
70+
71+
# 4. Configure systemd to start usbmuxd with QuickTime mode enabled
72+
sudo mkdir -p /etc/systemd/system/usbmuxd.service.d
73+
sudo tee /etc/systemd/system/usbmuxd.service.d/override.conf >/dev/null <<'EOF'
74+
[Service]
75+
Environment=USBMUXD_DEFAULT_DEVICE_MODE=2
76+
EOF
77+
sudo systemctl daemon-reload
78+
sudo systemctl enable --now usbmuxd
79+
```
80+
81+
## Verify
82+
83+
```bash
84+
# Should report a recent build SHA, not v1.0.x:
85+
/usr/local/sbin/usbmuxd --version
86+
87+
# Should list "USBMUXD_DEFAULT_DEVICE_MODE":
88+
strings /usr/local/sbin/usbmuxd | grep DEVICE_MODE
89+
90+
# Plug an iDevice, then confirm the daemon picked it up
91+
# and switched to QuickTime mode (configuration value 5):
92+
pymobiledevice3 usbmux list
93+
cat /sys/bus/usb/devices/*/bConfigurationValue \
94+
/sys/bus/usb/devices/*/idVendor 2>/dev/null \
95+
| paste - - | awk '$2 == "05ac" {print "active config:", $1}'
96+
```
97+
98+
If the active config is `5`, you're set.
99+
100+
## Run
101+
102+
```bash
103+
# Capture raw H.264 to a file
104+
pymobiledevice3 valeria -o capture.h264 --duration 10
105+
```
106+
107+
The iDevice screen must be **on** (not asleep) for frames to actually
108+
flow. The protocol completes its handshake either way, but a sleeping
109+
display produces no encoder output.
110+
111+
## Troubleshooting
112+
113+
**`Valeria: capture started (0x0)` and an empty output file** - the
114+
handshake reached the protocol thread but the device never sent a video
115+
clock setup. Most often the iDevice screen is asleep. Tap the iDevice
116+
to wake it and re-run.
117+
118+
**`failed to start capture: [Errno 16] Resource busy`** - another
119+
process (often a stale `pmd3 valeria` from a prior run, or a separate
120+
`usbmuxd` instance) is still holding the USBMux interface. Check
121+
`ps aux | grep usbmuxd` - there should be exactly one instance.
122+
123+
**Multiple captures in a row** - each `pymobiledevice3 valeria`
124+
invocation asks `usbmuxd` to switch the iDevice into the QT-enabled USB
125+
configuration for the duration of the capture, and back to a QT-free
126+
configuration when done (mirroring macOS's `iOSScreenCaptureAssistant`
127+
pattern). This means each invocation is self-contained - no daemon, no
128+
kept-alive USB iface claim - and back-to-back captures across long
129+
idles work cleanly.
130+
131+
**Other `pymobiledevice3` commands stop working after install** -
132+
double-check that the distro `usbmuxd` was removed cleanly. `which usbmuxd`
133+
should return `/usr/local/sbin/usbmuxd`. `systemctl status usbmuxd`
134+
should show the upstream binary.
135+
136+
## Reverting
137+
138+
To go back to the distro setup (no QuickTime alt-config):
139+
140+
```bash
141+
sudo systemctl stop usbmuxd
142+
sudo rm /etc/systemd/system/usbmuxd.service.d/override.conf
143+
sudo apt-get install -y --reinstall usbmuxd
144+
sudo systemctl daemon-reload
145+
sudo systemctl restart usbmuxd
146+
```
147+
148+
The upstream `libplist` / `libimobiledevice-glue` libraries you built
149+
into `/usr/local/` are harmless to leave installed. Remove them too
150+
with `(cd /tmp/<lib> && sudo make uninstall)` if you want a perfectly
151+
clean revert.
152+
153+
## Background - what `USBMUXD_DEFAULT_DEVICE_MODE=2` does
154+
155+
Upstream `usbmuxd` since commit `6d0183dd` (2022-12-22) supports the
156+
env var `USBMUXD_DEFAULT_DEVICE_MODE`. The values map to USB
157+
configuration numbers Apple defines on iDevices:
158+
159+
| Mode | USB config | Description |
160+
|------|------------|-------------|
161+
| 1 | 4 | Default - usbmuxd only (sync, lockdown, tethering) |
162+
| 2 | 5 | QuickTime alt-config - adds the AV streaming interface |
163+
| 3 | 6 | Other internal mode |
164+
| 4 | 7 | Other internal mode |
165+
| 5 | 8 | Other internal mode |
166+
167+
Setting it to `2` tells `usbmuxd` to put the iDevice into a USB layout
168+
that exposes BOTH a QT-capable configuration (5) and a USBMux-only
169+
configuration (4). The `SetActiveConfiguration` IPC on our fork's
170+
`dynamic-config-switch` branch then lets `pymobiledevice3 valeria`
171+
toggle between them per-capture - matching macOS's
172+
`iOSScreenCaptureAssistant`, which also keeps the iDevice in the
173+
non-QT config when idle and switches into the QT config only while a
174+
capture is active.
175+
176+
If you don't need that idle-when-not-capturing behaviour (e.g. a
177+
multi-device farm where you'd rather avoid the per-capture lockdown
178+
disconnect), skip our fork and use stock upstream `usbmuxd` with the
179+
same `USBMUXD_DEFAULT_DEVICE_MODE=2`. The iDevice stays in config 5
180+
permanently; the USBMux iface is always present so lockdown ops keep
181+
working unaffected.

0 commit comments

Comments
 (0)