pymobiledevice3 4.14.6__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 (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -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)
@@ -0,0 +1,42 @@
1
+ import logging
2
+ from textwrap import dedent
3
+
4
+ from typer_injector import InjectingTyper
5
+
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
7
+ from pymobiledevice3.services.idam import IDAMService
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ cli = InjectingTyper(
13
+ name="idam",
14
+ help=dedent("""\
15
+ Access IDAM (Inter-Device Audio and MIDI) configuration
16
+
17
+ For more info refer to:
18
+ <https://www.youtube.com/watch?v=IXmP938brnc>
19
+ """),
20
+ no_args_is_help=True,
21
+ )
22
+
23
+
24
+ @cli.command()
25
+ def configuration_inquiry(service_provider: ServiceProviderDep) -> None:
26
+ """Inquiry IDAM configuration"""
27
+ with IDAMService(service_provider) as idam:
28
+ print_json(idam.configuration_inquiry())
29
+
30
+
31
+ @cli.command()
32
+ def enable(service_provider: ServiceProviderDep) -> None:
33
+ """Enable IDAM"""
34
+ with IDAMService(service_provider) as idam:
35
+ idam.set_idam_configuration(True)
36
+
37
+
38
+ @cli.command()
39
+ def disable(service_provider: ServiceProviderDep) -> None:
40
+ """Disable IDAM"""
41
+ with IDAMService(service_provider) as idam:
42
+ idam.set_idam_configuration(False)
@@ -2,13 +2,13 @@ import asyncio
2
2
  import logging
3
3
  import plistlib
4
4
  from pathlib import Path
5
- from typing import Optional
5
+ from typing import Annotated, Literal, Optional
6
6
 
7
- import click
7
+ import typer
8
+ from typer_injector import InjectingTyper
8
9
 
9
- from pymobiledevice3.cli.cli_common import Command, CommandWithoutAutopair, print_json, sudo_required
10
+ from pymobiledevice3.cli.cli_common import NoAutoPairServiceProviderDep, ServiceProviderDep, print_json, sudo_required
10
11
  from pymobiledevice3.cli.remote import tunnel_task
11
- from pymobiledevice3.lockdown import LockdownClient
12
12
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
13
13
  from pymobiledevice3.remote.common import TunnelProtocol
14
14
  from pymobiledevice3.remote.tunnel_service import CoreDeviceTunnelProxy
@@ -17,169 +17,174 @@ from pymobiledevice3.services.heartbeat import HeartbeatService
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
- @click.group()
21
- def cli() -> None:
22
- pass
20
+ cli = InjectingTyper(
21
+ name="lockdown",
22
+ help="Pair/Unpair device or access other lockdown services",
23
+ no_args_is_help=True,
24
+ )
23
25
 
24
26
 
25
- @cli.group('lockdown')
26
- def lockdown_group() -> None:
27
- """ Pair/Unpair device or access other lockdown services """
28
- pass
29
-
30
-
31
- @lockdown_group.command('recovery', cls=Command)
32
- def lockdown_recovery(service_provider: LockdownClient):
33
- """ enter recovery """
27
+ @cli.command("recovery")
28
+ def lockdown_recovery(service_provider: ServiceProviderDep) -> None:
29
+ """enter recovery"""
34
30
  print_json(service_provider.enter_recovery())
35
31
 
36
32
 
37
- @lockdown_group.command('service', cls=Command)
38
- @click.argument('service_name')
39
- def lockdown_service(service_provider: LockdownServiceProvider, service_name):
40
- """ send-receive raw service messages with a given service name"""
33
+ @cli.command("service")
34
+ def lockdown_service(service_provider: ServiceProviderDep, service_name: str) -> None:
35
+ """send-receive raw service messages with a given service name"""
41
36
  service_provider.start_lockdown_service(service_name).shell()
42
37
 
43
38
 
44
- @lockdown_group.command('developer-service', cls=Command)
45
- @click.argument('service_name')
46
- def lockdown_developer_service(service_provider: LockdownServiceProvider, service_name):
47
- """ send-receive raw service messages with a given developer service name """
39
+ @cli.command("developer-service")
40
+ def lockdown_developer_service(service_provider: ServiceProviderDep, service_name: str) -> None:
41
+ """send-receive raw service messages with a given developer service name"""
48
42
  service_provider.start_lockdown_developer_service(service_name).shell()
49
43
 
50
44
 
51
- @lockdown_group.command('info', cls=Command)
52
- def lockdown_info(service_provider: LockdownServiceProvider):
53
- """ query all lockdown values """
45
+ @cli.command("info")
46
+ def lockdown_info(service_provider: ServiceProviderDep) -> None:
47
+ """query all lockdown values"""
54
48
  print_json(service_provider.all_values)
55
49
 
56
50
 
57
- @lockdown_group.command('get', cls=Command)
58
- @click.argument('domain', required=False)
59
- @click.argument('key', required=False)
60
- def lockdown_get(service_provider: LockdownClient, domain, key):
61
- """ query lockdown values by their domain and key names """
51
+ @cli.command("get")
52
+ def lockdown_get(service_provider: ServiceProviderDep, domain: Optional[str] = None, key: Optional[str] = None) -> None:
53
+ """query lockdown values by their domain and key names"""
62
54
  print_json(service_provider.get_value(domain=domain, key=key))
63
55
 
64
56
 
65
- @lockdown_group.command('set', cls=Command)
66
- @click.argument('value')
67
- @click.argument('domain', required=False)
68
- @click.argument('key', required=False)
69
- def lockdown_set(service_provider: LockdownClient, value, domain, key):
70
- """ set a lockdown value using python's eval() """
57
+ @cli.command("set")
58
+ def lockdown_set(
59
+ service_provider: ServiceProviderDep,
60
+ value: str,
61
+ domain: Optional[str] = None,
62
+ key: Optional[str] = None,
63
+ ) -> None:
64
+ """set a lockdown value using python's eval()"""
71
65
  print_json(service_provider.set_value(value=eval(value), domain=domain, key=key))
72
66
 
73
67
 
74
- @lockdown_group.command('remove', cls=Command)
75
- @click.argument('domain')
76
- @click.argument('key')
77
- def lockdown_remove(service_provider: LockdownClient, domain, key):
78
- """ remove a domain/key pair """
68
+ @cli.command("remove")
69
+ def lockdown_remove(service_provider: ServiceProviderDep, domain: str, key: str) -> None:
70
+ """remove a domain/key pair"""
79
71
  print_json(service_provider.remove_value(domain=domain, key=key))
80
72
 
81
73
 
82
- @lockdown_group.command('unpair', cls=CommandWithoutAutopair)
83
- @click.argument('host_id', required=False)
84
- def lockdown_unpair(service_provider: LockdownClient, host_id: str = None):
85
- """ unpair from connected device """
74
+ @cli.command("unpair")
75
+ def lockdown_unpair(service_provider: NoAutoPairServiceProviderDep, host_id: Optional[str] = None) -> None:
76
+ """unpair from connected device"""
86
77
  service_provider.unpair(host_id=host_id)
87
78
 
88
79
 
89
- @lockdown_group.command('pair', cls=CommandWithoutAutopair)
90
- def lockdown_pair(service_provider: LockdownClient):
91
- """ pair device """
80
+ @cli.command("pair")
81
+ def lockdown_pair(service_provider: NoAutoPairServiceProviderDep) -> None:
82
+ """pair device"""
92
83
  service_provider.pair()
93
84
 
94
85
 
95
- @lockdown_group.command('pair-supervised', cls=CommandWithoutAutopair)
96
- @click.argument('keybag', type=click.Path(file_okay=True, dir_okay=False, exists=True))
97
- def lockdown_pair_supervised(service_provider: LockdownClient, keybag: str) -> None:
98
- """ pair supervised device """
99
- service_provider.pair_supervised(Path(keybag))
86
+ @cli.command("pair-supervised")
87
+ def lockdown_pair_supervised(
88
+ service_provider: NoAutoPairServiceProviderDep,
89
+ keybag: Annotated[
90
+ Path,
91
+ typer.Argument(file_okay=True, dir_okay=False, exists=True),
92
+ ],
93
+ ) -> None:
94
+ """pair supervised device"""
95
+ service_provider.pair_supervised(keybag)
100
96
 
101
97
 
102
- @lockdown_group.command('save-pair-record', cls=CommandWithoutAutopair)
103
- @click.argument('output', type=click.File('wb'))
104
- def lockdown_save_pair_record(service_provider: LockdownClient, output):
105
- """ save pair record to specified location """
98
+ @cli.command("save-pair-record")
99
+ def lockdown_save_pair_record(service_provider: NoAutoPairServiceProviderDep, output: Path) -> None:
100
+ """save pair record to specified location"""
106
101
  if service_provider.pair_record is None:
107
- logger.error('no pairing record was found')
102
+ logger.error("no pairing record was found")
108
103
  return
109
- plistlib.dump(service_provider.pair_record, output)
104
+ output.write_bytes(plistlib.dumps(service_provider.pair_record))
110
105
 
111
106
 
112
- @lockdown_group.command('date', cls=Command)
113
- def lockdown_date(service_provider: LockdownClient):
114
- """ get device date """
107
+ @cli.command("date")
108
+ def lockdown_date(service_provider: ServiceProviderDep) -> None:
109
+ """get device date"""
115
110
  print(service_provider.date)
116
111
 
117
112
 
118
- @lockdown_group.command('heartbeat', cls=Command)
119
- def lockdown_heartbeat(service_provider: LockdownClient):
120
- """ start heartbeat service """
113
+ @cli.command("heartbeat")
114
+ def lockdown_heartbeat(service_provider: ServiceProviderDep) -> None:
115
+ """start heartbeat service"""
121
116
  HeartbeatService(service_provider).start()
122
117
 
123
118
 
124
- @lockdown_group.command('language', cls=Command)
125
- @click.argument('language', required=False)
126
- def lockdown_language(service_provider: LockdownClient, language: Optional[str]) -> None:
127
- """ Get/Set current language settings """
119
+ @cli.command("language")
120
+ def lockdown_language(service_provider: ServiceProviderDep, language: Optional[str] = None) -> None:
121
+ """Get/Set current language settings"""
128
122
  if language is not None:
129
123
  service_provider.set_language(language)
130
124
  print_json(service_provider.language)
131
125
 
132
126
 
133
- @lockdown_group.command('locale', cls=Command)
134
- @click.argument('locale', required=False)
135
- def lockdown_locale(service_provider: LockdownClient, locale: Optional[str]) -> None:
136
- """ Get/Set current language settings """
127
+ @cli.command("locale")
128
+ def lockdown_locale(service_provider: ServiceProviderDep, locale: Optional[str] = None) -> None:
129
+ """Get/Set current language settings"""
137
130
  if locale is not None:
138
131
  service_provider.set_locale(locale)
139
132
  print_json(service_provider.locale)
140
133
 
141
134
 
142
- @lockdown_group.command('device-name', cls=Command)
143
- @click.argument('new_name', required=False)
144
- def lockdown_device_name(service_provider: LockdownClient, new_name):
145
- """ get/set current device name """
135
+ @cli.command("device-name")
136
+ def lockdown_device_name(service_provider: ServiceProviderDep, new_name: Optional[str] = None) -> None:
137
+ """get/set current device name"""
146
138
  if new_name:
147
- service_provider.set_value(new_name, key='DeviceName')
139
+ service_provider.set_value(new_name, key="DeviceName")
148
140
  else:
149
- print(f'{service_provider.get_value(key="DeviceName")}')
141
+ print(f"{service_provider.get_value(key='DeviceName')}")
150
142
 
151
143
 
152
- @lockdown_group.command('wifi-connections', cls=Command)
153
- @click.argument('state', type=click.Choice(['on', 'off']), required=False)
154
- def lockdown_wifi_connections(service_provider: LockdownClient, state):
155
- """ get/set wifi connections state """
144
+ @cli.command("wifi-connections")
145
+ def lockdown_wifi_connections(
146
+ service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
147
+ ) -> None:
148
+ """get/set wifi connections state"""
156
149
  if not state:
157
150
  # show current state
158
- print_json(service_provider.get_value(domain='com.apple.mobile.wireless_lockdown'))
151
+ print_json(service_provider.get_value(domain="com.apple.mobile.wireless_lockdown"))
159
152
  else:
160
153
  # enable/disable
161
- service_provider.enable_wifi_connections = state == 'on'
154
+ service_provider.enable_wifi_connections = state == "on"
162
155
 
163
156
 
164
- @lockdown_group.command('start-tunnel', cls=Command)
165
- @click.option('--script-mode', is_flag=True,
166
- help='Show only HOST and port number to allow easy parsing from external shell scripts')
167
- @sudo_required
168
- def cli_start_tunnel(
169
- service_provider: LockdownServiceProvider, script_mode: bool) -> None:
170
- """ start tunnel """
171
- service = CoreDeviceTunnelProxy(service_provider)
172
- asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None, protocol=TunnelProtocol.TCP), debug=True)
157
+ async def async_cli_start_tunnel(service_provider: LockdownServiceProvider, script_mode: bool) -> None:
158
+ await tunnel_task(
159
+ await CoreDeviceTunnelProxy.create(service_provider),
160
+ script_mode=script_mode,
161
+ secrets=None,
162
+ protocol=TunnelProtocol.TCP,
163
+ )
173
164
 
174
165
 
175
- @lockdown_group.command('assistive-touch', cls=Command)
176
- @click.argument('state', type=click.Choice(['on', 'off']), required=False)
177
- def lockdown_assistive_touch(service_provider: LockdownClient, state: str) -> None:
178
- """ get/set assistive touch icon state (visibility) """
166
+ @cli.command("start-tunnel")
167
+ @sudo_required
168
+ def cli_start_tunnel(
169
+ service_provider: ServiceProviderDep,
170
+ script_mode: Annotated[
171
+ bool,
172
+ typer.Option(help="Show only HOST and port number to allow easy parsing from external shell scripts"),
173
+ ] = False,
174
+ ) -> None:
175
+ """start tunnel"""
176
+ asyncio.run(async_cli_start_tunnel(service_provider, script_mode), debug=True)
177
+
178
+
179
+ @cli.command("assistive-touch")
180
+ def lockdown_assistive_touch(
181
+ service_provider: ServiceProviderDep, state: Optional[Literal["on", "off"]] = None
182
+ ) -> None:
183
+ """get/set assistive touch icon state (visibility)"""
179
184
  if not state:
180
- key = 'AssistiveTouchEnabledByiTunes'
181
- accessibility_values = service_provider.get_value('com.apple.Accessibility')
185
+ key = "AssistiveTouchEnabledByiTunes"
186
+ accessibility_values = service_provider.get_value("com.apple.Accessibility")
182
187
  print_json({key: bool(accessibility_values[key])})
183
188
  else:
184
189
  # enable/disable
185
- service_provider.assistive_touch = state == 'on'
190
+ service_provider.assistive_touch = state == "on"