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.
Files changed (79) hide show
  1. misc/understanding_idevice_protocol_layers.md +10 -5
  2. pymobiledevice3/__main__.py +171 -46
  3. pymobiledevice3/_version.py +2 -2
  4. pymobiledevice3/bonjour.py +22 -21
  5. pymobiledevice3/cli/activation.py +24 -22
  6. pymobiledevice3/cli/afc.py +49 -41
  7. pymobiledevice3/cli/amfi.py +13 -18
  8. pymobiledevice3/cli/apps.py +71 -65
  9. pymobiledevice3/cli/backup.py +134 -93
  10. pymobiledevice3/cli/bonjour.py +31 -29
  11. pymobiledevice3/cli/cli_common.py +175 -232
  12. pymobiledevice3/cli/companion_proxy.py +12 -12
  13. pymobiledevice3/cli/crash.py +95 -52
  14. pymobiledevice3/cli/developer/__init__.py +62 -0
  15. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  16. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  17. pymobiledevice3/cli/developer/arbitration.py +50 -0
  18. pymobiledevice3/cli/developer/condition.py +33 -0
  19. pymobiledevice3/cli/developer/core_device.py +294 -0
  20. pymobiledevice3/cli/developer/debugserver.py +244 -0
  21. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  22. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  23. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  24. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  25. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  26. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  27. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  28. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  29. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  30. pymobiledevice3/cli/idam.py +42 -0
  31. pymobiledevice3/cli/lockdown.py +70 -75
  32. pymobiledevice3/cli/mounter.py +99 -57
  33. pymobiledevice3/cli/notification.py +38 -26
  34. pymobiledevice3/cli/pcap.py +36 -20
  35. pymobiledevice3/cli/power_assertion.py +15 -16
  36. pymobiledevice3/cli/processes.py +11 -17
  37. pymobiledevice3/cli/profile.py +120 -75
  38. pymobiledevice3/cli/provision.py +27 -26
  39. pymobiledevice3/cli/remote.py +109 -100
  40. pymobiledevice3/cli/restore.py +134 -129
  41. pymobiledevice3/cli/springboard.py +50 -50
  42. pymobiledevice3/cli/syslog.py +145 -65
  43. pymobiledevice3/cli/usbmux.py +66 -27
  44. pymobiledevice3/cli/version.py +2 -5
  45. pymobiledevice3/cli/webinspector.py +232 -156
  46. pymobiledevice3/exceptions.py +6 -2
  47. pymobiledevice3/lockdown.py +5 -1
  48. pymobiledevice3/lockdown_service_provider.py +5 -0
  49. pymobiledevice3/remote/remote_service_discovery.py +18 -10
  50. pymobiledevice3/restore/device.py +28 -4
  51. pymobiledevice3/restore/restore.py +2 -2
  52. pymobiledevice3/service_connection.py +15 -12
  53. pymobiledevice3/services/afc.py +731 -220
  54. pymobiledevice3/services/device_link.py +45 -31
  55. pymobiledevice3/services/idam.py +20 -0
  56. pymobiledevice3/services/lockdown_service.py +12 -9
  57. pymobiledevice3/services/mobile_config.py +1 -0
  58. pymobiledevice3/services/mobilebackup2.py +6 -3
  59. pymobiledevice3/services/os_trace.py +97 -55
  60. pymobiledevice3/services/remote_fetch_symbols.py +13 -8
  61. pymobiledevice3/services/screenshot.py +2 -2
  62. pymobiledevice3/services/web_protocol/alert.py +8 -8
  63. pymobiledevice3/services/web_protocol/automation_session.py +87 -79
  64. pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  65. pymobiledevice3/services/web_protocol/driver.py +71 -70
  66. pymobiledevice3/services/web_protocol/element.py +58 -56
  67. pymobiledevice3/services/web_protocol/selenium_api.py +47 -47
  68. pymobiledevice3/services/web_protocol/session_protocol.py +3 -2
  69. pymobiledevice3/services/web_protocol/switch_to.py +23 -19
  70. pymobiledevice3/services/webinspector.py +42 -67
  71. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +5 -3
  72. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/RECORD +76 -61
  73. pymobiledevice3/cli/completions.py +0 -50
  74. pymobiledevice3/cli/developer.py +0 -1539
  75. pymobiledevice3/cli/diagnostics.py +0 -110
  76. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +0 -0
  77. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  78. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/licenses/LICENSE +0 -0
  79. {pymobiledevice3-5.0.4.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -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 typing import Optional, TextIO
8
+ from pathlib import Path
9
+ from typing import Annotated, Optional, TextIO
8
10
 
9
- import click
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
- BaseCommand,
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
- @click.group()
70
- def cli() -> None:
71
- pass
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
- @remote_cli.command("tunneld", cls=BaseCommand)
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, port: int, daemonize: bool, protocol: str, usb: bool, wifi: bool, usbmux: bool, mobdev2: bool
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
- @remote_cli.command("browse", cls=BaseCommand)
127
- @click.option("--timeout", type=click.FLOAT, default=DEFAULT_BONJOUR_TIMEOUT, help="Bonjour timeout (in seconds)")
128
- def browse(timeout: float) -> None:
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
- @remote_cli.command("rsd-info", cls=RSDCommand)
134
- def rsd_info(service_provider: RemoteServiceDiscoveryService):
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
- click.style("Secrets: ", bold=True, fg="magenta")
157
- + click.style(secrets.name, bold=True, fg="white")
156
+ typer.style("Secrets: ", bold=True, fg="magenta")
157
+ + typer.style(secrets.name, bold=True, fg="white")
158
158
  )
159
159
  print(
160
- click.style("Identifier: ", bold=True, fg="yellow")
161
- + click.style(service.remote_identifier, bold=True, fg="white")
160
+ typer.style("Identifier: ", bold=True, fg="yellow")
161
+ + typer.style(service.remote_identifier, bold=True, fg="white")
162
162
  )
163
163
  print(
164
- click.style("Interface: ", bold=True, fg="yellow")
165
- + click.style(tunnel_result.interface, bold=True, fg="white")
164
+ typer.style("Interface: ", bold=True, fg="yellow")
165
+ + typer.style(tunnel_result.interface, bold=True, fg="white")
166
166
  )
167
167
  print(
168
- click.style("Protocol: ", bold=True, fg="yellow")
169
- + click.style(tunnel_result.protocol, bold=True, fg="white")
168
+ typer.style("Protocol: ", bold=True, fg="yellow")
169
+ + typer.style(tunnel_result.protocol, bold=True, fg="white")
170
170
  )
171
171
  print(
172
- click.style("RSD Address: ", bold=True, fg="yellow")
173
- + click.style(tunnel_result.address, bold=True, fg="white")
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
- click.style("RSD Port: ", bold=True, fg="yellow")
177
- + click.style(tunnel_result.port, bold=True, fg="white")
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
- click.style("Use the follow connection option:\n", bold=True, fg="yellow")
181
- + click.style(f"--rsd {tunnel_result.address} {tunnel_result.port}", bold=True, fg="cyan")
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
- @remote_cli.command("start-tunnel", cls=BaseCommand)
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: ConnectionType,
253
- udid: Optional[str],
254
- secrets: TextIO,
255
- script_mode: bool,
256
- max_idle_timeout: float,
257
- protocol: str,
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
- asyncio.run(
263
- start_tunnel_task(
264
- ConnectionType(connection_type),
265
- secrets,
266
- udid,
267
- script_mode,
268
- max_idle_timeout=max_idle_timeout,
269
- protocol=TunnelProtocol(protocol),
270
- ),
271
- debug=True,
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
- @remote_cli.command("pair", cls=BaseCommand)
315
- @click.option("--name", help="Device name for a specific device to look for")
316
- def cli_pair(name: Optional[str]) -> None:
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
- @remote_cli.command("delete-pair", cls=BaseCommand)
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
- @remote_cli.command("service", cls=RSDCommand)
336
- @click.argument("service_name")
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)