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
@@ -1,1215 +0,0 @@
1
- # flake8: noqa: C901
2
- import asyncio
3
- import json
4
- import logging
5
- import os
6
- import posixpath
7
- import shlex
8
- import signal
9
- import sys
10
- from collections import namedtuple
11
- from dataclasses import asdict
12
- from datetime import datetime
13
- from pathlib import Path
14
- from typing import IO, Optional
15
-
16
- import click
17
- from click.exceptions import MissingParameter, UsageError
18
- from packaging.version import Version
19
- from pykdebugparser.pykdebugparser import PyKdebugParser
20
-
21
- import pymobiledevice3
22
- from pymobiledevice3.cli.cli_common import BASED_INT, Command, RSDCommand, default_json_encoder, print_json, \
23
- user_requested_colored_output
24
- from pymobiledevice3.exceptions import DeviceAlreadyInUseError, DvtDirListError, ExtractingStackshotError, \
25
- RSDRequiredError, UnrecognizedSelectorError
26
- from pymobiledevice3.lockdown import LockdownClient
27
- from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
28
- from pymobiledevice3.osu.os_utils import get_os_utils
29
- from pymobiledevice3.remote.core_device.app_service import AppServiceService
30
- from pymobiledevice3.remote.core_device.device_info import DeviceInfoService
31
- from pymobiledevice3.remote.core_device.file_service import APPLE_DOMAIN_DICT, FileServiceService
32
- from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
33
- from pymobiledevice3.services.accessibilityaudit import AccessibilityAudit
34
- from pymobiledevice3.services.debugserver_applist import DebugServerAppList
35
- from pymobiledevice3.services.device_arbitration import DtDeviceArbitration
36
- from pymobiledevice3.services.dtfetchsymbols import DtFetchSymbols
37
- from pymobiledevice3.services.dvt.dvt_secure_socket_proxy import DvtSecureSocketProxyService
38
- from pymobiledevice3.services.dvt.instruments.activity_trace_tap import ActivityTraceTap, decode_message_format
39
- from pymobiledevice3.services.dvt.instruments.application_listing import ApplicationListing
40
- from pymobiledevice3.services.dvt.instruments.condition_inducer import ConditionInducer
41
- from pymobiledevice3.services.dvt.instruments.core_profile_session_tap import CoreProfileSessionTap
42
- from pymobiledevice3.services.dvt.instruments.device_info import DeviceInfo
43
- from pymobiledevice3.services.dvt.instruments.energy_monitor import EnergyMonitor
44
- from pymobiledevice3.services.dvt.instruments.graphics import Graphics
45
- from pymobiledevice3.services.dvt.instruments.location_simulation import LocationSimulation
46
- from pymobiledevice3.services.dvt.instruments.network_monitor import ConnectionDetectionEvent, NetworkMonitor
47
- from pymobiledevice3.services.dvt.instruments.notifications import Notifications
48
- from pymobiledevice3.services.dvt.instruments.process_control import ProcessControl
49
- from pymobiledevice3.services.dvt.instruments.screenshot import Screenshot
50
- from pymobiledevice3.services.dvt.instruments.sysmontap import Sysmontap
51
- from pymobiledevice3.services.dvt.testmanaged.xcuitest import XCUITestService
52
- from pymobiledevice3.services.remote_fetch_symbols import RemoteFetchSymbolsService
53
- from pymobiledevice3.services.remote_server import RemoteServer
54
- from pymobiledevice3.services.screenshot import ScreenshotService
55
- from pymobiledevice3.services.simulate_location import DtSimulateLocation
56
- from pymobiledevice3.tcp_forwarder import LockdownTcpForwarder
57
- from pymobiledevice3.utils import try_decode
58
-
59
- OSUTILS = get_os_utils()
60
- BSC_SUBCLASS = 0x40c
61
- BSC_CLASS = 0x4
62
- VFS_AND_TRACES_SET = {0x03010000, 0x07ff0000}
63
- DEBUGSERVER_CONNECTION_STEPS = '''
64
- Follow the following connections steps from LLDB:
65
-
66
- (lldb) platform select remote-ios
67
- (lldb) target create /path/to/local/application.app
68
- (lldb) script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('/private/var/containers/Bundle/Application/<APP-UUID>/application.app'))
69
- (lldb) process connect connect://[{host}]:{port} <-- ACTUAL CONNECTION DETAILS!
70
- (lldb) process launch
71
- '''
72
-
73
- MatchedProcessByPid = namedtuple('MatchedProcess', 'name pid')
74
-
75
- logger = logging.getLogger(__name__)
76
-
77
-
78
- @click.group()
79
- def cli() -> None:
80
- pass
81
-
82
-
83
- @cli.group()
84
- def developer() -> None:
85
- """
86
- Perform developer operations (Requires enable of Developer-Mode)
87
-
88
- These options require the DeveloperDiskImage.dmg to be mounted on the device prior
89
- to execution. You can achieve this using:
90
-
91
- pymobiledevice3 mounter mount
92
-
93
- Also, starting at iOS 17.0, a tunnel must be created to the device for the services
94
- to be accessible. Therefore, every CLI command is retried with a `--tunnel` option
95
- for implicitly accessing tunneld when necessary
96
- """
97
- pass
98
-
99
-
100
- @developer.command('shell', cls=Command)
101
- @click.argument('service')
102
- @click.option('-r', '--remove-ssl-context', is_flag=True)
103
- def developer_shell(service_provider: LockdownClient, service, remove_ssl_context):
104
- """ Launch developer IPython shell (used for pymobiledevice3 R&D) """
105
- with RemoteServer(service_provider, service, remove_ssl_context) as service:
106
- service.shell()
107
-
108
-
109
- @developer.group()
110
- def dvt() -> None:
111
- """ Access advanced instrumentation APIs """
112
- pass
113
-
114
-
115
- @dvt.command('proclist', cls=Command)
116
- def proclist(service_provider: LockdownClient):
117
- """ Show process list """
118
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
119
- processes = DeviceInfo(dvt).proclist()
120
- for process in processes:
121
- if 'startDate' in process:
122
- process['startDate'] = str(process['startDate'])
123
-
124
- print_json(processes)
125
-
126
-
127
- @dvt.command('applist', cls=Command)
128
- def applist(service_provider: LockdownServiceProvider) -> None:
129
- """ Show application list """
130
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
131
- apps = ApplicationListing(dvt).applist()
132
- print_json(apps)
133
-
134
-
135
- @dvt.command('signal', cls=Command)
136
- @click.argument('pid', type=click.INT)
137
- @click.argument('sig', type=click.INT, required=False)
138
- @click.option('-s', '--signal-name', type=click.Choice([s.name for s in signal.Signals]))
139
- def send_signal(service_provider, pid, sig, signal_name):
140
- """ Send a signal to process by its PID """
141
- if not sig and not signal_name:
142
- raise MissingParameter(param_type='argument|option', param_hint='\'SIG|SIGNAL-NAME\'')
143
- if sig and signal_name:
144
- raise UsageError(message='Cannot give SIG and SIGNAL-NAME together')
145
- sig = sig or signal.Signals[signal_name].value
146
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
147
- ProcessControl(dvt).signal(pid, sig)
148
-
149
-
150
- @dvt.command('kill', cls=Command)
151
- @click.argument('pid', type=click.INT)
152
- def kill(service_provider: LockdownClient, pid):
153
- """ Kill a process by its pid. """
154
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
155
- ProcessControl(dvt).kill(pid)
156
-
157
-
158
- @dvt.command(cls=Command)
159
- @click.argument('app_bundle_identifier')
160
- def process_id_for_bundle_id(service_provider: LockdownServiceProvider, app_bundle_identifier: str) -> None:
161
- """ Get PID of a bundle identifier (only returns a valid value if its running). """
162
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
163
- print(ProcessControl(dvt).process_identifier_for_bundle_identifier(app_bundle_identifier))
164
-
165
-
166
- def get_matching_processes(service_provider: LockdownServiceProvider, name: Optional[str] = None,
167
- bundle_identifier: Optional[str] = None) \
168
- -> list[MatchedProcessByPid]:
169
- result = []
170
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
171
- device_info = DeviceInfo(dvt)
172
- for process in device_info.proclist():
173
- current_name = process.get('name')
174
- current_bundle_identifier = process.get('bundleIdentifier', '')
175
- pid = process['pid']
176
- if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or \
177
- (name is not None and name in current_name):
178
- result.append(MatchedProcessByPid(name=current_name, pid=pid))
179
- return result
180
-
181
-
182
- @dvt.command('pkill', cls=Command)
183
- @click.argument('expression')
184
- @click.option('--bundle', is_flag=True, help='Treat given expression as a bundle-identifier instead of a process name')
185
- def pkill(service_provider: LockdownServiceProvider, expression: str, bundle: False) -> None:
186
- """ kill all processes containing `expression` in their name. """
187
- matching_name = expression if not bundle else None
188
- matching_bundle_identifier = expression if bundle else None
189
- matching_processes = get_matching_processes(service_provider, name=matching_name,
190
- bundle_identifier=matching_bundle_identifier)
191
-
192
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
193
- process_control = ProcessControl(dvt)
194
-
195
- for process in matching_processes:
196
- logger.info(f'killing {process.name}({process.pid})')
197
- process_control.kill(process.pid)
198
-
199
-
200
- @dvt.command('launch', cls=Command)
201
- @click.argument('arguments', type=click.STRING)
202
- @click.option('--kill-existing/--no-kill-existing', default=True,
203
- help='Whether to kill an existing instance of this process')
204
- @click.option('--suspended', is_flag=True, help='Same as WaitForDebugger')
205
- @click.option('--env', multiple=True, type=click.Tuple((str, str)),
206
- help='Environment variables to pass to process given as a list of key value')
207
- @click.option('--stream', is_flag=True)
208
- def launch(service_provider: LockdownClient, arguments: str, kill_existing: bool, suspended: bool, env: tuple,
209
- stream: bool) -> None:
210
- """ Launch a process. """
211
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
212
- parsed_arguments = shlex.split(arguments)
213
- process_control = ProcessControl(dvt)
214
- pid = process_control.launch(bundle_id=parsed_arguments[0], arguments=parsed_arguments[1:],
215
- kill_existing=kill_existing, start_suspended=suspended,
216
- environment=dict(env))
217
- print(f'Process launched with pid {pid}')
218
- while stream:
219
- for output_received in process_control:
220
- logging.getLogger(f'PID:{output_received.pid}').info(output_received.message.strip())
221
-
222
-
223
- @dvt.command('shell', cls=Command)
224
- def dvt_shell(service_provider: LockdownClient):
225
- """ Launch developer shell (used for pymobiledevice3 R&D) """
226
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
227
- dvt.shell()
228
-
229
-
230
- def show_dirlist(device_info: DeviceInfo, dirname, recursive=False):
231
- try:
232
- filenames = device_info.ls(dirname)
233
- except DvtDirListError:
234
- return
235
-
236
- for filename in filenames:
237
- filename = posixpath.join(dirname, filename)
238
- print(filename)
239
- if recursive:
240
- show_dirlist(device_info, filename, recursive=recursive)
241
-
242
-
243
- @dvt.command('ls', cls=Command)
244
- @click.argument('path', type=click.Path(exists=False, readable=False))
245
- @click.option('-r', '--recursive', is_flag=True)
246
- def ls(service_provider: LockdownClient, path, recursive):
247
- """ List directory """
248
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
249
- show_dirlist(DeviceInfo(dvt), path, recursive=recursive)
250
-
251
-
252
- @dvt.command('device-information', cls=Command)
253
- def device_information(service_provider: LockdownClient):
254
- """ Print system information """
255
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
256
- device_info = DeviceInfo(dvt)
257
- info = {
258
- 'hardware': device_info.hardware_information(),
259
- 'network': device_info.network_information(),
260
- 'kernel-name': device_info.mach_kernel_name(),
261
- 'kpep-database': device_info.kpep_database(),
262
- }
263
- try:
264
- info['system'] = device_info.system_information()
265
- except UnrecognizedSelectorError:
266
- pass
267
- print_json(info)
268
-
269
-
270
- @dvt.command('netstat', cls=Command)
271
- def netstat(service_provider: LockdownClient):
272
- """ Print information about current network activity. """
273
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
274
- with NetworkMonitor(dvt) as monitor:
275
- for event in monitor:
276
- if isinstance(event, ConnectionDetectionEvent):
277
- logger.info(
278
- f'Connection detected: {event.local_address.data.address}:{event.local_address.port} -> '
279
- f'{event.remote_address.data.address}:{event.remote_address.port}')
280
-
281
-
282
- @dvt.command('screenshot', cls=Command)
283
- @click.argument('out', type=click.File('wb'))
284
- def screenshot(service_provider: LockdownClient, out):
285
- """ Take device screenshot """
286
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
287
- out.write(Screenshot(dvt).get_screenshot())
288
-
289
-
290
- @dvt.command('xcuitest', cls=Command)
291
- @click.argument('bundle-id')
292
- def xcuitest(service_provider: LockdownClient, bundle_id: str) -> None:
293
- """\b
294
- Start XCUITest
295
-
296
- Usage example:
297
- python3 -m pymobiledevice3 developer dvt xcuitest com.facebook.WebDriverAgentRunner.xctrunner
298
- """
299
- XCUITestService(service_provider).run(bundle_id)
300
-
301
-
302
- @dvt.group('sysmon')
303
- def sysmon():
304
- """ System monitor options. """
305
-
306
-
307
- @sysmon.group('process')
308
- def sysmon_process():
309
- """ Process monitor options. """
310
-
311
-
312
- @sysmon_process.command('monitor', cls=Command)
313
- @click.argument('threshold', type=click.FLOAT)
314
- def sysmon_process_monitor(service_provider: LockdownClient, threshold):
315
- """ monitor all most consuming processes by given cpuUsage threshold. """
316
-
317
- Process = namedtuple('process', 'pid name cpuUsage physFootprint')
318
-
319
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
320
- with Sysmontap(dvt) as sysmon:
321
- for process_snapshot in sysmon.iter_processes():
322
- entries = []
323
- for process in process_snapshot:
324
- if (process['cpuUsage'] is not None) and (process['cpuUsage'] >= threshold):
325
- entries.append(Process(pid=process['pid'], name=process['name'], cpuUsage=process['cpuUsage'],
326
- physFootprint=process['physFootprint']))
327
-
328
- logger.info(entries)
329
-
330
-
331
- @sysmon_process.command('single', cls=Command)
332
- @click.option('-a', '--attributes', multiple=True,
333
- help='filter processes by given attribute value given as key=value')
334
- def sysmon_process_single(service_provider: LockdownClient, attributes: list[str]):
335
- """ show a single snapshot of currently running processes. """
336
-
337
- count = 0
338
-
339
- result = []
340
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
341
- device_info = DeviceInfo(dvt)
342
-
343
- with Sysmontap(dvt) as sysmon:
344
- for process_snapshot in sysmon.iter_processes():
345
- count += 1
346
-
347
- if count < 2:
348
- # first sample doesn't contain an initialized value for cpuUsage
349
- continue
350
-
351
- for process in process_snapshot:
352
- skip = False
353
- if attributes is not None:
354
- for filter_attr in attributes:
355
- filter_attr, filter_value = filter_attr.split('=')
356
- if str(process[filter_attr]) != filter_value:
357
- skip = True
358
- break
359
-
360
- if skip:
361
- continue
362
-
363
- # adding "artificially" the execName field
364
- process['execName'] = device_info.execname_for_pid(process['pid'])
365
- result.append(process)
366
-
367
- # exit after single snapshot
368
- break
369
- print_json(result)
370
-
371
-
372
- @sysmon.command('system', cls=Command)
373
- @click.option('-f', '--fields', help='field names splitted by ",".')
374
- def sysmon_system(service_provider: LockdownClient, fields):
375
- """ show current system stats. """
376
-
377
- if fields is not None:
378
- fields = fields.split(',')
379
-
380
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
381
- sysmontap = Sysmontap(dvt)
382
- with sysmontap as sysmon:
383
- for row in sysmon:
384
- if 'System' in row:
385
- system = sysmon.system_attributes_cls(*row['System'])
386
- break
387
-
388
- attrs_dict = asdict(system)
389
- for name, value in attrs_dict.items():
390
- if (fields is None) or (name in fields):
391
- print(f'{name}: {value}')
392
-
393
-
394
- @dvt.group('core-profile-session')
395
- def core_profile_session():
396
- """ Access tailspin features """
397
-
398
-
399
- bsc_filter = click.option('--bsc/--no-bsc', default=False, help='Whether to print BSC events or not.')
400
- class_filter = click.option('-cf', '--class-filters', multiple=True, type=BASED_INT,
401
- help='Events class filter. Omit for all.')
402
- subclass_filter = click.option('-sf', '--subclass-filters', multiple=True, type=BASED_INT,
403
- help='Events subclass filter. Omit for all.')
404
-
405
-
406
- def parse_filters(subclasses: list[int], classes: list[int]):
407
- if not subclasses and not classes:
408
- return None
409
- parsed = set()
410
- for subclass in subclasses:
411
- if subclass == BSC_SUBCLASS:
412
- parsed |= VFS_AND_TRACES_SET
413
- parsed.add(subclass << 16)
414
- for class_ in classes:
415
- if class_ == BSC_CLASS:
416
- parsed |= VFS_AND_TRACES_SET
417
- parsed.add((class_ << 24) | 0x00ff0000)
418
- return parsed
419
-
420
-
421
- @core_profile_session.command('live', cls=Command)
422
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
423
- @bsc_filter
424
- @class_filter
425
- @subclass_filter
426
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
427
- @click.option('--timestamp/--no-timestamp', default=True, help='Whether to print timestamp or not.')
428
- @click.option('--event-name/--no-event-name', default=True, help='Whether to print event name or not.')
429
- @click.option('--func-qual/--no-func-qual', default=True, help='Whether to print function qualifier or not.')
430
- @click.option('--show-tid/--no-show-tid', default=True, help='Whether to print thread id or not.')
431
- @click.option('--process-name/--no-process-name', default=True, help='Whether to print process name or not.')
432
- @click.option('--args/--no-args', default=True, help='Whether to print event arguments or not.')
433
- def live_profile_session(service_provider: LockdownClient, count, bsc, class_filters, subclass_filters, tid, timestamp,
434
- event_name, func_qual, show_tid, process_name, args):
435
- """ Print kevents received from the device in real time. """
436
-
437
- parser = PyKdebugParser()
438
- parser.filter_class = class_filters
439
- if bsc:
440
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
441
- parser.filter_subclass = subclass_filters
442
- filters = parse_filters(subclass_filters, class_filters)
443
- parser.filter_tid = tid
444
- parser.show_timestamp = timestamp
445
- parser.show_name = event_name
446
- parser.show_func_qual = func_qual
447
- parser.show_tid = show_tid
448
- parser.show_process = process_name
449
- parser.show_args = args
450
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
451
- trace_codes_map = DeviceInfo(dvt).trace_codes()
452
- time_config = CoreProfileSessionTap.get_time_config(dvt)
453
- parser.numer = time_config['numer']
454
- parser.denom = time_config['denom']
455
- parser.mach_absolute_time = time_config['mach_absolute_time']
456
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
457
- parser.timezone = time_config['timezone']
458
- with CoreProfileSessionTap(dvt, time_config, filters) as tap:
459
- i = 0
460
- for event in parser.formatted_kevents(tap.get_kdbuf_stream(), trace_codes_map):
461
- print(event)
462
- i += 1
463
- if i == count:
464
- break
465
-
466
-
467
- @core_profile_session.command('save', cls=Command)
468
- @click.argument('out', type=click.File('wb'))
469
- @bsc_filter
470
- @class_filter
471
- @subclass_filter
472
- def save_profile_session(service_provider: LockdownClient, out, bsc, class_filters, subclass_filters):
473
- """ Dump core profiling information. """
474
- if bsc:
475
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
476
- filters = parse_filters(subclass_filters, class_filters)
477
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
478
- with CoreProfileSessionTap(dvt, {}, filters) as tap:
479
- tap.dump(out)
480
-
481
-
482
- @core_profile_session.command('stackshot', cls=Command)
483
- @click.option('--out', type=click.File('w'), default=None)
484
- def stackshot(service_provider: LockdownClient, out):
485
- """ Dump stackshot information. """
486
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
487
- with CoreProfileSessionTap(dvt, {}) as tap:
488
- try:
489
- data = tap.get_stackshot()
490
- except ExtractingStackshotError:
491
- logger.error(f'Extracting stackshot failed')
492
- return
493
-
494
- if out is not None:
495
- json.dump(data, out, indent=4, default=default_json_encoder)
496
- else:
497
- print_json(data)
498
-
499
-
500
- @core_profile_session.command('parse-live', cls=Command)
501
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
502
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
503
- @click.option('--show-tid/--no-show-tid', default=False, help='Whether to print thread id or not.')
504
- @bsc_filter
505
- @class_filter
506
- @subclass_filter
507
- @click.option('--process', default=None, help='Process ID / name to filter. Omit for all.')
508
- def parse_live_profile_session(service_provider: LockdownClient, count, tid, show_tid, bsc, class_filters,
509
- subclass_filters, process):
510
- """ Print traces (syscalls, thread events, etc.) received from the device in real time. """
511
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
512
- print('Receiving time information')
513
- time_config = CoreProfileSessionTap.get_time_config(dvt)
514
- parser = PyKdebugParser()
515
- parser.filter_class = list(class_filters)
516
- if bsc:
517
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
518
- parser.filter_subclass = subclass_filters
519
- filters = parse_filters(subclass_filters, class_filters)
520
- parser.numer = time_config['numer']
521
- parser.denom = time_config['denom']
522
- parser.mach_absolute_time = time_config['mach_absolute_time']
523
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
524
- parser.timezone = time_config['timezone']
525
- parser.filter_tid = tid
526
- parser.filter_process = process
527
- parser.show_tid = show_tid
528
-
529
- with CoreProfileSessionTap(dvt, time_config, filters) as tap:
530
- if show_tid:
531
- print('{:^32}|{:^11}|{:^33}| Event'.format('Time', 'Thread', 'Process'))
532
- else:
533
- print('{:^32}|{:^33}| Event'.format('Time', 'Process'))
534
-
535
- i = 0
536
- for trace in parser.formatted_traces(tap.get_kdbuf_stream()):
537
- print(trace)
538
- i += 1
539
- if i == count:
540
- break
541
-
542
-
543
- def get_image_name(dsc_uuid_map, image_uuid, current_dsc_map):
544
- if not current_dsc_map:
545
- for dsc_mapping in dsc_uuid_map.values():
546
- if image_uuid in dsc_mapping:
547
- current_dsc_map.update(dsc_mapping)
548
-
549
- return current_dsc_map.get(image_uuid, image_uuid)
550
-
551
-
552
- def format_callstack(callstack, dsc_uuid_map, current_dsc_map):
553
- lines = callstack.splitlines()
554
- for i, line in enumerate(lines[1:]):
555
- if ':' in line:
556
- uuid = line.split(':')[0].strip()
557
- lines[i + 1] = line.replace(uuid, get_image_name(dsc_uuid_map, uuid, current_dsc_map))
558
- return '\n'.join(lines)
559
-
560
-
561
- @core_profile_session.command('callstacks-live', cls=Command)
562
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
563
- @click.option('--process', default=None, help='Process to filter. Omit for all.')
564
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
565
- @click.option('--show-tid/--no-show-tid', default=False, help='Whether to print thread id or not.')
566
- def callstacks_live_profile_session(service_provider: LockdownClient, count, process, tid, show_tid):
567
- """ Print callstacks received from the device in real time. """
568
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
569
- print('Receiving time information')
570
- time_config = CoreProfileSessionTap.get_time_config(dvt)
571
- parser = PyKdebugParser()
572
- parser.numer = time_config['numer']
573
- parser.denom = time_config['denom']
574
- parser.mach_absolute_time = time_config['mach_absolute_time']
575
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
576
- parser.timezone = time_config['timezone']
577
- parser.filter_tid = tid
578
- parser.filter_process = process
579
- parser.color = user_requested_colored_output()
580
- parser.show_tid = show_tid
581
-
582
- with open(os.path.join(pymobiledevice3.__path__[0], 'resources', 'dsc_uuid_map.json')) as fd:
583
- dsc_uuid_map = json.load(fd)
584
-
585
- current_dsc_map = {}
586
- with CoreProfileSessionTap(dvt, time_config) as tap:
587
- i = 0
588
- for callstack in parser.formatted_callstacks(tap.get_kdbuf_stream()):
589
- print(format_callstack(callstack, dsc_uuid_map, current_dsc_map))
590
- i += 1
591
- if i == count:
592
- break
593
-
594
-
595
- @dvt.command('trace-codes', cls=Command)
596
- def dvt_trace_codes(service_provider: LockdownClient):
597
- """ Print KDebug trace codes. """
598
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
599
- device_info = DeviceInfo(dvt)
600
- print_json({hex(k): v for k, v in device_info.trace_codes().items()})
601
-
602
-
603
- @dvt.command('name-for-uid', cls=Command)
604
- @click.argument('uid', type=click.INT)
605
- def dvt_name_for_uid(service_provider: LockdownClient, uid):
606
- """ Print the assiciated username for the given uid. """
607
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
608
- device_info = DeviceInfo(dvt)
609
- print(device_info.name_for_uid(uid))
610
-
611
-
612
- @dvt.command('name-for-gid', cls=Command)
613
- @click.argument('gid', type=click.INT)
614
- def dvt_name_for_gid(service_provider: LockdownClient, gid):
615
- """ Print the assiciated group name for the given gid. """
616
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
617
- device_info = DeviceInfo(dvt)
618
- print(device_info.name_for_gid(gid))
619
-
620
-
621
- @dvt.command('oslog', cls=Command)
622
- @click.option('--pid', type=click.INT)
623
- def dvt_oslog(service_provider: LockdownClient, pid):
624
- """ Sniff device oslog (not very stable, but includes more data and normal syslog) """
625
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
626
- with ActivityTraceTap(dvt) as tap:
627
- for message in tap:
628
- message_pid = message.process
629
- # without message_type maybe signpost have event_type
630
- message_type = message.message_type if hasattr(message, 'message_type') else message.event_type \
631
- if hasattr(message, 'event_type') else 'unknown'
632
- sender_image_path = message.sender_image_path
633
- image_name = os.path.basename(sender_image_path)
634
- subsystem = message.subsystem
635
- category = message.category
636
- timestamp = datetime.now()
637
-
638
- if pid is not None and message_pid != pid:
639
- continue
640
-
641
- if message.message:
642
- formatted_message = decode_message_format(message.message)
643
- else:
644
- formatted_message = message.name
645
-
646
- if user_requested_colored_output():
647
- timestamp = click.style(str(timestamp), bold=True)
648
- message_pid = click.style(str(message_pid), 'magenta')
649
- subsystem = click.style(subsystem, 'green')
650
- category = click.style(category, 'green')
651
- image_name = click.style(image_name, 'yellow')
652
- message_type = click.style(message_type, 'cyan')
653
-
654
- print(f'[{timestamp}][{subsystem}][{category}][{message_pid}][{image_name}] '
655
- f'<{message_type}>: {formatted_message}')
656
-
657
-
658
- @dvt.command('energy', cls=Command)
659
- @click.argument('pid-list', nargs=-1)
660
- def dvt_energy(service_provider: LockdownClient, pid_list):
661
- """ Monitor the energy consumption for given PIDs """
662
-
663
- if len(pid_list) == 0:
664
- logger.error('pid_list must not be empty')
665
- return
666
-
667
- pid_list = [int(pid) for pid in pid_list]
668
-
669
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
670
- with EnergyMonitor(dvt, pid_list) as energy_monitor:
671
- for telemetry in energy_monitor:
672
- logger.info(telemetry)
673
-
674
-
675
- @dvt.command('notifications', cls=Command)
676
- def dvt_notifications(service_provider: LockdownClient):
677
- """ Monitor memory and app notifications """
678
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
679
- with Notifications(dvt) as notifications:
680
- for notification in notifications:
681
- logger.info(notification)
682
-
683
-
684
- @dvt.command('graphics', cls=Command)
685
- def dvt_notifications(service_provider: LockdownClient):
686
- """ Monitor graphics-related information """
687
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
688
- with Graphics(dvt) as graphics:
689
- for stats in graphics:
690
- logger.info(stats)
691
-
692
-
693
- @developer.group('fetch-symbols')
694
- def fetch_symbols():
695
- """ Download the DSC (and dyld) from the device """
696
- pass
697
-
698
-
699
- async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) -> None:
700
- if Version(service_provider.product_version) < Version('17.0'):
701
- print_json(DtFetchSymbols(service_provider).list_files())
702
- else:
703
- if not isinstance(service_provider, RemoteServiceDiscoveryService):
704
- raise RSDRequiredError(service_provider.identifier)
705
-
706
- async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
707
- print_json([f.file_path for f in await fetch_symbols.get_dsc_file_list()])
708
-
709
-
710
- @fetch_symbols.command('list', cls=Command)
711
- def fetch_symbols_list(service_provider: LockdownServiceProvider) -> None:
712
- """ list of files to be downloaded """
713
- asyncio.run(fetch_symbols_list_task(service_provider), debug=True)
714
-
715
-
716
- async def fetch_symbols_download_task(service_provider: LockdownServiceProvider, out: str) -> None:
717
- out = Path(out)
718
- out.mkdir(parents=True, exist_ok=True)
719
-
720
- if Version(service_provider.product_version) < Version('17.0'):
721
- fetch_symbols = DtFetchSymbols(service_provider)
722
- files = fetch_symbols.list_files()
723
-
724
- downloaded_files = set()
725
-
726
- for i, file in enumerate(files):
727
- if file.startswith('/'):
728
- # trim root to allow relative download
729
- file = file[1:]
730
- file = out / file
731
-
732
- if file not in downloaded_files:
733
- # first time the file was seen in list, means we can safely remove any old copy if any
734
- file.unlink(missing_ok=True)
735
-
736
- downloaded_files.add(file)
737
- file.parent.mkdir(parents=True, exist_ok=True)
738
- with open(file, 'ab') as f:
739
- # same file may appear twice, so we'll need to append data into it
740
- logger.info(f'writing to: {file}')
741
- fetch_symbols.get_file(i, f)
742
- else:
743
- if not isinstance(service_provider, RemoteServiceDiscoveryService):
744
- raise RSDRequiredError(service_provider.identifier)
745
- async with RemoteFetchSymbolsService(service_provider) as fetch_symbols:
746
- await fetch_symbols.download(out)
747
-
748
-
749
- @fetch_symbols.command('download', cls=Command)
750
- @click.argument('out', type=click.Path(dir_okay=True, file_okay=False))
751
- def fetch_symbols_download(service_provider: LockdownServiceProvider, out: str) -> None:
752
- """ download the linker and dyld cache to a specified directory """
753
- asyncio.run(fetch_symbols_download_task(service_provider, out), debug=True)
754
-
755
-
756
- @developer.group('simulate-location')
757
- def simulate_location():
758
- """ Simulate device location by given input """
759
- pass
760
-
761
-
762
- @simulate_location.command('clear', cls=Command)
763
- def simulate_location_clear(service_provider: LockdownClient):
764
- """ clear simulated location """
765
- DtSimulateLocation(service_provider).clear()
766
-
767
-
768
- @simulate_location.command('set', cls=Command)
769
- @click.argument('latitude', type=click.FLOAT)
770
- @click.argument('longitude', type=click.FLOAT)
771
- def simulate_location_set(service_provider: LockdownClient, latitude, longitude):
772
- """
773
- set a simulated location.
774
- try:
775
- ... set -- 40.690008 -74.045843 for liberty island
776
- """
777
- DtSimulateLocation(service_provider).set(latitude, longitude)
778
-
779
-
780
- @simulate_location.command('play', cls=Command)
781
- @click.argument('filename', type=click.Path(exists=True, file_okay=True, dir_okay=False))
782
- @click.argument('timing_randomness_range', type=click.INT)
783
- @click.option('--disable-sleep', is_flag=True, default=False)
784
- def simulate_location_play(service_provider: LockdownClient, filename, timing_randomness_range, disable_sleep):
785
- """ play a .gpx file """
786
- DtSimulateLocation(service_provider).play_gpx_file(filename, timing_randomness_range, disable_sleep=disable_sleep)
787
-
788
-
789
- @developer.group('accessibility')
790
- def accessibility():
791
- """ Interact with accessibility-related features """
792
- pass
793
-
794
-
795
- @accessibility.command('run-audit', cls=Command)
796
- @click.argument('test_types', nargs=-1)
797
- def accessibility_run_audit(service_provider: LockdownServiceProvider, test_types):
798
- """ runs accessibility audit tests """
799
- param = list(test_types)
800
- audit_issues = AccessibilityAudit(service_provider).run_audit(param)
801
- print_json([audit_issue.json() for audit_issue in audit_issues], False)
802
-
803
-
804
- @accessibility.command('supported-audit-types', cls=Command)
805
- def accessibility_supported_audit_types(service_provider: LockdownServiceProvider):
806
- """ lists supported accessibility audit test types """
807
- print_json(AccessibilityAudit(service_provider).supported_audits_types())
808
-
809
-
810
- @accessibility.command('capabilities', cls=Command)
811
- def accessibility_capabilities(service_provider: LockdownClient):
812
- """ display accessibility capabilities """
813
- print_json(AccessibilityAudit(service_provider).capabilities)
814
-
815
-
816
- @accessibility.group('settings')
817
- def accessibility_settings():
818
- """ accessibility settings. """
819
- pass
820
-
821
-
822
- @accessibility_settings.command('show', cls=Command)
823
- def accessibility_settings_show(service_provider: LockdownClient):
824
- """ show current settings """
825
- for setting in AccessibilityAudit(service_provider).settings:
826
- print(setting)
827
-
828
-
829
- @accessibility_settings.command('set', cls=Command)
830
- @click.argument('setting')
831
- @click.argument('value')
832
- def accessibility_settings_set(service_provider: LockdownClient, setting, value):
833
- """
834
- change current settings
835
-
836
- in order to list all available use the "show" command
837
- """
838
- service = AccessibilityAudit(service_provider)
839
- service.set_setting(setting, eval(value))
840
- OSUTILS.wait_return()
841
-
842
-
843
- @accessibility.command('shell', cls=Command)
844
- def accessibility_shell(service_provider: LockdownClient):
845
- """ start and ipython accessibility shell """
846
- AccessibilityAudit(service_provider).shell()
847
-
848
-
849
- @accessibility.command('notifications', cls=Command)
850
- def accessibility_notifications(service_provider: LockdownClient):
851
- """ show notifications """
852
-
853
- service = AccessibilityAudit(service_provider)
854
- for event in service.iter_events():
855
- if event.name in ('hostAppStateChanged:',
856
- 'hostInspectorCurrentElementChanged:',):
857
- for focus_item in event.data:
858
- logger.info(focus_item)
859
-
860
-
861
- @accessibility.command('list-items', cls=Command)
862
- def accessibility_list_items(service_provider: LockdownClient):
863
- """ list items available in currently shown menu """
864
-
865
- service = AccessibilityAudit(service_provider)
866
- iterator = service.iter_events()
867
-
868
- # every focus change is expected publish a "hostInspectorCurrentElementChanged:"
869
- service.move_focus_next()
870
-
871
- first_item = None
872
-
873
- for event in iterator:
874
- if event.name != 'hostInspectorCurrentElementChanged:':
875
- # ignore any other events
876
- continue
877
-
878
- # each such event should contain exactly one element that became in focus
879
- current_item = event.data[0]
880
-
881
- if first_item is None:
882
- first_item = current_item
883
- else:
884
- if first_item.caption == current_item.caption:
885
- return
886
-
887
- print(f'{current_item.caption}: {current_item.element.identifier}')
888
- service.move_focus_next()
889
-
890
-
891
- @developer.group('condition')
892
- def condition():
893
- """ Force a predefined condition """
894
- pass
895
-
896
-
897
- @condition.command('list', cls=Command)
898
- def condition_list(service_provider: LockdownServiceProvider) -> None:
899
- """ list all available conditions """
900
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
901
- print_json(ConditionInducer(dvt).list())
902
-
903
-
904
- @condition.command('clear', cls=Command)
905
- def condition_clear(service_provider: LockdownClient):
906
- """ clear current condition """
907
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
908
- ConditionInducer(dvt).clear()
909
-
910
-
911
- @condition.command('set', cls=Command)
912
- @click.argument('profile_identifier')
913
- def condition_set(service_provider: LockdownClient, profile_identifier):
914
- """ set a specific condition """
915
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
916
- ConditionInducer(dvt).set(profile_identifier)
917
- OSUTILS.wait_return()
918
-
919
-
920
- @developer.command(cls=Command)
921
- @click.argument('out', type=click.File('wb'))
922
- def screenshot(service_provider: LockdownClient, out):
923
- """ Take a screenshot in PNG format """
924
- out.write(ScreenshotService(lockdown=service_provider).take_screenshot())
925
-
926
-
927
- @developer.group('debugserver')
928
- def debugserver():
929
- """ Interact with debugserver """
930
- pass
931
-
932
-
933
- @debugserver.command('applist', cls=Command)
934
- def debugserver_applist(service_provider: LockdownClient):
935
- """ Get applist xml """
936
- print_json(DebugServerAppList(service_provider).get())
937
-
938
-
939
- @debugserver.command('start-server', cls=Command)
940
- @click.argument('local_port', type=click.INT, required=False)
941
- def debugserver_start_server(service_provider: LockdownClient, local_port: Optional[int] = None):
942
- """
943
- if local_port is provided, start a debugserver at remote listening on a given port locally.
944
- if local_port is not provided and iOS version >= 17.0 then just print the connect string
945
-
946
- Please note the connection must be done soon afterward using your own lldb client.
947
- This can be done using the following commands within lldb shell.
948
- """
949
-
950
- if Version(service_provider.product_version) < Version('17.0'):
951
- service_name = 'com.apple.debugserver.DVTSecureSocketProxy'
952
- else:
953
- service_name = 'com.apple.internal.dt.remote.debugproxy'
954
-
955
- if local_port is not None:
956
- print(DEBUGSERVER_CONNECTION_STEPS.format(host='127.0.0.1', port=local_port))
957
- print('Started port forwarding. Press Ctrl-C to close this shell when done')
958
- sys.stdout.flush()
959
- LockdownTcpForwarder(service_provider, local_port, service_name).start()
960
- elif Version(service_provider.product_version) >= Version('17.0'):
961
- if not isinstance(service_provider, RemoteServiceDiscoveryService):
962
- raise RSDRequiredError(service_provider.identifier)
963
- debugserver_port = service_provider.get_service_port(service_name)
964
- print(DEBUGSERVER_CONNECTION_STEPS.format(host=service_provider.service.address[0], port=debugserver_port))
965
- else:
966
- print("local_port is required for iOS < 17.0")
967
-
968
-
969
- @developer.group('arbitration')
970
- def arbitration():
971
- """ Mark/Unmark device as "in-use" """
972
- pass
973
-
974
-
975
- @arbitration.command('version', cls=Command)
976
- def version(service_provider: LockdownClient):
977
- """ get arbitration version """
978
- with DtDeviceArbitration(service_provider) as device_arbitration:
979
- print_json(device_arbitration.version)
980
-
981
-
982
- @arbitration.command('check-in', cls=Command)
983
- @click.argument('hostname')
984
- @click.option('-f', '--force', default=False, is_flag=True)
985
- def check_in(service_provider: LockdownClient, hostname, force):
986
- """ owner check-in """
987
- with DtDeviceArbitration(service_provider) as device_arbitration:
988
- try:
989
- device_arbitration.check_in(hostname, force=force)
990
- OSUTILS.wait_return()
991
- except DeviceAlreadyInUseError as e:
992
- logger.error(e.message)
993
-
994
-
995
- @arbitration.command('check-out', cls=Command)
996
- def check_out(service_provider: LockdownClient):
997
- """ owner check-out """
998
- with DtDeviceArbitration(service_provider) as device_arbitration:
999
- device_arbitration.check_out()
1000
-
1001
-
1002
- @dvt.command('har', cls=Command)
1003
- def dvt_har(service_provider: LockdownClient):
1004
- """
1005
- Enable har-logging
1006
-
1007
- For more information, please read:
1008
- https://github.com/doronz88/harlogger?tab=readme-ov-file#enable-http-instrumentation-method
1009
- """
1010
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
1011
- print('> Press Ctrl-C to abort')
1012
- with ActivityTraceTap(dvt, enable_http_archive_logging=True) as tap:
1013
- while True:
1014
- tap.channel.receive_message()
1015
-
1016
-
1017
- @dvt.group('simulate-location')
1018
- def dvt_simulate_location():
1019
- """ Simulate device location by given input """
1020
- pass
1021
-
1022
-
1023
- @dvt_simulate_location.command('clear', cls=Command)
1024
- def dvt_simulate_location_clear(service_provider: LockdownClient):
1025
- """ Clear currently simulated location """
1026
- with DvtSecureSocketProxyService(service_provider) as dvt:
1027
- LocationSimulation(dvt).clear()
1028
-
1029
-
1030
- @dvt_simulate_location.command('set', cls=Command)
1031
- @click.argument('latitude', type=click.FLOAT)
1032
- @click.argument('longitude', type=click.FLOAT)
1033
- def dvt_simulate_location_set(service_provider: LockdownClient, latitude, longitude):
1034
- """
1035
- Set a simulated location.
1036
- For example:
1037
- ... set -- 40.690008 -74.045843 for liberty island
1038
- """
1039
- with DvtSecureSocketProxyService(service_provider) as dvt:
1040
- LocationSimulation(dvt).set(latitude, longitude)
1041
- OSUTILS.wait_return()
1042
-
1043
-
1044
- @dvt_simulate_location.command('play', cls=Command)
1045
- @click.argument('filename', type=click.Path(exists=True, file_okay=True, dir_okay=False))
1046
- @click.argument('timing_randomness_range', type=click.INT, default=0)
1047
- @click.option('--disable-sleep', is_flag=True, default=False)
1048
- def dvt_simulate_location_play(service_provider: LockdownClient, filename: str, timing_randomness_range: int,
1049
- disable_sleep: bool) -> None:
1050
- """ Simulate inputs from a given .gpx file """
1051
- with DvtSecureSocketProxyService(service_provider) as dvt:
1052
- LocationSimulation(dvt).play_gpx_file(filename, disable_sleep=disable_sleep,
1053
- timing_randomness_range=timing_randomness_range)
1054
- OSUTILS.wait_return()
1055
-
1056
-
1057
- @developer.group()
1058
- def core_device() -> None:
1059
- """ Access features exposed by the DeveloperDiskImage """
1060
- pass
1061
-
1062
-
1063
- async def core_device_list_directory_task(
1064
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str) -> None:
1065
- async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain]) as file_service:
1066
- print_json(await file_service.retrieve_directory_list(path))
1067
-
1068
-
1069
- @core_device.command('list-directory', cls=RSDCommand)
1070
- @click.argument('domain', type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1071
- @click.argument('path')
1072
- def core_device_list_directory(
1073
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str) -> None:
1074
- """ List directory at given domain-path """
1075
- asyncio.run(core_device_list_directory_task(service_provider, domain, path))
1076
-
1077
-
1078
- async def core_device_read_file_task(
1079
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, output: Optional[IO]) -> None:
1080
- async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain]) as file_service:
1081
- buf = await file_service.retrieve_file(path)
1082
- if output is not None:
1083
- output.write(buf)
1084
- else:
1085
- print(try_decode(buf))
1086
-
1087
-
1088
- @core_device.command('read-file', cls=RSDCommand)
1089
- @click.argument('domain', type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1090
- @click.argument('path')
1091
- @click.option('-o', '--output', type=click.File('wb'))
1092
- def core_device_read_file(
1093
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, output: Optional[IO]) -> None:
1094
- """ Read file from given domain-path """
1095
- asyncio.run(core_device_read_file_task(service_provider, domain, path, output))
1096
-
1097
-
1098
- async def core_device_list_launch_application_task(
1099
- service_provider: RemoteServiceDiscoveryService, bundle_identifier: str, argument: list[str],
1100
- kill_existing: bool, suspended: bool, env: list[tuple[str, str]]) -> None:
1101
- async with AppServiceService(service_provider) as app_service:
1102
- print_json(await app_service.launch_application(bundle_identifier, argument, kill_existing,
1103
- suspended, dict(env)))
1104
-
1105
-
1106
- @core_device.command('launch-application', cls=RSDCommand)
1107
- @click.argument('bundle_identifier')
1108
- @click.argument('argument', nargs=-1)
1109
- @click.option('--kill-existing/--no-kill-existing', default=True,
1110
- help='Whether to kill an existing instance of this process')
1111
- @click.option('--suspended', is_flag=True, help='Same as WaitForDebugger')
1112
- @click.option('--env', multiple=True, type=click.Tuple((str, str)),
1113
- help='Environment variables to pass to process given as a list of key value')
1114
- def core_device_launch_application(
1115
- service_provider: RemoteServiceDiscoveryService, bundle_identifier: str, argument: tuple[str],
1116
- kill_existing: bool, suspended: bool, env: list[tuple[str, str]]) -> None:
1117
- """ Launch application """
1118
- asyncio.run(
1119
- core_device_list_launch_application_task(
1120
- service_provider, bundle_identifier, list(argument), kill_existing, suspended, env))
1121
-
1122
-
1123
- async def core_device_list_processes_task(service_provider: RemoteServiceDiscoveryService) -> None:
1124
- async with AppServiceService(service_provider) as app_service:
1125
- print_json(await app_service.list_processes())
1126
-
1127
-
1128
- @core_device.command('list-processes', cls=RSDCommand)
1129
- def core_device_list_processes(service_provider: RemoteServiceDiscoveryService) -> None:
1130
- """ Get process list """
1131
- asyncio.run(core_device_list_processes_task(service_provider))
1132
-
1133
-
1134
- async def core_device_uninstall_app_task(service_provider: RemoteServiceDiscoveryService,
1135
- bundle_identifier: str) -> None:
1136
- async with AppServiceService(service_provider) as app_service:
1137
- await app_service.uninstall_app(bundle_identifier)
1138
-
1139
-
1140
- @core_device.command('uninstall', cls=RSDCommand)
1141
- @click.argument('bundle_identifier')
1142
- def core_device_uninstall_app(service_provider: RemoteServiceDiscoveryService, bundle_identifier: str) -> None:
1143
- """ Uninstall application """
1144
- asyncio.run(core_device_uninstall_app_task(service_provider, bundle_identifier))
1145
-
1146
-
1147
- async def core_device_send_signal_to_process_task(
1148
- service_provider: RemoteServiceDiscoveryService, pid: int, signal: int) -> None:
1149
- async with AppServiceService(service_provider) as app_service:
1150
- print_json(await app_service.send_signal_to_process(pid, signal))
1151
-
1152
-
1153
- @core_device.command('send-signal-to-process', cls=RSDCommand)
1154
- @click.argument('pid', type=click.INT)
1155
- @click.argument('signal', type=click.INT)
1156
- def core_device_send_signal_to_process(service_provider: RemoteServiceDiscoveryService, pid: int, signal: int) -> None:
1157
- """ Send signal to process """
1158
- asyncio.run(core_device_send_signal_to_process_task(service_provider, pid, signal))
1159
-
1160
-
1161
- async def core_device_get_device_info_task(service_provider: RemoteServiceDiscoveryService) -> None:
1162
- async with DeviceInfoService(service_provider) as app_service:
1163
- print_json(await app_service.get_device_info())
1164
-
1165
-
1166
- @core_device.command('get-device-info', cls=RSDCommand)
1167
- def core_device_get_device_info(service_provider: RemoteServiceDiscoveryService) -> None:
1168
- """ Get device information """
1169
- asyncio.run(core_device_get_device_info_task(service_provider))
1170
-
1171
-
1172
- async def core_device_get_display_info_task(service_provider: RemoteServiceDiscoveryService) -> None:
1173
- async with DeviceInfoService(service_provider) as app_service:
1174
- print_json(await app_service.get_display_info())
1175
-
1176
-
1177
- @core_device.command('get-display-info', cls=RSDCommand)
1178
- def core_device_get_display_info(service_provider: RemoteServiceDiscoveryService) -> None:
1179
- """ Get display information """
1180
- asyncio.run(core_device_get_display_info_task(service_provider))
1181
-
1182
-
1183
- async def core_device_query_mobilegestalt_task(service_provider: RemoteServiceDiscoveryService, key: list[str]) -> None:
1184
- """ Query MobileGestalt """
1185
- async with DeviceInfoService(service_provider) as app_service:
1186
- print_json(await app_service.query_mobilegestalt(key))
1187
-
1188
-
1189
- @core_device.command('query-mobilegestalt', cls=RSDCommand)
1190
- @click.argument('key', nargs=-1, type=click.STRING)
1191
- def core_device_query_mobilegestalt(service_provider: RemoteServiceDiscoveryService, key: tuple[str]) -> None:
1192
- """ Query MobileGestalt """
1193
- asyncio.run(core_device_query_mobilegestalt_task(service_provider, list(key)))
1194
-
1195
-
1196
- async def core_device_get_lockstate_task(service_provider: RemoteServiceDiscoveryService) -> None:
1197
- async with DeviceInfoService(service_provider) as app_service:
1198
- print_json(await app_service.get_lockstate())
1199
-
1200
-
1201
- @core_device.command('get-lockstate', cls=RSDCommand)
1202
- def core_device_get_lockstate(service_provider: RemoteServiceDiscoveryService) -> None:
1203
- """ Get lockstate """
1204
- asyncio.run(core_device_get_lockstate_task(service_provider))
1205
-
1206
-
1207
- async def core_device_list_apps_task(service_provider: RemoteServiceDiscoveryService) -> None:
1208
- async with AppServiceService(service_provider) as app_service:
1209
- print_json(await app_service.list_apps())
1210
-
1211
-
1212
- @core_device.command('list-apps', cls=RSDCommand)
1213
- def core_device_list_apps(service_provider: RemoteServiceDiscoveryService) -> None:
1214
- """ Get application list """
1215
- asyncio.run(core_device_list_apps_task(service_provider))