pymobiledevice3 5.0.4__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/understanding_idevice_protocol_layers.md +10 -5
- pymobiledevice3/__main__.py +171 -46
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +22 -21
- pymobiledevice3/cli/activation.py +24 -22
- pymobiledevice3/cli/afc.py +49 -41
- pymobiledevice3/cli/amfi.py +13 -18
- pymobiledevice3/cli/apps.py +71 -65
- pymobiledevice3/cli/backup.py +134 -93
- pymobiledevice3/cli/bonjour.py +31 -29
- pymobiledevice3/cli/cli_common.py +175 -232
- pymobiledevice3/cli/companion_proxy.py +12 -12
- pymobiledevice3/cli/crash.py +95 -52
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +70 -75
- pymobiledevice3/cli/mounter.py +99 -57
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +36 -20
- pymobiledevice3/cli/power_assertion.py +15 -16
- pymobiledevice3/cli/processes.py +11 -17
- pymobiledevice3/cli/profile.py +120 -75
- pymobiledevice3/cli/provision.py +27 -26
- pymobiledevice3/cli/remote.py +109 -100
- pymobiledevice3/cli/restore.py +134 -129
- pymobiledevice3/cli/springboard.py +50 -50
- pymobiledevice3/cli/syslog.py +145 -65
- pymobiledevice3/cli/usbmux.py +66 -27
- pymobiledevice3/cli/version.py +2 -5
- pymobiledevice3/cli/webinspector.py +232 -156
- pymobiledevice3/exceptions.py +6 -2
- pymobiledevice3/lockdown.py +5 -1
- pymobiledevice3/lockdown_service_provider.py +5 -0
- pymobiledevice3/remote/remote_service_discovery.py +18 -10
- pymobiledevice3/restore/device.py +28 -4
- pymobiledevice3/restore/restore.py +2 -2
- pymobiledevice3/service_connection.py +15 -12
- pymobiledevice3/services/afc.py +731 -220
- pymobiledevice3/services/device_link.py +45 -31
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/lockdown_service.py +12 -9
- pymobiledevice3/services/mobile_config.py +1 -0
- pymobiledevice3/services/mobilebackup2.py +6 -3
- pymobiledevice3/services/os_trace.py +97 -55
- pymobiledevice3/services/remote_fetch_symbols.py +13 -8
- pymobiledevice3/services/screenshot.py +2 -2
- pymobiledevice3/services/web_protocol/alert.py +8 -8
- pymobiledevice3/services/web_protocol/automation_session.py +87 -79
- pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
- pymobiledevice3/services/web_protocol/driver.py +71 -70
- pymobiledevice3/services/web_protocol/element.py +58 -56
- pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
- pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
- pymobiledevice3/services/web_protocol/switch_to.py +23 -19
- pymobiledevice3/services/webinspector.py +42 -67
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
- pymobiledevice3/cli/completions.py +0 -50
- pymobiledevice3/cli/developer.py +0 -1539
- pymobiledevice3/cli/diagnostics.py +0 -110
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/cli/remote.py
CHANGED
|
@@ -3,15 +3,17 @@ import dataclasses
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
5
|
import tempfile
|
|
6
|
+
from contextlib import nullcontext
|
|
6
7
|
from functools import partial
|
|
7
|
-
from
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Annotated, Optional, TextIO
|
|
8
10
|
|
|
9
|
-
import
|
|
11
|
+
import typer
|
|
12
|
+
from typer_injector import InjectingTyper
|
|
10
13
|
|
|
11
14
|
from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing_manual_pairing
|
|
12
15
|
from pymobiledevice3.cli.cli_common import (
|
|
13
|
-
|
|
14
|
-
RSDCommand,
|
|
16
|
+
RSDServiceProviderDep,
|
|
15
17
|
print_json,
|
|
16
18
|
prompt_device_list,
|
|
17
19
|
sudo_required,
|
|
@@ -38,6 +40,7 @@ logger = logging.getLogger(__name__)
|
|
|
38
40
|
async def browse_rsd(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> list[dict]:
|
|
39
41
|
devices = []
|
|
40
42
|
for rsd in await get_rsds(timeout):
|
|
43
|
+
assert rsd.peer_info is not None
|
|
41
44
|
devices.append({
|
|
42
45
|
"address": rsd.service.address[0],
|
|
43
46
|
"port": RSD_PORT,
|
|
@@ -66,40 +69,36 @@ async def cli_browse(timeout: float = DEFAULT_BONJOUR_TIMEOUT) -> None:
|
|
|
66
69
|
})
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@cli.group("remote")
|
|
75
|
-
def remote_cli() -> None:
|
|
76
|
-
"""Create RemoteXPC tunnels"""
|
|
77
|
-
pass
|
|
72
|
+
cli = InjectingTyper(
|
|
73
|
+
name="remote",
|
|
74
|
+
help="Create and browse RemoteXPC tunnels (RSD/tunneld) for developer services.",
|
|
75
|
+
no_args_is_help=True,
|
|
76
|
+
)
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
@
|
|
81
|
-
@click.option("--host", default=TUNNELD_DEFAULT_ADDRESS[0])
|
|
82
|
-
@click.option("--port", type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
|
|
83
|
-
@click.option("-d", "--daemonize", is_flag=True)
|
|
84
|
-
@click.option(
|
|
85
|
-
"-p",
|
|
86
|
-
"--protocol",
|
|
87
|
-
type=click.Choice([e.value for e in TunnelProtocol]),
|
|
88
|
-
help="Transport protocol. If python version >= 3.13 will default to TCP. Otherwise will default to QUIC",
|
|
89
|
-
default=TunnelProtocol.DEFAULT.value,
|
|
90
|
-
)
|
|
91
|
-
@click.option("--usb/--no-usb", default=True, help="Enable usb monitoring")
|
|
92
|
-
@click.option("--wifi/--no-wifi", default=True, help="Enable wifi monitoring")
|
|
93
|
-
@click.option("--usbmux/--no-usbmux", default=True, help="Enable usbmux monitoring")
|
|
94
|
-
@click.option("--mobdev2/--no-mobdev2", default=True, help="Enable mobdev2 monitoring")
|
|
79
|
+
@cli.command("tunneld")
|
|
95
80
|
@sudo_required
|
|
96
81
|
def cli_tunneld(
|
|
97
|
-
host: str,
|
|
82
|
+
host: Annotated[str, typer.Option(help="Address to bind the tunneld server to.")] = TUNNELD_DEFAULT_ADDRESS[0],
|
|
83
|
+
port: Annotated[int, typer.Option(help="Port to bind the tunneld server to.")] = TUNNELD_DEFAULT_ADDRESS[1],
|
|
84
|
+
daemonize: Annotated[bool, typer.Option("--daemonize", "-d", help="Run tunneld in the background.")] = False,
|
|
85
|
+
protocol: Annotated[
|
|
86
|
+
TunnelProtocol,
|
|
87
|
+
typer.Option(
|
|
88
|
+
"--protocol",
|
|
89
|
+
"-p",
|
|
90
|
+
case_sensitive=False,
|
|
91
|
+
help="Transport protocol for tunneld (default: TCP on Python >=3.13, otherwise QUIC).",
|
|
92
|
+
),
|
|
93
|
+
] = TunnelProtocol.DEFAULT,
|
|
94
|
+
usb: Annotated[bool, typer.Option(help="Enable USB monitoring")] = True,
|
|
95
|
+
wifi: Annotated[bool, typer.Option(help="Enable WiFi monitoring")] = True,
|
|
96
|
+
usbmux: Annotated[bool, typer.Option(help="Enable usbmux monitoring")] = True,
|
|
97
|
+
mobdev2: Annotated[bool, typer.Option(help="Enable mobdev2 monitoring")] = True,
|
|
98
98
|
) -> None:
|
|
99
99
|
"""Start Tunneld service for remote tunneling"""
|
|
100
100
|
if not verify_tunnel_imports():
|
|
101
101
|
return
|
|
102
|
-
protocol = TunnelProtocol(protocol)
|
|
103
102
|
tunneld_runner = partial(
|
|
104
103
|
TunneldRunner.create,
|
|
105
104
|
host,
|
|
@@ -123,15 +122,16 @@ def cli_tunneld(
|
|
|
123
122
|
tunneld_runner()
|
|
124
123
|
|
|
125
124
|
|
|
126
|
-
@
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
@cli.command("browse")
|
|
126
|
+
def browse(
|
|
127
|
+
timeout: Annotated[float, typer.Option(help="Bonjour timeout (in seconds)")] = DEFAULT_BONJOUR_TIMEOUT,
|
|
128
|
+
) -> None:
|
|
129
129
|
"""browse RemoteXPC devices using bonjour"""
|
|
130
130
|
asyncio.run(cli_browse(timeout), debug=True)
|
|
131
131
|
|
|
132
132
|
|
|
133
|
-
@
|
|
134
|
-
def rsd_info(service_provider:
|
|
133
|
+
@cli.command("rsd-info")
|
|
134
|
+
def rsd_info(service_provider: RSDServiceProviderDep) -> None:
|
|
135
135
|
"""show info extracted from RSD peer"""
|
|
136
136
|
print_json(service_provider.peer_info)
|
|
137
137
|
|
|
@@ -153,32 +153,32 @@ async def tunnel_task(
|
|
|
153
153
|
if user_requested_colored_output():
|
|
154
154
|
if secrets is not None:
|
|
155
155
|
print(
|
|
156
|
-
|
|
157
|
-
+
|
|
156
|
+
typer.style("Secrets: ", bold=True, fg="magenta")
|
|
157
|
+
+ typer.style(secrets.name, bold=True, fg="white")
|
|
158
158
|
)
|
|
159
159
|
print(
|
|
160
|
-
|
|
161
|
-
+
|
|
160
|
+
typer.style("Identifier: ", bold=True, fg="yellow")
|
|
161
|
+
+ typer.style(service.remote_identifier, bold=True, fg="white")
|
|
162
162
|
)
|
|
163
163
|
print(
|
|
164
|
-
|
|
165
|
-
+
|
|
164
|
+
typer.style("Interface: ", bold=True, fg="yellow")
|
|
165
|
+
+ typer.style(tunnel_result.interface, bold=True, fg="white")
|
|
166
166
|
)
|
|
167
167
|
print(
|
|
168
|
-
|
|
169
|
-
+
|
|
168
|
+
typer.style("Protocol: ", bold=True, fg="yellow")
|
|
169
|
+
+ typer.style(tunnel_result.protocol, bold=True, fg="white")
|
|
170
170
|
)
|
|
171
171
|
print(
|
|
172
|
-
|
|
173
|
-
+
|
|
172
|
+
typer.style("RSD Address: ", bold=True, fg="yellow")
|
|
173
|
+
+ typer.style(tunnel_result.address, bold=True, fg="white")
|
|
174
174
|
)
|
|
175
175
|
print(
|
|
176
|
-
|
|
177
|
-
+
|
|
176
|
+
typer.style("RSD Port: ", bold=True, fg="yellow")
|
|
177
|
+
+ typer.style(tunnel_result.port, bold=True, fg="white")
|
|
178
178
|
)
|
|
179
179
|
print(
|
|
180
|
-
|
|
181
|
-
+
|
|
180
|
+
typer.style("Use the follow connection option:\n", bold=True, fg="yellow")
|
|
181
|
+
+ typer.style(f"--rsd {tunnel_result.address} {tunnel_result.port}", bold=True, fg="cyan")
|
|
182
182
|
)
|
|
183
183
|
else:
|
|
184
184
|
if secrets is not None:
|
|
@@ -196,7 +196,7 @@ async def tunnel_task(
|
|
|
196
196
|
|
|
197
197
|
async def start_tunnel_task(
|
|
198
198
|
connection_type: ConnectionType,
|
|
199
|
-
secrets: TextIO,
|
|
199
|
+
secrets: Optional[TextIO],
|
|
200
200
|
udid: Optional[str] = None,
|
|
201
201
|
script_mode: bool = False,
|
|
202
202
|
max_idle_timeout: float = MAX_IDLE_TIMEOUT,
|
|
@@ -224,52 +224,59 @@ async def start_tunnel_task(
|
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
|
|
227
|
-
@
|
|
228
|
-
@click.option(
|
|
229
|
-
"-t",
|
|
230
|
-
"--connection-type",
|
|
231
|
-
type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
|
|
232
|
-
default=ConnectionType.USB.value,
|
|
233
|
-
)
|
|
234
|
-
@click.option("--udid", help="UDID for a specific device to look for")
|
|
235
|
-
@click.option("--secrets", type=click.File("wt"), help="TLS keyfile for decrypting with Wireshark")
|
|
236
|
-
@click.option(
|
|
237
|
-
"--script-mode",
|
|
238
|
-
is_flag=True,
|
|
239
|
-
help="Show only HOST and port number to allow easy parsing from external shell scripts",
|
|
240
|
-
)
|
|
241
|
-
@click.option(
|
|
242
|
-
"--max-idle-timeout", type=click.FLOAT, default=MAX_IDLE_TIMEOUT, help="Maximum QUIC idle time (ping interval)"
|
|
243
|
-
)
|
|
244
|
-
@click.option(
|
|
245
|
-
"-p",
|
|
246
|
-
"--protocol",
|
|
247
|
-
type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
|
|
248
|
-
default=TunnelProtocol.DEFAULT.value,
|
|
249
|
-
)
|
|
227
|
+
@cli.command("start-tunnel")
|
|
250
228
|
@sudo_required
|
|
251
229
|
def cli_start_tunnel(
|
|
252
|
-
connection_type:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
230
|
+
connection_type: Annotated[
|
|
231
|
+
ConnectionType,
|
|
232
|
+
typer.Option(
|
|
233
|
+
"--connection-type",
|
|
234
|
+
"-t",
|
|
235
|
+
case_sensitive=False,
|
|
236
|
+
help="Connection interface to tunnel (USB, WiFi, etc.).",
|
|
237
|
+
),
|
|
238
|
+
] = ConnectionType.USB,
|
|
239
|
+
udid: Annotated[
|
|
240
|
+
Optional[str],
|
|
241
|
+
typer.Option(help="UDID for a specific device to look for"),
|
|
242
|
+
] = None,
|
|
243
|
+
secrets: Annotated[
|
|
244
|
+
Optional[Path],
|
|
245
|
+
typer.Option(help="File to write TLS secrets for Wireshark decryption."),
|
|
246
|
+
] = None,
|
|
247
|
+
script_mode: Annotated[
|
|
248
|
+
bool,
|
|
249
|
+
typer.Option(help="Print only HOST and port for scripts instead of formatted output."),
|
|
250
|
+
] = False,
|
|
251
|
+
max_idle_timeout: Annotated[
|
|
252
|
+
float,
|
|
253
|
+
typer.Option(help="Maximum idle time before QUIC keepalive pings are sent."),
|
|
254
|
+
] = MAX_IDLE_TIMEOUT,
|
|
255
|
+
protocol: Annotated[
|
|
256
|
+
TunnelProtocol,
|
|
257
|
+
typer.Option(
|
|
258
|
+
"--protocol",
|
|
259
|
+
"-p",
|
|
260
|
+
case_sensitive=False,
|
|
261
|
+
help="Transport protocol for the tunnel (default: TCP on Python >=3.13, otherwise QUIC).",
|
|
262
|
+
),
|
|
263
|
+
] = TunnelProtocol.DEFAULT,
|
|
258
264
|
) -> None:
|
|
259
265
|
"""start tunnel"""
|
|
260
266
|
if not verify_tunnel_imports():
|
|
261
267
|
return
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
with secrets.open("wt") if secrets is not None else nullcontext() as secrets_file:
|
|
269
|
+
asyncio.run(
|
|
270
|
+
start_tunnel_task(
|
|
271
|
+
connection_type,
|
|
272
|
+
secrets_file,
|
|
273
|
+
udid,
|
|
274
|
+
script_mode,
|
|
275
|
+
max_idle_timeout=max_idle_timeout,
|
|
276
|
+
protocol=protocol,
|
|
277
|
+
),
|
|
278
|
+
debug=True,
|
|
279
|
+
)
|
|
273
280
|
|
|
274
281
|
|
|
275
282
|
@dataclasses.dataclass
|
|
@@ -280,7 +287,7 @@ class RemotePairingManualPairingDevice:
|
|
|
280
287
|
identifier: str
|
|
281
288
|
|
|
282
289
|
|
|
283
|
-
async def start_remote_pair_task(device_name: str) -> None:
|
|
290
|
+
async def start_remote_pair_task(device_name: Optional[str]) -> None:
|
|
284
291
|
if start_tunnel is None:
|
|
285
292
|
raise NotImplementedError("failed to start the tunnel on your platform")
|
|
286
293
|
|
|
@@ -311,17 +318,20 @@ async def start_remote_pair_task(device_name: str) -> None:
|
|
|
311
318
|
await service.connect(autopair=True)
|
|
312
319
|
|
|
313
320
|
|
|
314
|
-
@
|
|
315
|
-
|
|
316
|
-
|
|
321
|
+
@cli.command("pair")
|
|
322
|
+
def cli_pair(
|
|
323
|
+
name: Annotated[
|
|
324
|
+
Optional[str],
|
|
325
|
+
typer.Option(help="Device name for a specific device to look for"),
|
|
326
|
+
] = None,
|
|
327
|
+
) -> None:
|
|
317
328
|
"""start remote pairing for devices which allow"""
|
|
318
329
|
asyncio.run(start_remote_pair_task(name), debug=True)
|
|
319
330
|
|
|
320
331
|
|
|
321
|
-
@
|
|
322
|
-
@click.argument("udid")
|
|
332
|
+
@cli.command("delete-pair")
|
|
323
333
|
@sudo_required
|
|
324
|
-
def cli_delete_pair(udid: str):
|
|
334
|
+
def cli_delete_pair(udid: str) -> None:
|
|
325
335
|
"""delete a pairing record"""
|
|
326
336
|
pair_record_path = get_home_folder() / f"{get_remote_pairing_record_filename(udid)}.{PAIRING_RECORD_EXT}"
|
|
327
337
|
pair_record_path.unlink()
|
|
@@ -332,8 +342,7 @@ async def cli_service_task(service_provider: RemoteServiceDiscoveryService, serv
|
|
|
332
342
|
service.shell()
|
|
333
343
|
|
|
334
344
|
|
|
335
|
-
@
|
|
336
|
-
|
|
337
|
-
def cli_service(service_provider: RemoteServiceDiscoveryService, service_name: str) -> None:
|
|
345
|
+
@cli.command("service")
|
|
346
|
+
def cli_service(service_provider: RSDServiceProviderDep, service_name: str) -> None:
|
|
338
347
|
"""start an ipython shell for interacting with given service"""
|
|
339
348
|
asyncio.run(cli_service_task(service_provider, service_name), debug=True)
|