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
@@ -2,17 +2,26 @@ import asyncio
2
2
  import logging
3
3
  from functools import update_wrapper
4
4
  from pathlib import Path
5
+ from typing import Annotated, Optional
5
6
  from urllib.error import URLError
6
7
 
7
- import click
8
+ import typer
9
+ from typer_injector import InjectingTyper
8
10
 
9
- from pymobiledevice3.cli.cli_common import Command, print_json
10
- from pymobiledevice3.exceptions import AlreadyMountedError, DeveloperDiskImageNotFoundError, NotMountedError, \
11
- UnsupportedCommandError
12
- from pymobiledevice3.lockdown import LockdownClient
11
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
12
+ from pymobiledevice3.exceptions import (
13
+ AlreadyMountedError,
14
+ DeveloperDiskImageNotFoundError,
15
+ NotMountedError,
16
+ UnsupportedCommandError,
17
+ )
13
18
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
14
- from pymobiledevice3.services.mobile_image_mounter import DeveloperDiskImageMounter, MobileImageMounterService, \
15
- PersonalizedImageMounter, auto_mount
19
+ from pymobiledevice3.services.mobile_image_mounter import (
20
+ DeveloperDiskImageMounter,
21
+ MobileImageMounterService,
22
+ PersonalizedImageMounter,
23
+ auto_mount,
24
+ )
16
25
 
17
26
  logger = logging.getLogger(__name__)
18
27
 
@@ -22,161 +31,211 @@ def catch_errors(func):
22
31
  try:
23
32
  return func(*args, **kwargs)
24
33
  except AlreadyMountedError:
25
- logger.error('Given image was already mounted')
34
+ logger.error("Given image was already mounted")
26
35
  except UnsupportedCommandError:
27
- logger.error('Your iOS version doesn\'t support this command')
36
+ logger.error("Your iOS version doesn't support this command")
28
37
 
29
38
  return update_wrapper(catch_function, func)
30
39
 
31
40
 
32
- @click.group()
33
- def cli() -> None:
34
- pass
41
+ cli = InjectingTyper(
42
+ name="mounter",
43
+ help="Mount/Umount DeveloperDiskImage or query related info",
44
+ no_args_is_help=True,
45
+ )
35
46
 
36
47
 
37
- @cli.group()
38
- def mounter() -> None:
39
- """ Mount/Umount DeveloperDiskImage or query related info """
40
- pass
41
-
42
-
43
- @mounter.command('list', cls=Command)
44
- def mounter_list(service_provider: LockdownClient):
45
- """ list all mounted images """
48
+ @cli.command("list")
49
+ def mounter_list(service_provider: ServiceProviderDep) -> None:
50
+ """list all mounted images"""
46
51
  output = []
47
52
 
48
53
  images = MobileImageMounterService(lockdown=service_provider).copy_devices()
49
54
  for image in images:
50
- image_signature = image.get('ImageSignature')
55
+ image_signature = image.get("ImageSignature")
51
56
  if image_signature is not None:
52
- image['ImageSignature'] = image_signature.hex()
57
+ image["ImageSignature"] = image_signature.hex()
53
58
  output.append(image)
54
59
 
55
60
  print_json(output)
56
61
 
57
62
 
58
- @mounter.command('lookup', cls=Command)
59
- @click.argument('image_type')
60
- def mounter_lookup(service_provider: LockdownClient, image_type):
61
- """ lookup mounter image type """
63
+ @cli.command("lookup")
64
+ def mounter_lookup(service_provider: ServiceProviderDep, image_type: str) -> None:
65
+ """lookup mounter image type"""
62
66
  try:
63
67
  signature = MobileImageMounterService(lockdown=service_provider).lookup_image(image_type)
64
68
  print_json(signature)
65
69
  except NotMountedError:
66
- logger.error(f'Disk image of type: {image_type} is not mounted')
70
+ logger.error(f"Disk image of type: {image_type} is not mounted")
67
71
 
68
72
 
69
- @mounter.command('umount-developer', cls=Command)
73
+ @cli.command("umount-developer")
70
74
  @catch_errors
71
- def mounter_umount_developer(service_provider: LockdownClient):
72
- """ unmount Developer image """
75
+ def mounter_umount_developer(service_provider: ServiceProviderDep) -> None:
76
+ """unmount Developer image"""
73
77
  try:
74
78
  DeveloperDiskImageMounter(lockdown=service_provider).umount()
75
- logger.info('Developer image unmounted successfully')
79
+ logger.info("Developer image unmounted successfully")
76
80
  except NotMountedError:
77
- logger.error('Developer image isn\'t currently mounted')
81
+ logger.error("Developer image isn't currently mounted")
78
82
 
79
83
 
80
- @mounter.command('umount-personalized', cls=Command)
84
+ @cli.command("umount-personalized")
81
85
  @catch_errors
82
- def mounter_umount_personalized(service_provider: LockdownClient):
83
- """ unmount Personalized image """
86
+ def mounter_umount_personalized(service_provider: ServiceProviderDep) -> None:
87
+ """unmount Personalized image"""
84
88
  try:
85
89
  PersonalizedImageMounter(lockdown=service_provider).umount()
86
- logger.info('Personalized image unmounted successfully')
90
+ logger.info("Personalized image unmounted successfully")
87
91
  except NotMountedError:
88
- logger.error('Personalized image isn\'t currently mounted')
92
+ logger.error("Personalized image isn't currently mounted")
89
93
 
90
94
 
91
- @mounter.command('mount-developer', cls=Command)
92
- @click.argument('image', type=click.Path(exists=True, file_okay=True, dir_okay=False))
93
- @click.argument('signature', type=click.Path(exists=True, file_okay=True, dir_okay=False))
95
+ @cli.command("mount-developer")
94
96
  @catch_errors
95
- def mounter_mount_developer(service_provider: LockdownServiceProvider, image: str, signature: str) -> None:
96
- """ mount developer image """
97
- DeveloperDiskImageMounter(lockdown=service_provider).mount(Path(image), Path(signature))
98
- logger.info('Developer image mounted successfully')
99
-
100
-
101
- async def mounter_mount_personalized_task(service_provider: LockdownServiceProvider, image: str, trust_cache: str,
102
- build_manifest: str) -> None:
103
- await PersonalizedImageMounter(lockdown=service_provider).mount(Path(image), Path(build_manifest),
104
- Path(trust_cache))
105
- logger.info('Personalized image mounted successfully')
106
-
107
-
108
- @mounter.command('mount-personalized', cls=Command)
109
- @click.argument('image', type=click.Path(exists=True, file_okay=True, dir_okay=False))
110
- @click.argument('trust-cache', type=click.Path(exists=True, file_okay=True, dir_okay=False))
111
- @click.argument('build-manifest', type=click.Path(exists=True, file_okay=True, dir_okay=False))
97
+ def mounter_mount_developer(
98
+ service_provider: ServiceProviderDep,
99
+ image: Annotated[
100
+ Path,
101
+ typer.Argument(
102
+ exists=True,
103
+ file_okay=True,
104
+ dir_okay=False,
105
+ ),
106
+ ],
107
+ signature: Annotated[
108
+ Path,
109
+ typer.Argument(
110
+ exists=True,
111
+ file_okay=True,
112
+ dir_okay=False,
113
+ ),
114
+ ],
115
+ ) -> None:
116
+ """mount developer image"""
117
+ DeveloperDiskImageMounter(lockdown=service_provider).mount(image, signature)
118
+ logger.info("Developer image mounted successfully")
119
+
120
+
121
+ async def mounter_mount_personalized_task(
122
+ service_provider: LockdownServiceProvider, image: str, trust_cache: str, build_manifest: str
123
+ ) -> None:
124
+ await PersonalizedImageMounter(lockdown=service_provider).mount(
125
+ Path(image), Path(build_manifest), Path(trust_cache)
126
+ )
127
+ logger.info("Personalized image mounted successfully")
128
+
129
+
130
+ @cli.command("mount-personalized")
112
131
  @catch_errors
113
- def mounter_mount_personalized(service_provider: LockdownServiceProvider, image: str, trust_cache: str,
114
- build_manifest: str) -> None:
115
- """ mount personalized image """
116
- asyncio.run(mounter_mount_personalized_task(service_provider, image, trust_cache, build_manifest), debug=True)
132
+ def mounter_mount_personalized(
133
+ service_provider: ServiceProviderDep,
134
+ image: Annotated[
135
+ Path,
136
+ typer.Argument(
137
+ exists=True,
138
+ file_okay=True,
139
+ dir_okay=False,
140
+ ),
141
+ ],
142
+ trust_cache: Annotated[
143
+ Path,
144
+ typer.Argument(
145
+ exists=True,
146
+ file_okay=True,
147
+ dir_okay=False,
148
+ ),
149
+ ],
150
+ build_manifest: Annotated[
151
+ Path,
152
+ typer.Argument(
153
+ exists=True,
154
+ file_okay=True,
155
+ dir_okay=False,
156
+ ),
157
+ ],
158
+ ) -> None:
159
+ """mount personalized image"""
160
+ asyncio.run(
161
+ mounter_mount_personalized_task(service_provider, str(image), str(trust_cache), str(build_manifest)), debug=True
162
+ )
117
163
 
118
164
 
119
165
  async def mounter_auto_mount_task(service_provider: LockdownServiceProvider, xcode: str, version: str) -> None:
120
166
  try:
121
167
  await auto_mount(service_provider, xcode=xcode, version=version)
122
- logger.info('DeveloperDiskImage mounted successfully')
168
+ logger.info("DeveloperDiskImage mounted successfully")
123
169
  except URLError:
124
- logger.warning('failed to query DeveloperDiskImage versions')
170
+ logger.warning("failed to query DeveloperDiskImage versions")
125
171
  except DeveloperDiskImageNotFoundError:
126
- logger.error('Unable to find the correct DeveloperDiskImage')
172
+ logger.error("Unable to find the correct DeveloperDiskImage")
127
173
  except AlreadyMountedError:
128
- logger.error('DeveloperDiskImage already mounted')
174
+ logger.error("DeveloperDiskImage already mounted")
129
175
  except PermissionError as e:
130
176
  logger.error(
131
- f'DeveloperDiskImage could not be saved to Xcode default path ({e.filename}). '
132
- f'Please make sure your user has the necessary permissions')
133
-
134
-
135
- @mounter.command('auto-mount', cls=Command)
136
- @click.option('-x', '--xcode', type=click.Path(exists=True, dir_okay=True, file_okay=False),
137
- help='Xcode application path used to figure out automatically the DeveloperDiskImage path')
138
- @click.option('-v', '--version', help='use a different DeveloperDiskImage version from the one retrieved by lockdown'
139
- 'connection')
140
- def mounter_auto_mount(service_provider: LockdownServiceProvider, xcode: str, version: str) -> None:
141
- """ auto-detect correct DeveloperDiskImage and mount it """
142
- asyncio.run(mounter_auto_mount_task(service_provider, xcode, version), debug=True)
143
-
144
-
145
- @mounter.command('query-developer-mode-status', cls=Command)
146
- def mounter_query_developer_mode_status(service_provider: LockdownClient):
147
- """ Query developer mode status """
177
+ f"DeveloperDiskImage could not be saved to Xcode default path ({e.filename}). "
178
+ f"Please make sure your user has the necessary permissions"
179
+ )
180
+
181
+
182
+ @cli.command("auto-mount")
183
+ def mounter_auto_mount(
184
+ service_provider: ServiceProviderDep,
185
+ xcode: Annotated[
186
+ Optional[Path],
187
+ typer.Option(
188
+ "--xcode",
189
+ "-x",
190
+ exists=True,
191
+ file_okay=True,
192
+ dir_okay=False,
193
+ help="Xcode application path used to figure out automatically the DeveloperDiskImage path",
194
+ ),
195
+ ] = None,
196
+ version: Annotated[
197
+ Optional[str],
198
+ typer.Option(help="Use a different DeveloperDiskImage version from the one retrieved by lockdownconnection"),
199
+ ] = None,
200
+ ) -> None:
201
+ """auto-detect correct DeveloperDiskImage and mount it"""
202
+ asyncio.run(mounter_auto_mount_task(service_provider, str(xcode), version), debug=True)
203
+
204
+
205
+ @cli.command("query-developer-mode-status")
206
+ def mounter_query_developer_mode_status(service_provider: ServiceProviderDep) -> None:
207
+ """Query developer mode status"""
148
208
  print_json(MobileImageMounterService(lockdown=service_provider).query_developer_mode_status())
149
209
 
150
210
 
151
- @mounter.command('query-nonce', cls=Command)
152
- @click.option('--image-type')
153
- def mounter_query_nonce(service_provider: LockdownClient, image_type: str):
154
- """ Query nonce """
211
+ @cli.command("query-nonce")
212
+ def mounter_query_nonce(service_provider: ServiceProviderDep, image_type: Annotated[str, typer.Option()]) -> None:
213
+ """Query nonce"""
155
214
  print_json(MobileImageMounterService(lockdown=service_provider).query_nonce(image_type))
156
215
 
157
216
 
158
- @mounter.command('query-personalization-identifiers', cls=Command)
159
- def mounter_query_personalization_identifiers(service_provider: LockdownClient):
160
- """ Query personalization identifiers """
217
+ @cli.command("query-personalization-identifiers")
218
+ def mounter_query_personalization_identifiers(service_provider: ServiceProviderDep) -> None:
219
+ """Query personalization identifiers"""
161
220
  print_json(MobileImageMounterService(lockdown=service_provider).query_personalization_identifiers())
162
221
 
163
222
 
164
- @mounter.command('query-personalization-manifest', cls=Command)
165
- def mounter_query_personalization_manifest(service_provider: LockdownClient):
166
- """ Query personalization manifest """
223
+ @cli.command("query-personalization-manifest")
224
+ def mounter_query_personalization_manifest(service_provider: ServiceProviderDep) -> None:
225
+ """Query personalization manifest"""
167
226
  result = []
168
227
  mounter = MobileImageMounterService(lockdown=service_provider)
169
228
  for device in mounter.copy_devices():
170
- result.append(mounter.query_personalization_manifest(device['PersonalizedImageType'], device['ImageSignature']))
229
+ result.append(mounter.query_personalization_manifest(device["PersonalizedImageType"], device["ImageSignature"]))
171
230
  print_json(result)
172
231
 
173
232
 
174
- @mounter.command('roll-personalization-nonce', cls=Command)
175
- def mounter_roll_personalization_nonce(service_provider: LockdownClient):
233
+ @cli.command("roll-personalization-nonce")
234
+ def mounter_roll_personalization_nonce(service_provider: ServiceProviderDep) -> None:
176
235
  MobileImageMounterService(lockdown=service_provider).roll_personalization_nonce()
177
236
 
178
237
 
179
- @mounter.command('roll-cryptex-nonce', cls=Command)
180
- def mounter_roll_cryptex_nonce(service_provider: LockdownClient):
181
- """ Roll cryptex nonce (will reboot) """
238
+ @cli.command("roll-cryptex-nonce")
239
+ def mounter_roll_cryptex_nonce(service_provider: ServiceProviderDep) -> None:
240
+ """Roll cryptex nonce (will reboot)"""
182
241
  MobileImageMounterService(lockdown=service_provider).roll_cryptex_nonce()
@@ -1,41 +1,48 @@
1
1
  import logging
2
+ from typing import Annotated
2
3
 
3
- import click
4
+ import typer
5
+ from typer_injector import InjectingTyper
4
6
 
5
- from pymobiledevice3.cli.cli_common import Command
6
- from pymobiledevice3.lockdown import LockdownClient
7
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
7
8
  from pymobiledevice3.resources.firmware_notifications import get_notifications
8
9
  from pymobiledevice3.services.notification_proxy import NotificationProxyService
9
10
 
10
11
  logger = logging.getLogger(__name__)
11
12
 
12
13
 
13
- @click.group()
14
- def cli() -> None:
15
- pass
14
+ cli = InjectingTyper(
15
+ name="notifications",
16
+ help="Post or observe Darwin notifications via notification_proxy.",
17
+ no_args_is_help=True,
18
+ )
16
19
 
17
20
 
18
- @cli.group()
19
- def notification() -> None:
20
- """ Post/Observe notifications """
21
- pass
22
-
23
-
24
- @notification.command(cls=Command)
25
- @click.argument('names', nargs=-1)
26
- @click.option('--insecure', is_flag=True, help='use the insecure relay meant for untrusted clients instead')
27
- def post(service_provider: LockdownClient, names, insecure):
28
- """ API for notify_post(). """
21
+ @cli.command()
22
+ def post(
23
+ service_provider: ServiceProviderDep,
24
+ names: list[str],
25
+ insecure: Annotated[
26
+ bool,
27
+ typer.Option(help="Use the insecure relay meant for untrusted clients instead of the trusted channel."),
28
+ ],
29
+ ) -> None:
30
+ """Post one or more Darwin notifications (notify_post)."""
29
31
  service = NotificationProxyService(lockdown=service_provider, insecure=insecure)
30
32
  for name in names:
31
33
  service.notify_post(name)
32
34
 
33
35
 
34
- @notification.command(cls=Command)
35
- @click.argument('names', nargs=-1)
36
- @click.option('--insecure', is_flag=True, help='use the insecure relay meant for untrusted clients instead')
37
- def observe(service_provider: LockdownClient, names, insecure):
38
- """ API for notify_register_dispatch(). """
36
+ @cli.command()
37
+ def observe(
38
+ service_provider: ServiceProviderDep,
39
+ names: list[str],
40
+ insecure: Annotated[
41
+ bool,
42
+ typer.Option(help="Use the insecure relay meant for untrusted clients instead of the trusted channel."),
43
+ ],
44
+ ) -> None:
45
+ """Subscribe and stream notifications (notify_register_dispatch)."""
39
46
  service = NotificationProxyService(lockdown=service_provider, insecure=insecure)
40
47
  for name in names:
41
48
  service.notify_register_dispatch(name)
@@ -44,10 +51,15 @@ def observe(service_provider: LockdownClient, names, insecure):
44
51
  logger.info(event)
45
52
 
46
53
 
47
- @notification.command('observe-all', cls=Command)
48
- @click.option('--insecure', is_flag=True, help='use the insecure relay meant for untrusted clients instead')
49
- def observe_all(service_provider: LockdownClient, insecure):
50
- """ attempt to observe all builtin firmware notifications. """
54
+ @cli.command("observe-all")
55
+ def observe_all(
56
+ service_provider: ServiceProviderDep,
57
+ insecure: Annotated[
58
+ bool,
59
+ typer.Option(help="Use the insecure relay meant for untrusted clients instead of the trusted channel."),
60
+ ],
61
+ ) -> None:
62
+ """Subscribe to all known firmware notifications and stream events."""
51
63
  service = NotificationProxyService(lockdown=service_provider, insecure=insecure)
52
64
  for notification in get_notifications():
53
65
  service.notify_register_dispatch(notification)
@@ -1,35 +1,37 @@
1
1
  from datetime import datetime
2
- from typing import IO, Optional
2
+ from pathlib import Path
3
+ from typing import Annotated, Optional
3
4
 
4
- import click
5
+ import typer
5
6
  from pygments import formatters, highlight, lexers
7
+ from typer_injector import InjectingTyper
6
8
 
7
- from pymobiledevice3.cli.cli_common import Command, print_hex, user_requested_colored_output
8
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
9
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_hex, user_requested_colored_output
9
10
  from pymobiledevice3.services.pcapd import PcapdService
10
11
 
11
-
12
- @click.group()
13
- def cli() -> None:
14
- pass
12
+ cli = InjectingTyper(
13
+ name="pcap",
14
+ help="Sniff device traffic via pcapd and optionally save to a .pcap file.",
15
+ no_args_is_help=True,
16
+ )
15
17
 
16
18
 
17
19
  def print_packet_header(packet, color: bool) -> None:
18
20
  date = datetime.fromtimestamp(packet.seconds + (packet.microseconds / 1000000))
19
21
  data = (
20
- f'{date}: '
21
- f'Process {packet.comm} ({packet.pid}), '
22
- f'Interface: {packet.interface_name} ({packet.interface_type.name}), '
23
- f'Family: {packet.protocol_family.name}'
22
+ f"{date}: "
23
+ f"Process {packet.comm} ({packet.pid}), "
24
+ f"Interface: {packet.interface_name} ({packet.interface_type.name}), "
25
+ f"Family: {packet.protocol_family.name}"
24
26
  )
25
27
  if not color:
26
28
  print(data)
27
29
  else:
28
- print(highlight(data, lexers.HspecLexer(), formatters.Terminal256Formatter(style='native')), end='')
30
+ print(highlight(data, lexers.HspecLexer(), formatters.Terminal256Formatter(style="native")), end="")
29
31
 
30
32
 
31
33
  def print_packet(packet, color: Optional[bool] = None):
32
- """ Return the packet, so it can be chained in a generator """
34
+ """Return the packet, so it can be chained in a generator"""
33
35
  if color is None:
34
36
  color = user_requested_colored_output()
35
37
  print_packet_header(packet, color)
@@ -37,20 +39,39 @@ def print_packet(packet, color: Optional[bool] = None):
37
39
  return packet
38
40
 
39
41
 
40
- @cli.command(cls=Command)
41
- @click.argument('out', type=click.File('wb'), required=False)
42
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of packets to sniff. Omit to endless sniff.')
43
- @click.option('--process', default=None, help='Process to filter. Omit for all.')
44
- @click.option('-i', '--interface', default=None, help='Interface name to filter. Omit for all.')
45
- def pcap(service_provider: LockdownServiceProvider, out: Optional[IO], count: int, process: Optional[str],
46
- interface: Optional[str]) -> None:
47
- """ Sniff device traffic """
42
+ @cli.command()
43
+ def pcap(
44
+ service_provider: ServiceProviderDep,
45
+ out: Optional[Path] = None,
46
+ count: Annotated[
47
+ int,
48
+ typer.Option(
49
+ "--count",
50
+ "-c",
51
+ help="Number of packets to sniff. Omit to endless sniff.",
52
+ ),
53
+ ] = -1,
54
+ process: Annotated[
55
+ Optional[str],
56
+ typer.Option(help="Process to filter. Omit for all."),
57
+ ] = None,
58
+ interface: Annotated[
59
+ Optional[str],
60
+ typer.Option(
61
+ "--interface",
62
+ "-i",
63
+ help="Interface name to filter. Omit for all.",
64
+ ),
65
+ ] = None,
66
+ ) -> None:
67
+ """Sniff device traffic."""
48
68
  service = PcapdService(lockdown=service_provider)
49
69
  packets_generator = service.watch(packets_count=count, process=process, interface_name=interface)
50
70
 
51
71
  if out is not None:
52
- packets_generator_with_print = map(lambda p: print_packet(p), packets_generator)
53
- service.write_to_pcap(out, packets_generator_with_print)
72
+ packets_generator_with_print = (print_packet(p) for p in packets_generator)
73
+ with out.open("wb") as out_file:
74
+ service.write_to_pcap(out_file, packets_generator_with_print)
54
75
  return
55
76
 
56
77
  for packet in packets_generator:
@@ -1,25 +1,26 @@
1
1
  import time
2
+ from typing import Literal, Optional
2
3
 
3
- import click
4
+ from typer_injector import InjectingTyper
4
5
 
5
- from pymobiledevice3.cli.cli_common import Command
6
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
6
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep
7
7
  from pymobiledevice3.services.power_assertion import PowerAssertionService
8
8
 
9
+ cli = InjectingTyper(
10
+ name="power-assertion",
11
+ no_args_is_help=True,
12
+ )
9
13
 
10
- @click.group()
11
- def cli() -> None:
12
- pass
13
14
 
14
-
15
- @cli.command('power-assertion', cls=Command)
16
- @click.argument('type', type=click.Choice(
17
- ['AMDPowerAssertionTypeWirelessSync', 'PreventUserIdleSystemSleep', 'PreventSystemSleep']))
18
- @click.argument('name')
19
- @click.argument('timeout', type=click.INT)
20
- @click.argument('details', required=False)
21
- def power_assertion(service_provider: LockdownServiceProvider, type, name, timeout, details) -> None:
22
- """ Create a power assertion """
23
- with PowerAssertionService(service_provider).create_power_assertion(type, name, timeout, details):
24
- print('> Hit Ctrl+C to exit')
15
+ @cli.command("power-assertion")
16
+ def power_assertion(
17
+ service_provider: ServiceProviderDep,
18
+ assertion_type: Literal["AMDPowerAssertionTypeWirelessSync", "PreventUserIdleSystemSleep", "PreventSystemSleep"],
19
+ name: str,
20
+ timeout: int,
21
+ details: Optional[str] = None,
22
+ ) -> None:
23
+ """Create a power assertion"""
24
+ with PowerAssertionService(service_provider).create_power_assertion(assertion_type, name, timeout, details):
25
+ print("> Hit Ctrl+C to exit")
25
26
  time.sleep(timeout)
@@ -1,37 +1,31 @@
1
1
  import logging
2
2
 
3
- import click
3
+ from typer_injector import InjectingTyper
4
4
 
5
- from pymobiledevice3.cli.cli_common import Command, print_json
6
- from pymobiledevice3.lockdown import LockdownClient
5
+ from pymobiledevice3.cli.cli_common import ServiceProviderDep, print_json
7
6
  from pymobiledevice3.services.os_trace import OsTraceService
8
7
 
9
8
  logger = logging.getLogger(__name__)
10
9
 
11
10
 
12
- @click.group()
13
- def cli() -> None:
14
- pass
11
+ cli = InjectingTyper(
12
+ name="processes",
13
+ help="View process list using diagnosticsd API",
14
+ no_args_is_help=True,
15
+ )
15
16
 
16
17
 
17
- @cli.group()
18
- def processes() -> None:
19
- """ View process list using diagnosticsd API """
20
- pass
18
+ @cli.command("ps")
19
+ def processes_ps(service_provider: ServiceProviderDep) -> None:
20
+ """show process list"""
21
+ print_json(OsTraceService(lockdown=service_provider).get_pid_list().get("Payload"))
21
22
 
22
23
 
23
- @processes.command('ps', cls=Command)
24
- def processes_ps(service_provider: LockdownClient):
25
- """ show process list """
26
- print_json(OsTraceService(lockdown=service_provider).get_pid_list().get('Payload'))
27
-
28
-
29
- @processes.command('pgrep', cls=Command)
30
- @click.argument('expression')
31
- def processes_pgrep(service_provider: LockdownClient, expression):
32
- """ try to match processes pid by given expression (like pgrep) """
33
- processes_list = OsTraceService(lockdown=service_provider).get_pid_list().get('Payload')
24
+ @cli.command("pgrep")
25
+ def processes_pgrep(service_provider: ServiceProviderDep, expression: str) -> None:
26
+ """try to match processes pid by given expression (like pgrep)"""
27
+ processes_list = OsTraceService(lockdown=service_provider).get_pid_list().get("Payload")
34
28
  for pid, process_info in processes_list.items():
35
- process_name = process_info.get('ProcessName')
29
+ process_name = process_info.get("ProcessName")
36
30
  if expression in process_name:
37
- logger.info(f'{pid} {process_name}')
31
+ logger.info(f"{pid} {process_name}")