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
@@ -0,0 +1,438 @@
1
+ import contextlib
2
+ import logging
3
+ import os
4
+ import posixpath
5
+ import shlex
6
+ from datetime import datetime
7
+ from enum import IntEnum
8
+ from pathlib import Path
9
+ from typing import Annotated, NamedTuple, Optional
10
+
11
+ import typer
12
+ from click.exceptions import BadParameter, MissingParameter, UsageError
13
+ from typer_injector import InjectingTyper
14
+
15
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json, user_requested_colored_output
16
+ from pymobiledevice3.cli.developer.dvt import core_profile_session, simulate_location, sysmon
17
+ from pymobiledevice3.exceptions import DvtDirListError, UnrecognizedSelectorError
18
+ from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService
19
+ from pymobiledevice3.services.dvt.instruments.activity_trace_tap import ActivityTraceTap, decode_message_format
20
+ from pymobiledevice3.services.dvt.instruments.application_listing import ApplicationListing
21
+ from pymobiledevice3.services.dvt.instruments.device_info import DeviceInfo
22
+ from pymobiledevice3.services.dvt.instruments.energy_monitor import EnergyMonitor
23
+ from pymobiledevice3.services.dvt.instruments.graphics import Graphics
24
+ from pymobiledevice3.services.dvt.instruments.network_monitor import ConnectionDetectionEvent, NetworkMonitor
25
+ from pymobiledevice3.services.dvt.instruments.notifications import Notifications
26
+ from pymobiledevice3.services.dvt.instruments.process_control import ProcessControl
27
+ from pymobiledevice3.services.dvt.instruments.screenshot import Screenshot
28
+ from pymobiledevice3.services.dvt.testmanaged.xcuitest import XCUITestService
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class MatchedProcessByPid(NamedTuple):
34
+ name: str
35
+ pid: int
36
+
37
+
38
+ cli = InjectingTyper(
39
+ name="dvt",
40
+ help="Drive DVT instrumentation APIs (process control, metrics, traces).",
41
+ no_args_is_help=True,
42
+ )
43
+
44
+ cli.add_typer(sysmon.cli)
45
+ cli.add_typer(core_profile_session.cli)
46
+ cli.add_typer(simulate_location.cli)
47
+
48
+
49
+ @cli.command("proclist")
50
+ def proclist(service_provider: ServiceProviderDep) -> None:
51
+ """Show processes (with start times) via DVT."""
52
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
53
+ processes = DeviceInfo(dvt).proclist()
54
+ for process in processes:
55
+ if "startDate" in process:
56
+ process["startDate"] = str(process["startDate"])
57
+
58
+ print_json(processes)
59
+
60
+
61
+ @cli.command("is-running-pid")
62
+ def is_running_pid(service_provider: ServiceProviderDep, pid: int) -> None:
63
+ """Check if a PID is currently running."""
64
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
65
+ print_json(DeviceInfo(dvt).is_running_pid(pid))
66
+
67
+
68
+ @cli.command("memlimitoff")
69
+ def memlimitoff(service_provider: ServiceProviderDep, pid: int) -> None:
70
+ """Disable jetsam memory limit for a PID."""
71
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
72
+ ProcessControl(dvt).disable_memory_limit_for_pid(pid)
73
+
74
+
75
+ @cli.command("applist")
76
+ def applist(service_provider: ServiceProviderDep) -> None:
77
+ """List installed applications via DVT."""
78
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
79
+ apps = ApplicationListing(dvt).applist()
80
+ print_json(apps)
81
+
82
+
83
+ class Signals(IntEnum):
84
+ """Platform-independent version of `signal.Signals`, allowing names to be used on Windows."""
85
+
86
+ HUP = 1
87
+ INT = 2
88
+ QUIT = 3
89
+ ILL = 4
90
+ TRAP = 5
91
+ ABRT = 6
92
+ EMT = 7
93
+ FPE = 8
94
+ KILL = 9
95
+ BUS = 10
96
+ SEGV = 11
97
+ SYS = 12
98
+ PIPE = 13
99
+ ALRM = 14
100
+ TERM = 15
101
+ URG = 16
102
+ STOP = 17
103
+ TSTP = 18
104
+ CONT = 19
105
+ CHLD = 20
106
+ TTIN = 21
107
+ TTOU = 22
108
+ IO = 23
109
+ XCPU = 24
110
+ XFSZ = 25
111
+ VTALRM = 26
112
+ PROF = 27
113
+ WINCH = 28
114
+ INFO = 29
115
+ USR1 = 30
116
+ USR2 = 31
117
+
118
+
119
+ @cli.command("signal")
120
+ def send_signal(
121
+ service_provider: ServiceProviderDep,
122
+ pid: int,
123
+ sig: Annotated[
124
+ Optional[int],
125
+ typer.Argument(),
126
+ ] = None,
127
+ signal_name: Annotated[
128
+ Optional[str],
129
+ typer.Option("--signal-name", "-s"),
130
+ ] = None,
131
+ ) -> None:
132
+ """Send a signal to a PID (choose numeric SIG or --signal-name)."""
133
+ if sig is not None and signal_name is not None:
134
+ raise UsageError(message="Cannot give SIG and SIGNAL-NAME together")
135
+
136
+ if signal_name is not None:
137
+ normalized_signal_name = signal_name.upper().removeprefix("SIG")
138
+ try:
139
+ sig = Signals[normalized_signal_name]
140
+ except KeyError:
141
+ raise BadParameter(f"{signal_name!r} is not a valid signal") from None
142
+ elif sig is not None:
143
+ try:
144
+ sig = Signals(sig)
145
+ except ValueError:
146
+ raise BadParameter(f"{sig} is not a valid signal") from None
147
+ else:
148
+ raise MissingParameter(param_type="argument|option", param_hint="'SIG|SIGNAL-NAME'")
149
+
150
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
151
+ ProcessControl(dvt).signal(pid, sig)
152
+
153
+
154
+ @cli.command("kill")
155
+ def kill(service_provider: ServiceProviderDep, pid: int) -> None:
156
+ """Kill a process by PID."""
157
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
158
+ ProcessControl(dvt).kill(pid)
159
+
160
+
161
+ @cli.command()
162
+ def process_id_for_bundle_id(service_provider: ServiceProviderDep, app_bundle_identifier: str) -> None:
163
+ """Get PID of a bundle identifier (only returns a valid value if its running)."""
164
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
165
+ print(ProcessControl(dvt).process_identifier_for_bundle_identifier(app_bundle_identifier))
166
+
167
+
168
+ def get_matching_processes(
169
+ service_provider: ServiceProviderDep,
170
+ name: Optional[str] = None,
171
+ bundle_identifier: Optional[str] = None,
172
+ ) -> list[MatchedProcessByPid]:
173
+ result: list[MatchedProcessByPid] = []
174
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
175
+ device_info = DeviceInfo(dvt)
176
+ for process in device_info.proclist():
177
+ current_name = process["name"]
178
+ current_bundle_identifier = process.get("bundleIdentifier", "")
179
+ pid = process["pid"]
180
+ if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or (
181
+ name is not None and name in current_name
182
+ ):
183
+ result.append(MatchedProcessByPid(name=current_name, pid=pid))
184
+ return result
185
+
186
+
187
+ @cli.command("pkill")
188
+ def pkill(
189
+ service_provider: ServiceProviderDep,
190
+ expression: str,
191
+ bundle: Annotated[
192
+ bool,
193
+ typer.Option(help="Treat given expression as a bundle-identifier instead of a process name"),
194
+ ] = False,
195
+ ) -> None:
196
+ """Kill all processes containing `expression` in their name."""
197
+ matching_name = expression if not bundle else None
198
+ matching_bundle_identifier = expression if bundle else None
199
+ matching_processes = get_matching_processes(
200
+ service_provider, name=matching_name, bundle_identifier=matching_bundle_identifier
201
+ )
202
+
203
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
204
+ process_control = ProcessControl(dvt)
205
+
206
+ for process in matching_processes:
207
+ logger.info(f"killing {process.name}({process.pid})")
208
+ process_control.kill(process.pid)
209
+
210
+
211
+ @cli.command("launch")
212
+ def launch(
213
+ service_provider: ServiceProviderDep,
214
+ arguments: str,
215
+ kill_existing: Annotated[
216
+ bool,
217
+ typer.Option(help="Whether to kill an existing instance of this process"),
218
+ ] = True,
219
+ suspended: Annotated[
220
+ bool,
221
+ typer.Option(help="Same as WaitForDebugger"),
222
+ ] = False,
223
+ env: Annotated[
224
+ Optional[list[str]],
225
+ typer.Option(
226
+ help="Environment variable to pass to process given as key=value (can be specified multiple times)"
227
+ ),
228
+ ] = None,
229
+ stream: bool = False,
230
+ ) -> None:
231
+ """Launch a process."""
232
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
233
+ parsed_arguments = shlex.split(arguments)
234
+ process_control = ProcessControl(dvt)
235
+ pid = process_control.launch(
236
+ bundle_id=parsed_arguments[0],
237
+ arguments=parsed_arguments[1:],
238
+ kill_existing=kill_existing,
239
+ start_suspended=suspended,
240
+ environment=dict(var.split("=", 1) for var in env or ()),
241
+ )
242
+ print(f"Process launched with pid {pid}")
243
+ while stream:
244
+ for output_received in process_control:
245
+ logging.getLogger(f"PID:{output_received.pid}").info(output_received.message.strip())
246
+
247
+
248
+ @cli.command("shell")
249
+ def dvt_shell(service_provider: ServiceProviderDep) -> None:
250
+ """Launch developer shell (used for pymobiledevice3 R&D)"""
251
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
252
+ dvt.shell()
253
+
254
+
255
+ def show_dirlist(device_info: DeviceInfo, dirname: str, recursive: bool = False) -> None:
256
+ try:
257
+ filenames = device_info.ls(dirname)
258
+ except DvtDirListError:
259
+ return
260
+
261
+ for filename in filenames:
262
+ filename = posixpath.join(dirname, filename)
263
+ print(filename)
264
+ if recursive:
265
+ show_dirlist(device_info, filename, recursive=recursive)
266
+
267
+
268
+ @cli.command("ls")
269
+ def ls(
270
+ service_provider: ServiceProviderDep,
271
+ path: Path,
272
+ recursive: Annotated[
273
+ bool,
274
+ typer.Option("--recursive", "-r"),
275
+ ] = False,
276
+ ) -> None:
277
+ """List directory"""
278
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
279
+ show_dirlist(DeviceInfo(dvt), str(path), recursive=recursive)
280
+
281
+
282
+ @cli.command("device-information")
283
+ def device_information(service_provider: ServiceProviderDep) -> None:
284
+ """Print system information"""
285
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
286
+ device_info = DeviceInfo(dvt)
287
+ info = {
288
+ "hardware": device_info.hardware_information(),
289
+ "network": device_info.network_information(),
290
+ "kernel-name": device_info.mach_kernel_name(),
291
+ "kpep-database": device_info.kpep_database(),
292
+ }
293
+ with contextlib.suppress(UnrecognizedSelectorError):
294
+ info["system"] = device_info.system_information()
295
+ print_json(info)
296
+
297
+
298
+ @cli.command("netstat")
299
+ def netstat(service_provider: ServiceProviderDep) -> None:
300
+ """Print information about current network activity."""
301
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, NetworkMonitor(dvt) as monitor:
302
+ for event in monitor:
303
+ if isinstance(event, ConnectionDetectionEvent):
304
+ local_host, local_port = event.local_address.split(":")
305
+ remote_host, remote_port = event.local_address.split(":")
306
+ logger.info(f"Connection detected: {local_host}:{local_port} -> {remote_host}:{remote_port}")
307
+
308
+
309
+ @cli.command("screenshot")
310
+ def dvt_screenshot(service_provider: ServiceProviderDep, out: Path) -> None:
311
+ """Take device screenshot"""
312
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
313
+ out.write_bytes(Screenshot(dvt).get_screenshot())
314
+
315
+
316
+ @cli.command("xcuitest")
317
+ def xcuitest(service_provider: ServiceProviderDep, bundle_id: str) -> None:
318
+ """
319
+ Start XCUITest
320
+
321
+ \b
322
+ Usage example:
323
+ \b python3 -m pymobiledevice3 developer dvt xcuitest com.facebook.WebDriverAgentRunner.xctrunner
324
+ """
325
+ XCUITestService(service_provider).run(bundle_id)
326
+
327
+
328
+ @cli.command("trace-codes")
329
+ def dvt_trace_codes(service_provider: ServiceProviderDep) -> None:
330
+ """Print KDebug trace codes."""
331
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
332
+ device_info = DeviceInfo(dvt)
333
+ print_json({hex(k): v for k, v in device_info.trace_codes().items()})
334
+
335
+
336
+ @cli.command("name-for-uid")
337
+ def dvt_name_for_uid(service_provider: ServiceProviderDep, uid: int) -> None:
338
+ """Print the assiciated username for the given uid."""
339
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
340
+ device_info = DeviceInfo(dvt)
341
+ print(device_info.name_for_uid(uid))
342
+
343
+
344
+ @cli.command("name-for-gid")
345
+ def dvt_name_for_gid(service_provider: ServiceProviderDep, gid: int) -> None:
346
+ """Print the assiciated group name for the given gid."""
347
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
348
+ device_info = DeviceInfo(dvt)
349
+ print(device_info.name_for_gid(gid))
350
+
351
+
352
+ @cli.command("oslog")
353
+ def dvt_oslog(service_provider: ServiceProviderDep, pid: int) -> None:
354
+ """Sniff device oslog (not very stable, but includes more data and normal syslog)"""
355
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, ActivityTraceTap(dvt) as tap:
356
+ for message in tap:
357
+ message_pid = message.process
358
+ # without message_type maybe signpost have event_type
359
+ message_type = (
360
+ message.message_type
361
+ if hasattr(message, "message_type")
362
+ else message.event_type
363
+ if hasattr(message, "event_type")
364
+ else "unknown"
365
+ )
366
+ sender_image_path = message.sender_image_path
367
+ image_name = os.path.basename(sender_image_path)
368
+ subsystem = message.subsystem
369
+ category = message.category
370
+ timestamp = datetime.now()
371
+
372
+ if pid is not None and message_pid != pid:
373
+ continue
374
+
375
+ formatted_message = decode_message_format(message.message) if message.message else message.name
376
+
377
+ if user_requested_colored_output():
378
+ timestamp = typer.style(str(timestamp), bold=True)
379
+ message_pid = typer.style(str(message_pid), "magenta")
380
+ subsystem = typer.style(subsystem, "green")
381
+ category = typer.style(category, "green")
382
+ image_name = typer.style(image_name, "yellow")
383
+ message_type = typer.style(message_type, "cyan")
384
+
385
+ print(
386
+ f"[{timestamp}][{subsystem}][{category}][{message_pid}][{image_name}] "
387
+ f"<{message_type}>: {formatted_message}"
388
+ )
389
+
390
+
391
+ @cli.command("energy")
392
+ def dvt_energy(service_provider: ServiceProviderDep, pid_list: list[str]) -> None:
393
+ """Monitor the energy consumption for given PIDs"""
394
+
395
+ if len(pid_list) == 0:
396
+ logger.error("pid_list must not be empty")
397
+ return
398
+
399
+ pid_int_list = [int(pid) for pid in pid_list]
400
+
401
+ with (
402
+ DvtSecureSocketProxyService(lockdown=service_provider) as dvt,
403
+ EnergyMonitor(dvt, pid_int_list) as energy_monitor,
404
+ ):
405
+ for telemetry in energy_monitor:
406
+ logger.info(telemetry)
407
+
408
+
409
+ @cli.command("notifications")
410
+ def dvt_notifications(service_provider: ServiceProviderDep) -> None:
411
+ """Monitor memory and app notifications"""
412
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Notifications(dvt) as notifications:
413
+ for notification in notifications:
414
+ logger.info(notification)
415
+
416
+
417
+ @cli.command("graphics")
418
+ def dvt_graphics(service_provider: ServiceProviderDep) -> None:
419
+ """Monitor graphics-related information"""
420
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Graphics(dvt) as graphics:
421
+ for stats in graphics:
422
+ logger.info(stats)
423
+
424
+
425
+ @cli.command("har")
426
+ def dvt_har(service_provider: ServiceProviderDep) -> None:
427
+ """
428
+ Enable har-logging
429
+
430
+ \b
431
+ For more information, please read:
432
+ \b https://github.com/doronz88/harlogger?tab=readme-ov-file#enable-http-instrumentation-method
433
+ """
434
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
435
+ print("> Press Ctrl-C to abort")
436
+ with ActivityTraceTap(dvt, enable_http_archive_logging=True) as tap:
437
+ while True:
438
+ tap.channel.receive_message()