pymobiledevice3 6.2.0__py3-none-any.whl → 7.0.0__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 (61) hide show
  1. pymobiledevice3/__main__.py +136 -44
  2. pymobiledevice3/_version.py +2 -2
  3. pymobiledevice3/bonjour.py +19 -20
  4. pymobiledevice3/cli/activation.py +24 -22
  5. pymobiledevice3/cli/afc.py +49 -41
  6. pymobiledevice3/cli/amfi.py +13 -18
  7. pymobiledevice3/cli/apps.py +71 -65
  8. pymobiledevice3/cli/backup.py +134 -93
  9. pymobiledevice3/cli/bonjour.py +31 -29
  10. pymobiledevice3/cli/cli_common.py +179 -232
  11. pymobiledevice3/cli/companion_proxy.py +12 -12
  12. pymobiledevice3/cli/crash.py +95 -52
  13. pymobiledevice3/cli/developer/__init__.py +62 -0
  14. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  15. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  16. pymobiledevice3/cli/developer/arbitration.py +50 -0
  17. pymobiledevice3/cli/developer/condition.py +33 -0
  18. pymobiledevice3/cli/developer/core_device.py +294 -0
  19. pymobiledevice3/cli/developer/debugserver.py +244 -0
  20. pymobiledevice3/cli/developer/dvt/__init__.py +387 -0
  21. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  22. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  23. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  24. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  25. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  26. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  27. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  28. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  29. pymobiledevice3/cli/idam.py +18 -22
  30. pymobiledevice3/cli/lockdown.py +70 -75
  31. pymobiledevice3/cli/mounter.py +99 -57
  32. pymobiledevice3/cli/notification.py +38 -26
  33. pymobiledevice3/cli/pcap.py +36 -20
  34. pymobiledevice3/cli/power_assertion.py +15 -16
  35. pymobiledevice3/cli/processes.py +11 -17
  36. pymobiledevice3/cli/profile.py +120 -75
  37. pymobiledevice3/cli/provision.py +27 -26
  38. pymobiledevice3/cli/remote.py +108 -99
  39. pymobiledevice3/cli/restore.py +134 -129
  40. pymobiledevice3/cli/springboard.py +50 -50
  41. pymobiledevice3/cli/syslog.py +138 -74
  42. pymobiledevice3/cli/usbmux.py +66 -27
  43. pymobiledevice3/cli/version.py +2 -5
  44. pymobiledevice3/cli/webinspector.py +149 -103
  45. pymobiledevice3/remote/remote_service_discovery.py +11 -10
  46. pymobiledevice3/restore/device.py +28 -4
  47. pymobiledevice3/service_connection.py +1 -1
  48. pymobiledevice3/services/mobilebackup2.py +4 -1
  49. pymobiledevice3/services/screenshot.py +2 -2
  50. pymobiledevice3/services/web_protocol/automation_session.py +4 -2
  51. pymobiledevice3/services/web_protocol/cdp_screencast.py +2 -1
  52. pymobiledevice3/services/web_protocol/element.py +3 -3
  53. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/METADATA +3 -2
  54. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/RECORD +58 -45
  55. pymobiledevice3/cli/completions.py +0 -50
  56. pymobiledevice3/cli/developer.py +0 -1645
  57. pymobiledevice3/cli/diagnostics.py +0 -110
  58. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/WHEEL +0 -0
  59. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/entry_points.txt +0 -0
  60. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/licenses/LICENSE +0 -0
  61. {pymobiledevice3-6.2.0.dist-info → pymobiledevice3-7.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+ from typing import Annotated
3
+
4
+ import typer
5
+ from typer_injector import InjectingTyper
6
+
7
+ from pymobiledevice3.cli.cli_common import OSUTILS, ServiceProviderDep
8
+ from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService
9
+ from pymobiledevice3.services.dvt.instruments.location_simulation import LocationSimulation
10
+
11
+ cli = InjectingTyper(
12
+ name="simulate-location",
13
+ help="Simulate device location by given input",
14
+ no_args_is_help=True,
15
+ )
16
+
17
+
18
+ @cli.command("clear")
19
+ def dvt_simulate_location_clear(service_provider: ServiceProviderDep) -> None:
20
+ """Clear currently simulated location"""
21
+ with DvtSecureSocketProxyService(service_provider) as dvt:
22
+ LocationSimulation(dvt).clear()
23
+
24
+
25
+ @cli.command("set")
26
+ def dvt_simulate_location_set(service_provider: ServiceProviderDep, latitude: float, longitude: float) -> None:
27
+ """
28
+ Set a simulated location.
29
+
30
+ \b
31
+ For example:
32
+ \b ... set -- 40.690008 -74.045843 for liberty island
33
+ """
34
+ with DvtSecureSocketProxyService(service_provider) as dvt:
35
+ LocationSimulation(dvt).set(latitude, longitude)
36
+ OSUTILS.wait_return()
37
+
38
+
39
+ @cli.command("play")
40
+ def dvt_simulate_location_play(
41
+ service_provider: ServiceProviderDep,
42
+ filename: Annotated[
43
+ Path,
44
+ typer.Argument(exists=True, file_okay=True, dir_okay=False),
45
+ ],
46
+ timing_randomness_range: int = 0,
47
+ disable_sleep: Annotated[bool, typer.Option()] = False,
48
+ ) -> None:
49
+ """Simulate inputs from a given .gpx file"""
50
+ with DvtSecureSocketProxyService(service_provider) as dvt:
51
+ LocationSimulation(dvt).play_gpx_file(
52
+ str(filename),
53
+ disable_sleep=disable_sleep,
54
+ timing_randomness_range=timing_randomness_range,
55
+ )
56
+ OSUTILS.wait_return()
@@ -0,0 +1,69 @@
1
+ import logging
2
+ from dataclasses import asdict
3
+ from typing import Annotated, Optional
4
+
5
+ import typer
6
+ from typer_injector import InjectingTyper
7
+
8
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
9
+ from pymobiledevice3.cli.developer.dvt.sysmon import process
10
+ from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService
11
+ from pymobiledevice3.services.dvt.instruments.sysmontap import Sysmontap
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ cli = InjectingTyper(
17
+ name="sysmon",
18
+ help="System monitor options.",
19
+ no_args_is_help=True,
20
+ )
21
+ cli.add_typer(process.cli)
22
+
23
+
24
+ @cli.command("system")
25
+ def sysmon_system(
26
+ service_provider: ServiceProviderDep,
27
+ fields: Annotated[
28
+ Optional[str],
29
+ typer.Option(
30
+ "--fields",
31
+ "-f",
32
+ help='field names separated by ",".',
33
+ ),
34
+ ] = None,
35
+ ) -> None:
36
+ """show current system stats."""
37
+
38
+ split_fields = fields.split(",") if fields is not None else None
39
+
40
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
41
+ sysmontap = Sysmontap(dvt)
42
+ with sysmontap as sysmon:
43
+ system = None
44
+ system_usage = None
45
+ system_usage_seen = False # Tracks if the first occurrence of SystemCPUUsage
46
+
47
+ for row in sysmon:
48
+ if "System" in row and system is None:
49
+ system = sysmon.system_attributes_cls(*row["System"])
50
+
51
+ if "SystemCPUUsage" in row:
52
+ if system_usage_seen:
53
+ system_usage = {
54
+ **row["SystemCPUUsage"],
55
+ "CPUCount": row["CPUCount"],
56
+ "EnabledCPUs": row["EnabledCPUs"],
57
+ }
58
+ else: # Ignore the first occurrence because first occurrence always gives a incorrect value - 100 or 0
59
+ system_usage_seen = True
60
+
61
+ if system and system_usage:
62
+ break
63
+
64
+ assert system is not None and system_usage is not None # for type checker
65
+
66
+ attrs_dict = {**asdict(system), **system_usage}
67
+ for name, value in attrs_dict.items():
68
+ if (split_fields is None) or (name in fields):
69
+ print(f"{name}: {value}")
@@ -0,0 +1,188 @@
1
+ import contextlib
2
+ import json
3
+ import logging
4
+ import time
5
+ from collections import namedtuple
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Annotated, Optional
9
+
10
+ import typer
11
+ from typer_injector import InjectingTyper
12
+
13
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, default_json_encoder, print_json
14
+ from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService
15
+ from pymobiledevice3.services.dvt.instruments.device_info import DeviceInfo
16
+ from pymobiledevice3.services.dvt.instruments.sysmontap import Sysmontap
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ cli = InjectingTyper(
22
+ name="process",
23
+ help="Process monitor options.",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+
28
+ @cli.command("monitor")
29
+ def sysmon_process_monitor(service_provider: ServiceProviderDep, threshold: float) -> None:
30
+ """monitor all most consuming processes by given cpuUsage threshold."""
31
+
32
+ Process = namedtuple("process", "pid name cpuUsage physFootprint")
33
+
34
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Sysmontap(dvt) as sysmon:
35
+ for process_snapshot in sysmon.iter_processes():
36
+ entries = []
37
+ for process in process_snapshot:
38
+ if (process["cpuUsage"] is not None) and (process["cpuUsage"] >= threshold):
39
+ entries.append(
40
+ Process(
41
+ pid=process["pid"],
42
+ name=process["name"],
43
+ cpuUsage=process["cpuUsage"],
44
+ physFootprint=process["physFootprint"],
45
+ )
46
+ )
47
+
48
+ logger.info(entries)
49
+
50
+
51
+ @cli.command("single")
52
+ def sysmon_process_single(
53
+ service_provider: ServiceProviderDep,
54
+ attributes: Annotated[
55
+ Optional[list[str]],
56
+ typer.Option(
57
+ "--attributes",
58
+ "-a",
59
+ help="filter processes by given attribute value given as key=value. Can be specified multiple times.",
60
+ ),
61
+ ] = None,
62
+ ) -> None:
63
+ """show a single snapshot of currently running processes."""
64
+
65
+ count = 0
66
+
67
+ result = []
68
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
69
+ device_info = DeviceInfo(dvt)
70
+
71
+ with Sysmontap(dvt) as sysmon:
72
+ for process_snapshot in sysmon.iter_processes():
73
+ count += 1
74
+
75
+ if count < 2:
76
+ # first sample doesn't contain an initialized value for cpuUsage
77
+ continue
78
+
79
+ for process in process_snapshot:
80
+ skip = False
81
+ if attributes is not None:
82
+ for filter_attr in attributes:
83
+ filter_attr, filter_value = filter_attr.split("=", 1)
84
+ if str(process[filter_attr]) != filter_value:
85
+ skip = True
86
+ break
87
+
88
+ if skip:
89
+ continue
90
+
91
+ # adding "artificially" the execName field
92
+ process["execName"] = device_info.execname_for_pid(process["pid"])
93
+ result.append(process)
94
+
95
+ # exit after single snapshot
96
+ break
97
+
98
+ print_json(result)
99
+
100
+
101
+ @cli.command("monitor-single")
102
+ def sysmon_process_monitor_single(
103
+ service_provider: ServiceProviderDep,
104
+ attributes: Annotated[
105
+ Optional[list[str]],
106
+ typer.Option(
107
+ "--attributes",
108
+ "-a",
109
+ help="filter processes by attribute (key=value). Multiple filters on same attribute use OR logic, different attributes use AND.",
110
+ ),
111
+ ] = None,
112
+ output: Annotated[
113
+ Optional[Path],
114
+ typer.Option(
115
+ "--output",
116
+ "-o",
117
+ help="output file path for JSONL format (optional, defaults to stdout)",
118
+ ),
119
+ ] = None,
120
+ interval: Annotated[
121
+ Optional[int],
122
+ typer.Option(
123
+ "--interval",
124
+ "-i",
125
+ help="minimum interval in milliseconds between outputs (optional)",
126
+ ),
127
+ ] = None,
128
+ duration: Annotated[
129
+ Optional[int],
130
+ typer.Option(
131
+ "--duration",
132
+ "-d",
133
+ help="maximum duration in milliseconds to run monitoring (optional)",
134
+ ),
135
+ ] = None,
136
+ ) -> None:
137
+ """Continuously monitor a single process with comprehensive metrics."""
138
+ count = 0
139
+ start_time = None
140
+
141
+ # Parse attributes into grouped filters: same attribute uses OR, different attributes use AND
142
+ parsed_filters: dict[str, list[str]] = {}
143
+ if attributes:
144
+ for raw in attributes:
145
+ key, value = raw.split("=", 1)
146
+ parsed_filters.setdefault(key, []).append(value)
147
+
148
+ def matches_filters(proc: dict) -> bool:
149
+ """Check if process matches all filter criteria."""
150
+ if not parsed_filters:
151
+ return True
152
+ return all(str(proc.get(key)) in values for key, values in parsed_filters.items())
153
+
154
+ with contextlib.ExitStack() as stack:
155
+ output_file = stack.enter_context(open(output, "w")) if output else None
156
+
157
+ dvt = stack.enter_context(DvtSecureSocketProxyService(lockdown=service_provider))
158
+ sysmon = stack.enter_context(Sysmontap(dvt))
159
+
160
+ for process_snapshot in sysmon.iter_processes():
161
+ count += 1
162
+
163
+ if count < 2:
164
+ continue
165
+
166
+ if start_time is None:
167
+ start_time = time.time()
168
+
169
+ if duration is not None:
170
+ elapsed_ms = (time.time() - start_time) * 1000
171
+ if elapsed_ms >= duration:
172
+ break
173
+
174
+ for process in process_snapshot:
175
+ if not matches_filters(process):
176
+ continue
177
+
178
+ process["timestamp"] = datetime.now(timezone.utc).isoformat()
179
+
180
+ if output_file:
181
+ json_output = json.dumps(process, default=default_json_encoder)
182
+ output_file.write(json_output + "\n")
183
+ output_file.flush()
184
+ else:
185
+ print_json(process)
186
+
187
+ if interval:
188
+ time.sleep(interval / 1000.0)
@@ -0,0 +1,108 @@
1
+ import asyncio
2
+ import logging
3
+ from pathlib import Path
4
+ from typing import Annotated, Optional
5
+
6
+ import typer
7
+ from ipsw_parser.dsc import create_device_support_layout, get_device_support_path
8
+ from packaging.version import Version
9
+ from typer_injector import InjectingTyper
10
+
11
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
12
+ from pymobiledevice3.exceptions import RSDRequiredError
13
+ from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
14
+ from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
15
+ from pymobiledevice3.services.dtfetchsymbols import DtFetchSymbols
16
+ from pymobiledevice3.services.remote_fetch_symbols import RemoteFetchSymbolsService
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ cli = InjectingTyper(
22
+ name="fetch-symbols",
23
+ help="Download the DSC (and dyld) from the device",
24
+ no_args_is_help=True,
25
+ )
26
+
27
+
28
+ async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) -> None:
29
+ if Version(service_provider.product_version) < Version("17.0"):
30
+ print_json(DtFetchSymbols(service_provider).list_files())
31
+ else:
32
+ if not isinstance(service_provider, RemoteServiceDiscoveryService):
33
+ raise RSDRequiredError(service_provider.identifier)
34
+
35
+ async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
36
+ print_json([f.file_path for f in await fetch_symbols.get_dsc_file_list()])
37
+
38
+
39
+ @cli.command("list")
40
+ def fetch_symbols_list(service_provider: ServiceProviderDep) -> None:
41
+ """list of files to be downloaded"""
42
+ asyncio.run(fetch_symbols_list_task(service_provider), debug=True)
43
+
44
+
45
+ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider, out: Optional[Path] = None) -> None:
46
+ should_create_device_support_layout = False
47
+ if out is None:
48
+ assert service_provider.product_type is not None # for type checker
49
+ out = get_device_support_path(
50
+ service_provider.product_type, service_provider.product_version, service_provider.product_build_version
51
+ )
52
+ should_create_device_support_layout = True
53
+
54
+ logger.info(f"Downloading DSC into: {out}")
55
+
56
+ out.mkdir(parents=True, exist_ok=True)
57
+
58
+ if Version(service_provider.product_version) < Version("17.0"):
59
+ fetch_symbols = DtFetchSymbols(service_provider)
60
+ files = fetch_symbols.list_files()
61
+
62
+ downloaded_files = set()
63
+
64
+ for i, file in enumerate(files):
65
+ if file.startswith("/"):
66
+ # trim root to allow relative download
67
+ file = file[1:]
68
+ file = out / file
69
+
70
+ if file not in downloaded_files:
71
+ # first time the file was seen in list, means we can safely remove any old copy if any
72
+ file.unlink(missing_ok=True)
73
+
74
+ downloaded_files.add(file)
75
+ file.parent.mkdir(parents=True, exist_ok=True)
76
+ with open(file, "ab") as f:
77
+ # same file may appear twice, so we'll need to append data into it
78
+ logger.info(f"writing to: {file}")
79
+ fetch_symbols.get_file(i, f)
80
+ else:
81
+ if not isinstance(service_provider, RemoteServiceDiscoveryService):
82
+ raise RSDRequiredError(service_provider.identifier)
83
+ async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
84
+ await fetch_symbols.download(out)
85
+
86
+ if should_create_device_support_layout:
87
+ assert service_provider.product_type is not None # for type checker
88
+ create_device_support_layout(
89
+ service_provider.product_type, service_provider.product_version, service_provider.product_build_version, out
90
+ )
91
+
92
+
93
+ @cli.command("download")
94
+ def fetch_symbols_download(
95
+ service_provider: ServiceProviderDep,
96
+ out: Annotated[
97
+ Optional[Path],
98
+ typer.Argument(dir_okay=True, file_okay=False),
99
+ ] = None,
100
+ ) -> None:
101
+ """
102
+ Fetches symbols from the given device and saves them into Xcode DeviceSupport directory.
103
+
104
+ This command downloads symbol data. Optionally, the user can specify an output directory where the data will
105
+ be stored. If no output directory is provided, the symbols will be downloaded into the Xcode directory directly
106
+ (DeviceSupport).
107
+ """
108
+ asyncio.run(fetch_symbols_download_task(service_provider, out), debug=True)
@@ -0,0 +1,51 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Annotated
4
+
5
+ import typer
6
+ from typer_injector import InjectingTyper
7
+
8
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
9
+ from pymobiledevice3.services.simulate_location import DtSimulateLocation
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ cli = InjectingTyper(
15
+ name="simulate-location",
16
+ help="Simulate GPS location (set, clear, or replay GPX routes).",
17
+ no_args_is_help=True,
18
+ )
19
+
20
+
21
+ @cli.command("clear")
22
+ def simulate_location_clear(service_provider: ServiceProviderDep) -> None:
23
+ """Stop location simulation and resume real GPS."""
24
+ DtSimulateLocation(service_provider).clear()
25
+
26
+
27
+ @cli.command("set")
28
+ def simulate_location_set(service_provider: ServiceProviderDep, latitude: float, longitude: float) -> None:
29
+ """
30
+ Set a fixed simulated location (latitude, longitude).
31
+ Example: `set 40.690008 -74.045843` (Liberty Island).
32
+ """
33
+ DtSimulateLocation(service_provider).set(latitude, longitude)
34
+
35
+
36
+ @cli.command("play")
37
+ def simulate_location_play(
38
+ service_provider: ServiceProviderDep,
39
+ filename: Annotated[
40
+ Path,
41
+ typer.Argument(exists=True, file_okay=True, dir_okay=False),
42
+ ],
43
+ timing_randomness_range: int,
44
+ disable_sleep: Annotated[bool, typer.Option()] = False,
45
+ ) -> None:
46
+ """Replay a GPX route; optionally disable sleeps and add timing jitter."""
47
+ DtSimulateLocation(service_provider).play_gpx_file(
48
+ str(filename),
49
+ disable_sleep=disable_sleep,
50
+ timing_randomness_range=timing_randomness_range,
51
+ )
@@ -0,0 +1,75 @@
1
+ import logging
2
+ from typing import Annotated, Optional
3
+
4
+ import typer
5
+ from typer_injector import InjectingTyper
6
+
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
8
+ from pymobiledevice3.cli.diagnostics import battery
9
+ from pymobiledevice3.lockdown import retry_create_using_usbmux
10
+ from pymobiledevice3.services.diagnostics import DiagnosticsService
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ cli = InjectingTyper(
16
+ name="diagnostics",
17
+ help="Reboot/Shutdown device or access other diagnostics services",
18
+ no_args_is_help=True,
19
+ )
20
+ cli.add_typer(battery.cli)
21
+
22
+
23
+ @cli.command("restart")
24
+ def diagnostics_restart(
25
+ service_provider: ServiceProviderDep,
26
+ reconnect: Annotated[
27
+ bool,
28
+ typer.Option(
29
+ "--reconnect",
30
+ "-r",
31
+ help="Wait until the device reconnects before finishing the operation.",
32
+ ),
33
+ ] = False,
34
+ ) -> None:
35
+ """Restart device"""
36
+ DiagnosticsService(lockdown=service_provider).restart()
37
+ if reconnect:
38
+ # Wait for the device to be available again
39
+ with retry_create_using_usbmux(None, serial=service_provider.udid):
40
+ print(f"Device Reconnected ({service_provider.udid}).")
41
+
42
+
43
+ @cli.command("shutdown")
44
+ def diagnostics_shutdown(service_provider: ServiceProviderDep) -> None:
45
+ """Shutdown device"""
46
+ DiagnosticsService(lockdown=service_provider).shutdown()
47
+
48
+
49
+ @cli.command("sleep")
50
+ def diagnostics_sleep(service_provider: ServiceProviderDep) -> None:
51
+ """Put device into sleep"""
52
+ DiagnosticsService(lockdown=service_provider).sleep()
53
+
54
+
55
+ @cli.command("info")
56
+ def diagnostics_info(service_provider: ServiceProviderDep) -> None:
57
+ """Get diagnostics info"""
58
+ print_json(DiagnosticsService(lockdown=service_provider).info())
59
+
60
+
61
+ @cli.command("ioregistry")
62
+ def diagnostics_ioregistry(
63
+ service_provider: ServiceProviderDep,
64
+ plane: Annotated[Optional[str], typer.Option()] = None,
65
+ name: Annotated[Optional[str], typer.Option()] = None,
66
+ ioclass: Annotated[Optional[str], typer.Option()] = None,
67
+ ) -> None:
68
+ """Get ioregistry info"""
69
+ print_json(DiagnosticsService(lockdown=service_provider).ioregistry(plane=plane, name=name, ioclass=ioclass))
70
+
71
+
72
+ @cli.command("mg")
73
+ def diagnostics_mg(service_provider: ServiceProviderDep, keys: Optional[list[str]] = None) -> None:
74
+ """Get MobileGestalt key values from given list. If empty, return all known."""
75
+ print_json(DiagnosticsService(lockdown=service_provider).mobilegestalt(keys=keys))
@@ -0,0 +1,47 @@
1
+ import logging
2
+ import time
3
+
4
+ from typer_injector import InjectingTyper
5
+
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
7
+ from pymobiledevice3.services.diagnostics import DiagnosticsService
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ cli = InjectingTyper(
13
+ name="battery",
14
+ help="Battery options",
15
+ no_args_is_help=True,
16
+ )
17
+
18
+
19
+ @cli.command("single")
20
+ def diagnostics_battery_single(service_provider: ServiceProviderDep) -> None:
21
+ """get single snapshot of battery data"""
22
+ raw_info = DiagnosticsService(lockdown=service_provider).get_battery()
23
+ print_json(raw_info)
24
+
25
+
26
+ @cli.command("monitor")
27
+ def diagnostics_battery_monitor(service_provider: ServiceProviderDep) -> None:
28
+ """monitor battery usage"""
29
+ diagnostics = DiagnosticsService(lockdown=service_provider)
30
+ while True:
31
+ raw_info = diagnostics.get_battery()
32
+ info = {
33
+ "InstantAmperage": raw_info.get("InstantAmperage"),
34
+ "Temperature": raw_info.get("Temperature"),
35
+ "Voltage": raw_info.get("Voltage"),
36
+ "IsCharging": raw_info.get("IsCharging"),
37
+ "CurrentCapacity": raw_info.get("CurrentCapacity"),
38
+ }
39
+ logger.info(info)
40
+ time.sleep(1)
41
+
42
+
43
+ @cli.command("wifi")
44
+ def diagnostics_wifi(service_provider: ServiceProviderDep) -> None:
45
+ """Query WiFi info from IORegistry"""
46
+ raw_info = DiagnosticsService(lockdown=service_provider).get_wifi()
47
+ print_json(raw_info)
@@ -1,46 +1,42 @@
1
1
  import logging
2
+ from textwrap import dedent
2
3
 
3
- import click
4
+ from typer_injector import InjectingTyper
4
5
 
5
- from pymobiledevice3.cli.cli_common import Command, print_json
6
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
7
7
  from pymobiledevice3.services.idam import IDAMService
8
8
 
9
9
  logger = logging.getLogger(__name__)
10
10
 
11
11
 
12
- @click.group()
13
- def cli() -> None:
14
- pass
12
+ cli = InjectingTyper(
13
+ name="idam",
14
+ help=dedent("""\
15
+ Access IDAM (Inter-Device Audio and MIDI) configuration
15
16
 
17
+ For more info refer to:
18
+ <https://www.youtube.com/watch?v=IXmP938brnc>
19
+ """),
20
+ no_args_is_help=True,
21
+ )
16
22
 
17
- @cli.group("idam")
18
- def idam() -> None:
19
- """
20
- Access IDAM (Inter-Device Audio and MIDI) configuration
21
23
 
22
- For more info refer to:
23
- <https://www.youtube.com/watch?v=IXmP938brnc>
24
- """
25
- pass
26
-
27
-
28
- @idam.command(cls=Command)
29
- def configuration_inquiry(service_provider: LockdownServiceProvider) -> None:
24
+ @cli.command()
25
+ def configuration_inquiry(service_provider: ServiceProviderDep) -> None:
30
26
  """Inquiry IDAM configuration"""
31
27
  with IDAMService(service_provider) as idam:
32
28
  print_json(idam.configuration_inquiry())
33
29
 
34
30
 
35
- @idam.command(cls=Command)
36
- def enable(service_provider: LockdownServiceProvider) -> None:
31
+ @cli.command()
32
+ def enable(service_provider: ServiceProviderDep) -> None:
37
33
  """Enable IDAM"""
38
34
  with IDAMService(service_provider) as idam:
39
35
  idam.set_idam_configuration(True)
40
36
 
41
37
 
42
- @idam.command(cls=Command)
43
- def disable(service_provider: LockdownServiceProvider) -> None:
38
+ @cli.command()
39
+ def disable(service_provider: ServiceProviderDep) -> None:
44
40
  """Disable IDAM"""
45
41
  with IDAMService(service_provider) as idam:
46
42
  idam.set_idam_configuration(False)