pymobiledevice3 4.27.4__py3-none-any.whl → 5.1.2__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 (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +123 -98
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +351 -117
  6. pymobiledevice3/ca.py +32 -24
  7. pymobiledevice3/cli/activation.py +7 -7
  8. pymobiledevice3/cli/afc.py +19 -19
  9. pymobiledevice3/cli/amfi.py +4 -4
  10. pymobiledevice3/cli/apps.py +51 -39
  11. pymobiledevice3/cli/backup.py +58 -32
  12. pymobiledevice3/cli/bonjour.py +27 -20
  13. pymobiledevice3/cli/cli_common.py +112 -81
  14. pymobiledevice3/cli/companion_proxy.py +4 -4
  15. pymobiledevice3/cli/completions.py +10 -10
  16. pymobiledevice3/cli/crash.py +37 -31
  17. pymobiledevice3/cli/developer.py +601 -519
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +82 -72
  20. pymobiledevice3/cli/mounter.py +84 -67
  21. pymobiledevice3/cli/notification.py +10 -10
  22. pymobiledevice3/cli/pcap.py +19 -14
  23. pymobiledevice3/cli/power_assertion.py +12 -10
  24. pymobiledevice3/cli/processes.py +10 -10
  25. pymobiledevice3/cli/profile.py +88 -77
  26. pymobiledevice3/cli/provision.py +17 -17
  27. pymobiledevice3/cli/remote.py +188 -111
  28. pymobiledevice3/cli/restore.py +43 -40
  29. pymobiledevice3/cli/springboard.py +30 -28
  30. pymobiledevice3/cli/syslog.py +85 -58
  31. pymobiledevice3/cli/usbmux.py +21 -20
  32. pymobiledevice3/cli/version.py +3 -2
  33. pymobiledevice3/cli/webinspector.py +156 -78
  34. pymobiledevice3/common.py +1 -1
  35. pymobiledevice3/exceptions.py +154 -60
  36. pymobiledevice3/irecv.py +49 -53
  37. pymobiledevice3/irecv_devices.py +1489 -492
  38. pymobiledevice3/lockdown.py +400 -251
  39. pymobiledevice3/lockdown_service_provider.py +5 -7
  40. pymobiledevice3/osu/os_utils.py +18 -9
  41. pymobiledevice3/osu/posix_util.py +28 -15
  42. pymobiledevice3/osu/win_util.py +14 -8
  43. pymobiledevice3/pair_records.py +19 -19
  44. pymobiledevice3/remote/common.py +4 -4
  45. pymobiledevice3/remote/core_device/app_service.py +94 -67
  46. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  47. pymobiledevice3/remote/core_device/device_info.py +5 -5
  48. pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
  49. pymobiledevice3/remote/core_device/file_service.py +47 -33
  50. pymobiledevice3/remote/remote_service_discovery.py +53 -35
  51. pymobiledevice3/remote/remotexpc.py +64 -42
  52. pymobiledevice3/remote/tunnel_service.py +383 -297
  53. pymobiledevice3/remote/utils.py +14 -13
  54. pymobiledevice3/remote/xpc_message.py +145 -125
  55. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  56. pymobiledevice3/resources/firmware_notifications.py +16 -16
  57. pymobiledevice3/restore/asr.py +27 -27
  58. pymobiledevice3/restore/base_restore.py +90 -47
  59. pymobiledevice3/restore/consts.py +87 -66
  60. pymobiledevice3/restore/device.py +11 -11
  61. pymobiledevice3/restore/fdr.py +46 -46
  62. pymobiledevice3/restore/ftab.py +19 -19
  63. pymobiledevice3/restore/img4.py +130 -133
  64. pymobiledevice3/restore/mbn.py +587 -0
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +535 -523
  67. pymobiledevice3/restore/restore_options.py +122 -115
  68. pymobiledevice3/restore/restored_client.py +25 -22
  69. pymobiledevice3/restore/tss.py +378 -270
  70. pymobiledevice3/service_connection.py +50 -46
  71. pymobiledevice3/services/accessibilityaudit.py +137 -127
  72. pymobiledevice3/services/afc.py +352 -292
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +61 -47
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +56 -48
  79. pymobiledevice3/services/diagnostics.py +971 -968
  80. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  81. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  82. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  83. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  84. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  85. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  86. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  87. pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
  88. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  89. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  90. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  91. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  92. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  93. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  94. pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
  95. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  96. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  97. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  98. pymobiledevice3/services/file_relay.py +10 -10
  99. pymobiledevice3/services/heartbeat.py +8 -7
  100. pymobiledevice3/services/house_arrest.py +12 -15
  101. pymobiledevice3/services/installation_proxy.py +119 -100
  102. pymobiledevice3/services/lockdown_service.py +12 -5
  103. pymobiledevice3/services/misagent.py +22 -19
  104. pymobiledevice3/services/mobile_activation.py +84 -72
  105. pymobiledevice3/services/mobile_config.py +331 -301
  106. pymobiledevice3/services/mobile_image_mounter.py +137 -116
  107. pymobiledevice3/services/mobilebackup2.py +188 -150
  108. pymobiledevice3/services/notification_proxy.py +11 -11
  109. pymobiledevice3/services/os_trace.py +128 -74
  110. pymobiledevice3/services/pcapd.py +306 -306
  111. pymobiledevice3/services/power_assertion.py +10 -9
  112. pymobiledevice3/services/preboard.py +4 -4
  113. pymobiledevice3/services/remote_fetch_symbols.py +16 -14
  114. pymobiledevice3/services/remote_server.py +176 -146
  115. pymobiledevice3/services/restore_service.py +16 -16
  116. pymobiledevice3/services/screenshot.py +13 -10
  117. pymobiledevice3/services/simulate_location.py +7 -7
  118. pymobiledevice3/services/springboard.py +15 -15
  119. pymobiledevice3/services/syslog.py +5 -5
  120. pymobiledevice3/services/web_protocol/alert.py +3 -3
  121. pymobiledevice3/services/web_protocol/automation_session.py +183 -179
  122. pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
  123. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  124. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  125. pymobiledevice3/services/web_protocol/driver.py +47 -45
  126. pymobiledevice3/services/web_protocol/element.py +74 -63
  127. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  128. pymobiledevice3/services/web_protocol/selenium_api.py +3 -3
  129. pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
  130. pymobiledevice3/services/web_protocol/switch_to.py +11 -12
  131. pymobiledevice3/services/webinspector.py +142 -116
  132. pymobiledevice3/tcp_forwarder.py +35 -22
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +310 -193
  135. pymobiledevice3/usbmux.py +197 -148
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +1 -2
  138. pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-4.27.4.dist-info/RECORD +0 -172
  140. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-4.27.4.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
- # flake8: noqa: C901
2
1
  import asyncio
2
+ import contextlib
3
3
  import json
4
4
  import logging
5
5
  import os
@@ -24,10 +24,21 @@ from plumbum import local
24
24
  from pykdebugparser.pykdebugparser import PyKdebugParser
25
25
 
26
26
  import pymobiledevice3
27
- from pymobiledevice3.cli.cli_common import BASED_INT, Command, RSDCommand, default_json_encoder, print_json, \
28
- user_requested_colored_output
29
- from pymobiledevice3.exceptions import DeviceAlreadyInUseError, DvtDirListError, ExtractingStackshotError, \
30
- RSDRequiredError, UnrecognizedSelectorError
27
+ from pymobiledevice3.cli.cli_common import (
28
+ BASED_INT,
29
+ Command,
30
+ RSDCommand,
31
+ default_json_encoder,
32
+ print_json,
33
+ user_requested_colored_output,
34
+ )
35
+ from pymobiledevice3.exceptions import (
36
+ DeviceAlreadyInUseError,
37
+ DvtDirListError,
38
+ ExtractingStackshotError,
39
+ RSDRequiredError,
40
+ UnrecognizedSelectorError,
41
+ )
31
42
  from pymobiledevice3.lockdown import LockdownClient, create_using_usbmux
32
43
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
33
44
  from pymobiledevice3.osu.os_utils import get_os_utils
@@ -65,10 +76,10 @@ from pymobiledevice3.tcp_forwarder import LockdownTcpForwarder
65
76
  from pymobiledevice3.utils import try_decode
66
77
 
67
78
  OSUTILS = get_os_utils()
68
- BSC_SUBCLASS = 0x40c
79
+ BSC_SUBCLASS = 0x40C
69
80
  BSC_CLASS = 0x4
70
- VFS_AND_TRACES_SET = {0x03010000, 0x07ff0000}
71
- DEBUGSERVER_CONNECTION_STEPS = '''
81
+ VFS_AND_TRACES_SET = {0x03010000, 0x07FF0000}
82
+ DEBUGSERVER_CONNECTION_STEPS = """
72
83
  Follow the following connections steps from LLDB:
73
84
 
74
85
  (lldb) platform select remote-ios
@@ -76,9 +87,9 @@ Follow the following connections steps from LLDB:
76
87
  (lldb) script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec('/private/var/containers/Bundle/Application/<APP-UUID>/application.app'))
77
88
  (lldb) process connect connect://[{host}]:{port} <-- ACTUAL CONNECTION DETAILS!
78
89
  (lldb) process launch
79
- '''
90
+ """
80
91
 
81
- MatchedProcessByPid = namedtuple('MatchedProcess', 'name pid')
92
+ MatchedProcessByPid = namedtuple("MatchedProcess", "name pid")
82
93
 
83
94
  logger = logging.getLogger(__name__)
84
95
 
@@ -105,148 +116,160 @@ def developer() -> None:
105
116
  pass
106
117
 
107
118
 
108
- @developer.command('shell', cls=Command)
109
- @click.argument('service')
110
- @click.option('-r', '--remove-ssl-context', is_flag=True)
119
+ @developer.command("shell", cls=Command)
120
+ @click.argument("service")
121
+ @click.option("-r", "--remove-ssl-context", is_flag=True)
111
122
  def developer_shell(service_provider: LockdownClient, service, remove_ssl_context):
112
- """ Launch developer IPython shell (used for pymobiledevice3 R&D) """
123
+ """Launch developer IPython shell (used for pymobiledevice3 R&D)"""
113
124
  with RemoteServer(service_provider, service, remove_ssl_context) as service:
114
125
  service.shell()
115
126
 
116
127
 
117
128
  @developer.group()
118
129
  def dvt() -> None:
119
- """ Access advanced instrumentation APIs """
130
+ """Access advanced instrumentation APIs"""
120
131
  pass
121
132
 
122
133
 
123
- @dvt.command('proclist', cls=Command)
134
+ @dvt.command("proclist", cls=Command)
124
135
  def proclist(service_provider: LockdownClient) -> None:
125
- """ Show process list """
136
+ """Show process list"""
126
137
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
127
138
  processes = DeviceInfo(dvt).proclist()
128
139
  for process in processes:
129
- if 'startDate' in process:
130
- process['startDate'] = str(process['startDate'])
140
+ if "startDate" in process:
141
+ process["startDate"] = str(process["startDate"])
131
142
 
132
143
  print_json(processes)
133
144
 
134
145
 
135
- @dvt.command('is-running-pid', cls=Command)
136
- @click.argument('pid', type=click.INT)
146
+ @dvt.command("is-running-pid", cls=Command)
147
+ @click.argument("pid", type=click.INT)
137
148
  def is_running_pid(service_provider: LockdownClient, pid: int) -> None:
138
- """ Simple check if PID is running """
149
+ """Simple check if PID is running"""
139
150
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
140
151
  print_json(DeviceInfo(dvt).is_running_pid(pid))
141
152
 
142
153
 
143
- @dvt.command('memlimitoff', cls=Command)
144
- @click.argument('pid', type=click.INT)
154
+ @dvt.command("memlimitoff", cls=Command)
155
+ @click.argument("pid", type=click.INT)
145
156
  def memlimitoff(service_provider: LockdownServiceProvider, pid: int) -> None:
146
- """ Disable process memory limit """
157
+ """Disable process memory limit"""
147
158
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
148
159
  ProcessControl(dvt).disable_memory_limit_for_pid(pid)
149
160
 
150
161
 
151
- @dvt.command('applist', cls=Command)
162
+ @dvt.command("applist", cls=Command)
152
163
  def applist(service_provider: LockdownServiceProvider) -> None:
153
- """ Show application list """
164
+ """Show application list"""
154
165
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
155
166
  apps = ApplicationListing(dvt).applist()
156
167
  print_json(apps)
157
168
 
158
169
 
159
- @dvt.command('signal', cls=Command)
160
- @click.argument('pid', type=click.INT)
161
- @click.argument('sig', type=click.INT, required=False)
162
- @click.option('-s', '--signal-name', type=click.Choice([s.name for s in signal.Signals]))
170
+ @dvt.command("signal", cls=Command)
171
+ @click.argument("pid", type=click.INT)
172
+ @click.argument("sig", type=click.INT, required=False)
173
+ @click.option("-s", "--signal-name", type=click.Choice([s.name for s in signal.Signals]))
163
174
  def send_signal(service_provider, pid, sig, signal_name) -> None:
164
- """ Send a signal to process by its PID """
175
+ """Send a signal to process by its PID"""
165
176
  if not sig and not signal_name:
166
- raise MissingParameter(param_type='argument|option', param_hint='\'SIG|SIGNAL-NAME\'')
177
+ raise MissingParameter(param_type="argument|option", param_hint="'SIG|SIGNAL-NAME'")
167
178
  if sig and signal_name:
168
- raise UsageError(message='Cannot give SIG and SIGNAL-NAME together')
179
+ raise UsageError(message="Cannot give SIG and SIGNAL-NAME together")
169
180
  sig = sig or signal.Signals[signal_name].value
170
181
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
171
182
  ProcessControl(dvt).signal(pid, sig)
172
183
 
173
184
 
174
- @dvt.command('kill', cls=Command)
175
- @click.argument('pid', type=click.INT)
185
+ @dvt.command("kill", cls=Command)
186
+ @click.argument("pid", type=click.INT)
176
187
  def kill(service_provider: LockdownClient, pid) -> None:
177
- """ Kill a process by its pid. """
188
+ """Kill a process by its pid."""
178
189
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
179
190
  ProcessControl(dvt).kill(pid)
180
191
 
181
192
 
182
193
  @dvt.command(cls=Command)
183
- @click.argument('app_bundle_identifier')
194
+ @click.argument("app_bundle_identifier")
184
195
  def process_id_for_bundle_id(service_provider: LockdownServiceProvider, app_bundle_identifier: str) -> None:
185
- """ Get PID of a bundle identifier (only returns a valid value if its running). """
196
+ """Get PID of a bundle identifier (only returns a valid value if its running)."""
186
197
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
187
198
  print(ProcessControl(dvt).process_identifier_for_bundle_identifier(app_bundle_identifier))
188
199
 
189
200
 
190
- def get_matching_processes(service_provider: LockdownServiceProvider, name: Optional[str] = None,
191
- bundle_identifier: Optional[str] = None) \
192
- -> list[MatchedProcessByPid]:
201
+ def get_matching_processes(
202
+ service_provider: LockdownServiceProvider, name: Optional[str] = None, bundle_identifier: Optional[str] = None
203
+ ) -> list[MatchedProcessByPid]:
193
204
  result = []
194
205
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
195
206
  device_info = DeviceInfo(dvt)
196
207
  for process in device_info.proclist():
197
- current_name = process.get('name')
198
- current_bundle_identifier = process.get('bundleIdentifier', '')
199
- pid = process['pid']
200
- if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or \
201
- (name is not None and name in current_name):
208
+ current_name = process.get("name")
209
+ current_bundle_identifier = process.get("bundleIdentifier", "")
210
+ pid = process["pid"]
211
+ if (bundle_identifier is not None and bundle_identifier in current_bundle_identifier) or (
212
+ name is not None and name in current_name
213
+ ):
202
214
  result.append(MatchedProcessByPid(name=current_name, pid=pid))
203
215
  return result
204
216
 
205
217
 
206
- @dvt.command('pkill', cls=Command)
207
- @click.argument('expression')
208
- @click.option('--bundle', is_flag=True, help='Treat given expression as a bundle-identifier instead of a process name')
218
+ @dvt.command("pkill", cls=Command)
219
+ @click.argument("expression")
220
+ @click.option("--bundle", is_flag=True, help="Treat given expression as a bundle-identifier instead of a process name")
209
221
  def pkill(service_provider: LockdownServiceProvider, expression: str, bundle: False) -> None:
210
- """ kill all processes containing `expression` in their name. """
222
+ """kill all processes containing `expression` in their name."""
211
223
  matching_name = expression if not bundle else None
212
224
  matching_bundle_identifier = expression if bundle else None
213
- matching_processes = get_matching_processes(service_provider, name=matching_name,
214
- bundle_identifier=matching_bundle_identifier)
225
+ matching_processes = get_matching_processes(
226
+ service_provider, name=matching_name, bundle_identifier=matching_bundle_identifier
227
+ )
215
228
 
216
229
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
217
230
  process_control = ProcessControl(dvt)
218
231
 
219
232
  for process in matching_processes:
220
- logger.info(f'killing {process.name}({process.pid})')
233
+ logger.info(f"killing {process.name}({process.pid})")
221
234
  process_control.kill(process.pid)
222
235
 
223
236
 
224
- @dvt.command('launch', cls=Command)
225
- @click.argument('arguments', type=click.STRING)
226
- @click.option('--kill-existing/--no-kill-existing', default=True,
227
- help='Whether to kill an existing instance of this process')
228
- @click.option('--suspended', is_flag=True, help='Same as WaitForDebugger')
229
- @click.option('--env', multiple=True, type=click.Tuple((str, str)),
230
- help='Environment variables to pass to process given as a list of key value')
231
- @click.option('--stream', is_flag=True)
232
- def launch(service_provider: LockdownClient, arguments: str, kill_existing: bool, suspended: bool, env: tuple,
233
- stream: bool) -> None:
234
- """ Launch a process. """
237
+ @dvt.command("launch", cls=Command)
238
+ @click.argument("arguments", type=click.STRING)
239
+ @click.option(
240
+ "--kill-existing/--no-kill-existing", default=True, help="Whether to kill an existing instance of this process"
241
+ )
242
+ @click.option("--suspended", is_flag=True, help="Same as WaitForDebugger")
243
+ @click.option(
244
+ "--env",
245
+ multiple=True,
246
+ type=click.Tuple((str, str)),
247
+ help="Environment variables to pass to process given as a list of key value",
248
+ )
249
+ @click.option("--stream", is_flag=True)
250
+ def launch(
251
+ service_provider: LockdownClient, arguments: str, kill_existing: bool, suspended: bool, env: tuple, stream: bool
252
+ ) -> None:
253
+ """Launch a process."""
235
254
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
236
255
  parsed_arguments = shlex.split(arguments)
237
256
  process_control = ProcessControl(dvt)
238
- pid = process_control.launch(bundle_id=parsed_arguments[0], arguments=parsed_arguments[1:],
239
- kill_existing=kill_existing, start_suspended=suspended,
240
- environment=dict(env))
241
- print(f'Process launched with pid {pid}')
257
+ pid = process_control.launch(
258
+ bundle_id=parsed_arguments[0],
259
+ arguments=parsed_arguments[1:],
260
+ kill_existing=kill_existing,
261
+ start_suspended=suspended,
262
+ environment=dict(env),
263
+ )
264
+ print(f"Process launched with pid {pid}")
242
265
  while stream:
243
266
  for output_received in process_control:
244
- logging.getLogger(f'PID:{output_received.pid}').info(output_received.message.strip())
267
+ logging.getLogger(f"PID:{output_received.pid}").info(output_received.message.strip())
245
268
 
246
269
 
247
- @dvt.command('shell', cls=Command)
270
+ @dvt.command("shell", cls=Command)
248
271
  def dvt_shell(service_provider: LockdownClient):
249
- """ Launch developer shell (used for pymobiledevice3 R&D) """
272
+ """Launch developer shell (used for pymobiledevice3 R&D)"""
250
273
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
251
274
  dvt.shell()
252
275
 
@@ -264,55 +287,53 @@ def show_dirlist(device_info: DeviceInfo, dirname, recursive=False):
264
287
  show_dirlist(device_info, filename, recursive=recursive)
265
288
 
266
289
 
267
- @dvt.command('ls', cls=Command)
268
- @click.argument('path', type=click.Path(exists=False, readable=False))
269
- @click.option('-r', '--recursive', is_flag=True)
290
+ @dvt.command("ls", cls=Command)
291
+ @click.argument("path", type=click.Path(exists=False, readable=False))
292
+ @click.option("-r", "--recursive", is_flag=True)
270
293
  def ls(service_provider: LockdownClient, path, recursive):
271
- """ List directory """
294
+ """List directory"""
272
295
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
273
296
  show_dirlist(DeviceInfo(dvt), path, recursive=recursive)
274
297
 
275
298
 
276
- @dvt.command('device-information', cls=Command)
299
+ @dvt.command("device-information", cls=Command)
277
300
  def device_information(service_provider: LockdownClient):
278
- """ Print system information """
301
+ """Print system information"""
279
302
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
280
303
  device_info = DeviceInfo(dvt)
281
304
  info = {
282
- 'hardware': device_info.hardware_information(),
283
- 'network': device_info.network_information(),
284
- 'kernel-name': device_info.mach_kernel_name(),
285
- 'kpep-database': device_info.kpep_database(),
305
+ "hardware": device_info.hardware_information(),
306
+ "network": device_info.network_information(),
307
+ "kernel-name": device_info.mach_kernel_name(),
308
+ "kpep-database": device_info.kpep_database(),
286
309
  }
287
- try:
288
- info['system'] = device_info.system_information()
289
- except UnrecognizedSelectorError:
290
- pass
310
+ with contextlib.suppress(UnrecognizedSelectorError):
311
+ info["system"] = device_info.system_information()
291
312
  print_json(info)
292
313
 
293
314
 
294
- @dvt.command('netstat', cls=Command)
315
+ @dvt.command("netstat", cls=Command)
295
316
  def netstat(service_provider: LockdownClient):
296
- """ Print information about current network activity. """
297
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
298
- with NetworkMonitor(dvt) as monitor:
299
- for event in monitor:
300
- if isinstance(event, ConnectionDetectionEvent):
301
- logger.info(
302
- f'Connection detected: {event.local_address.data.address}:{event.local_address.port} -> '
303
- f'{event.remote_address.data.address}:{event.remote_address.port}')
304
-
305
-
306
- @dvt.command('screenshot', cls=Command)
307
- @click.argument('out', type=click.File('wb'))
308
- def screenshot(service_provider: LockdownClient, out):
309
- """ Take device screenshot """
317
+ """Print information about current network activity."""
318
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, NetworkMonitor(dvt) as monitor:
319
+ for event in monitor:
320
+ if isinstance(event, ConnectionDetectionEvent):
321
+ logger.info(
322
+ f"Connection detected: {event.local_address.data.address}:{event.local_address.port} -> "
323
+ f"{event.remote_address.data.address}:{event.remote_address.port}"
324
+ )
325
+
326
+
327
+ @dvt.command("screenshot", cls=Command)
328
+ @click.argument("out", type=click.File("wb"))
329
+ def dvt_screenshot(service_provider: LockdownClient, out):
330
+ """Take device screenshot"""
310
331
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
311
332
  out.write(Screenshot(dvt).get_screenshot())
312
333
 
313
334
 
314
- @dvt.command('xcuitest', cls=Command)
315
- @click.argument('bundle-id')
335
+ @dvt.command("xcuitest", cls=Command)
336
+ @click.argument("bundle-id")
316
337
  def xcuitest(service_provider: LockdownClient, bundle_id: str) -> None:
317
338
  """\b
318
339
  Start XCUITest
@@ -323,40 +344,44 @@ def xcuitest(service_provider: LockdownClient, bundle_id: str) -> None:
323
344
  XCUITestService(service_provider).run(bundle_id)
324
345
 
325
346
 
326
- @dvt.group('sysmon')
347
+ @dvt.group("sysmon")
327
348
  def sysmon():
328
- """ System monitor options. """
349
+ """System monitor options."""
329
350
 
330
351
 
331
- @sysmon.group('process')
352
+ @sysmon.group("process")
332
353
  def sysmon_process():
333
- """ Process monitor options. """
354
+ """Process monitor options."""
334
355
 
335
356
 
336
- @sysmon_process.command('monitor', cls=Command)
337
- @click.argument('threshold', type=click.FLOAT)
357
+ @sysmon_process.command("monitor", cls=Command)
358
+ @click.argument("threshold", type=click.FLOAT)
338
359
  def sysmon_process_monitor(service_provider: LockdownClient, threshold):
339
- """ monitor all most consuming processes by given cpuUsage threshold. """
360
+ """monitor all most consuming processes by given cpuUsage threshold."""
340
361
 
341
- Process = namedtuple('process', 'pid name cpuUsage physFootprint')
362
+ Process = namedtuple("process", "pid name cpuUsage physFootprint")
342
363
 
343
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
344
- with Sysmontap(dvt) as sysmon:
345
- for process_snapshot in sysmon.iter_processes():
346
- entries = []
347
- for process in process_snapshot:
348
- if (process['cpuUsage'] is not None) and (process['cpuUsage'] >= threshold):
349
- entries.append(Process(pid=process['pid'], name=process['name'], cpuUsage=process['cpuUsage'],
350
- physFootprint=process['physFootprint']))
364
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Sysmontap(dvt) as sysmon:
365
+ for process_snapshot in sysmon.iter_processes():
366
+ entries = []
367
+ for process in process_snapshot:
368
+ if (process["cpuUsage"] is not None) and (process["cpuUsage"] >= threshold):
369
+ entries.append(
370
+ Process(
371
+ pid=process["pid"],
372
+ name=process["name"],
373
+ cpuUsage=process["cpuUsage"],
374
+ physFootprint=process["physFootprint"],
375
+ )
376
+ )
351
377
 
352
- logger.info(entries)
378
+ logger.info(entries)
353
379
 
354
380
 
355
- @sysmon_process.command('single', cls=Command)
356
- @click.option('-a', '--attributes', multiple=True,
357
- help='filter processes by given attribute value given as key=value')
381
+ @sysmon_process.command("single", cls=Command)
382
+ @click.option("-a", "--attributes", multiple=True, help="filter processes by given attribute value given as key=value")
358
383
  def sysmon_process_single(service_provider: LockdownClient, attributes: list[str]):
359
- """ show a single snapshot of currently running processes. """
384
+ """show a single snapshot of currently running processes."""
360
385
 
361
386
  count = 0
362
387
 
@@ -376,7 +401,7 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
376
401
  skip = False
377
402
  if attributes is not None:
378
403
  for filter_attr in attributes:
379
- filter_attr, filter_value = filter_attr.split('=')
404
+ filter_attr, filter_value = filter_attr.split("=")
380
405
  if str(process[filter_attr]) != filter_value:
381
406
  skip = True
382
407
  break
@@ -385,7 +410,7 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
385
410
  continue
386
411
 
387
412
  # adding "artificially" the execName field
388
- process['execName'] = device_info.execname_for_pid(process['pid'])
413
+ process["execName"] = device_info.execname_for_pid(process["pid"])
389
414
  result.append(process)
390
415
 
391
416
  # exit after single snapshot
@@ -393,13 +418,13 @@ def sysmon_process_single(service_provider: LockdownClient, attributes: list[str
393
418
  print_json(result)
394
419
 
395
420
 
396
- @sysmon.command('system', cls=Command)
397
- @click.option('-f', '--fields', help='field names splitted by ",".')
421
+ @sysmon.command("system", cls=Command)
422
+ @click.option("-f", "--fields", help='field names splitted by ",".')
398
423
  def sysmon_system(service_provider: LockdownClient, fields):
399
- """ show current system stats. """
424
+ """show current system stats."""
400
425
 
401
426
  if fields is not None:
402
- fields = fields.split(',')
427
+ fields = fields.split(",")
403
428
 
404
429
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
405
430
  sysmontap = Sysmontap(dvt)
@@ -409,16 +434,17 @@ def sysmon_system(service_provider: LockdownClient, fields):
409
434
  system_usage_seen = False # Tracks if the first occurrence of SystemCPUUsage
410
435
 
411
436
  for row in sysmon:
412
- if 'System' in row and system is None:
413
- system = sysmon.system_attributes_cls(*row['System'])
437
+ if "System" in row and system is None:
438
+ system = sysmon.system_attributes_cls(*row["System"])
414
439
 
415
- if 'SystemCPUUsage' in row:
440
+ if "SystemCPUUsage" in row:
416
441
  if system_usage_seen:
417
442
  system_usage = {
418
- **row['SystemCPUUsage'], **{
419
- 'CPUCount': row['CPUCount'],
420
- 'EnabledCPUs': row['EnabledCPUs'],
421
- }
443
+ **row["SystemCPUUsage"],
444
+ **{
445
+ "CPUCount": row["CPUCount"],
446
+ "EnabledCPUs": row["EnabledCPUs"],
447
+ },
422
448
  }
423
449
  else: # Ignore the first occurrence because first occurrence always gives a incorrect value - 100 or 0
424
450
  system_usage_seen = True
@@ -429,19 +455,21 @@ def sysmon_system(service_provider: LockdownClient, fields):
429
455
  attrs_dict = {**asdict(system), **system_usage}
430
456
  for name, value in attrs_dict.items():
431
457
  if (fields is None) or (name in fields):
432
- print(f'{name}: {value}')
458
+ print(f"{name}: {value}")
433
459
 
434
460
 
435
- @dvt.group('core-profile-session')
461
+ @dvt.group("core-profile-session")
436
462
  def core_profile_session():
437
- """ Access tailspin features """
463
+ """Access tailspin features"""
438
464
 
439
465
 
440
- bsc_filter = click.option('--bsc/--no-bsc', default=False, help='Whether to print BSC events or not.')
441
- class_filter = click.option('-cf', '--class-filters', multiple=True, type=BASED_INT,
442
- help='Events class filter. Omit for all.')
443
- subclass_filter = click.option('-sf', '--subclass-filters', multiple=True, type=BASED_INT,
444
- help='Events subclass filter. Omit for all.')
466
+ bsc_filter = click.option("--bsc/--no-bsc", default=False, help="Whether to print BSC events or not.")
467
+ class_filter = click.option(
468
+ "-cf", "--class-filters", multiple=True, type=BASED_INT, help="Events class filter. Omit for all."
469
+ )
470
+ subclass_filter = click.option(
471
+ "-sf", "--subclass-filters", multiple=True, type=BASED_INT, help="Events subclass filter. Omit for all."
472
+ )
445
473
 
446
474
 
447
475
  def parse_filters(subclasses: list[int], classes: list[int]):
@@ -455,30 +483,42 @@ def parse_filters(subclasses: list[int], classes: list[int]):
455
483
  for class_ in classes:
456
484
  if class_ == BSC_CLASS:
457
485
  parsed |= VFS_AND_TRACES_SET
458
- parsed.add((class_ << 24) | 0x00ff0000)
486
+ parsed.add((class_ << 24) | 0x00FF0000)
459
487
  return parsed
460
488
 
461
489
 
462
- @core_profile_session.command('live', cls=Command)
463
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
490
+ @core_profile_session.command("live", cls=Command)
491
+ @click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
464
492
  @bsc_filter
465
493
  @class_filter
466
494
  @subclass_filter
467
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
468
- @click.option('--timestamp/--no-timestamp', default=True, help='Whether to print timestamp or not.')
469
- @click.option('--event-name/--no-event-name', default=True, help='Whether to print event name or not.')
470
- @click.option('--func-qual/--no-func-qual', default=True, help='Whether to print function qualifier or not.')
471
- @click.option('--show-tid/--no-show-tid', default=True, help='Whether to print thread id or not.')
472
- @click.option('--process-name/--no-process-name', default=True, help='Whether to print process name or not.')
473
- @click.option('--args/--no-args', default=True, help='Whether to print event arguments or not.')
474
- def live_profile_session(service_provider: LockdownClient, count, bsc, class_filters, subclass_filters, tid, timestamp,
475
- event_name, func_qual, show_tid, process_name, args):
476
- """ Print kevents received from the device in real time. """
495
+ @click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
496
+ @click.option("--timestamp/--no-timestamp", default=True, help="Whether to print timestamp or not.")
497
+ @click.option("--event-name/--no-event-name", default=True, help="Whether to print event name or not.")
498
+ @click.option("--func-qual/--no-func-qual", default=True, help="Whether to print function qualifier or not.")
499
+ @click.option("--show-tid/--no-show-tid", default=True, help="Whether to print thread id or not.")
500
+ @click.option("--process-name/--no-process-name", default=True, help="Whether to print process name or not.")
501
+ @click.option("--args/--no-args", default=True, help="Whether to print event arguments or not.")
502
+ def live_profile_session(
503
+ service_provider: LockdownClient,
504
+ count,
505
+ bsc,
506
+ class_filters,
507
+ subclass_filters,
508
+ tid,
509
+ timestamp,
510
+ event_name,
511
+ func_qual,
512
+ show_tid,
513
+ process_name,
514
+ args,
515
+ ):
516
+ """Print kevents received from the device in real time."""
477
517
 
478
518
  parser = PyKdebugParser()
479
519
  parser.filter_class = class_filters
480
520
  if bsc:
481
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
521
+ subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
482
522
  parser.filter_subclass = subclass_filters
483
523
  filters = parse_filters(subclass_filters, class_filters)
484
524
  parser.filter_tid = tid
@@ -491,78 +531,75 @@ def live_profile_session(service_provider: LockdownClient, count, bsc, class_fil
491
531
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
492
532
  trace_codes_map = DeviceInfo(dvt).trace_codes()
493
533
  time_config = CoreProfileSessionTap.get_time_config(dvt)
494
- parser.numer = time_config['numer']
495
- parser.denom = time_config['denom']
496
- parser.mach_absolute_time = time_config['mach_absolute_time']
497
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
498
- parser.timezone = time_config['timezone']
534
+ parser.numer = time_config["numer"]
535
+ parser.denom = time_config["denom"]
536
+ parser.mach_absolute_time = time_config["mach_absolute_time"]
537
+ parser.usecs_since_epoch = time_config["usecs_since_epoch"]
538
+ parser.timezone = time_config["timezone"]
499
539
  with CoreProfileSessionTap(dvt, time_config, filters) as tap:
500
- i = 0
501
- for event in parser.formatted_kevents(tap.get_kdbuf_stream(), trace_codes_map):
540
+ for i, event in enumerate(parser.formatted_kevents(tap.get_kdbuf_stream(), trace_codes_map)):
502
541
  print(event)
503
- i += 1
504
542
  if i == count:
505
543
  break
506
544
 
507
545
 
508
- @core_profile_session.command('save', cls=Command)
509
- @click.argument('out', type=click.File('wb'))
546
+ @core_profile_session.command("save", cls=Command)
547
+ @click.argument("out", type=click.File("wb"))
510
548
  @bsc_filter
511
549
  @class_filter
512
550
  @subclass_filter
513
551
  def save_profile_session(service_provider: LockdownClient, out, bsc, class_filters, subclass_filters):
514
- """ Dump core profiling information. """
552
+ """Dump core profiling information."""
515
553
  if bsc:
516
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
554
+ subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
517
555
  filters = parse_filters(subclass_filters, class_filters)
518
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
519
- with CoreProfileSessionTap(dvt, {}, filters) as tap:
520
- tap.dump(out)
556
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, CoreProfileSessionTap(dvt, {}, filters) as tap:
557
+ tap.dump(out)
521
558
 
522
559
 
523
- @core_profile_session.command('stackshot', cls=Command)
524
- @click.option('--out', type=click.File('w'), default=None)
560
+ @core_profile_session.command("stackshot", cls=Command)
561
+ @click.option("--out", type=click.File("w"), default=None)
525
562
  def stackshot(service_provider: LockdownClient, out):
526
- """ Dump stackshot information. """
527
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
528
- with CoreProfileSessionTap(dvt, {}) as tap:
529
- try:
530
- data = tap.get_stackshot()
531
- except ExtractingStackshotError:
532
- logger.error(f'Extracting stackshot failed')
533
- return
534
-
535
- if out is not None:
536
- json.dump(data, out, indent=4, default=default_json_encoder)
537
- else:
538
- print_json(data)
563
+ """Dump stackshot information."""
564
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, CoreProfileSessionTap(dvt, {}) as tap:
565
+ try:
566
+ data = tap.get_stackshot()
567
+ except ExtractingStackshotError:
568
+ logger.exception("Extracting stackshot failed")
569
+ return
570
+
571
+ if out is not None:
572
+ json.dump(data, out, indent=4, default=default_json_encoder)
573
+ else:
574
+ print_json(data)
539
575
 
540
576
 
541
- @core_profile_session.command('parse-live', cls=Command)
542
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
543
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
544
- @click.option('--show-tid/--no-show-tid', default=False, help='Whether to print thread id or not.')
577
+ @core_profile_session.command("parse-live", cls=Command)
578
+ @click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
579
+ @click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
580
+ @click.option("--show-tid/--no-show-tid", default=False, help="Whether to print thread id or not.")
545
581
  @bsc_filter
546
582
  @class_filter
547
583
  @subclass_filter
548
- @click.option('--process', default=None, help='Process ID / name to filter. Omit for all.')
549
- def parse_live_profile_session(service_provider: LockdownClient, count, tid, show_tid, bsc, class_filters,
550
- subclass_filters, process):
551
- """ Print traces (syscalls, thread events, etc.) received from the device in real time. """
584
+ @click.option("--process", default=None, help="Process ID / name to filter. Omit for all.")
585
+ def parse_live_profile_session(
586
+ service_provider: LockdownClient, count, tid, show_tid, bsc, class_filters, subclass_filters, process
587
+ ):
588
+ """Print traces (syscalls, thread events, etc.) received from the device in real time."""
552
589
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
553
- print('Receiving time information')
590
+ print("Receiving time information")
554
591
  time_config = CoreProfileSessionTap.get_time_config(dvt)
555
592
  parser = PyKdebugParser()
556
593
  parser.filter_class = list(class_filters)
557
594
  if bsc:
558
- subclass_filters = list(subclass_filters) + [BSC_SUBCLASS]
595
+ subclass_filters = [*list(subclass_filters), BSC_SUBCLASS]
559
596
  parser.filter_subclass = subclass_filters
560
597
  filters = parse_filters(subclass_filters, class_filters)
561
- parser.numer = time_config['numer']
562
- parser.denom = time_config['denom']
563
- parser.mach_absolute_time = time_config['mach_absolute_time']
564
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
565
- parser.timezone = time_config['timezone']
598
+ parser.numer = time_config["numer"]
599
+ parser.denom = time_config["denom"]
600
+ parser.mach_absolute_time = time_config["mach_absolute_time"]
601
+ parser.usecs_since_epoch = time_config["usecs_since_epoch"]
602
+ parser.timezone = time_config["timezone"]
566
603
  parser.filter_tid = tid
567
604
  parser.filter_process = process
568
605
  parser.show_tid = show_tid
@@ -570,14 +607,12 @@ def parse_live_profile_session(service_provider: LockdownClient, count, tid, sho
570
607
 
571
608
  with CoreProfileSessionTap(dvt, time_config, filters) as tap:
572
609
  if show_tid:
573
- print('{:^32}|{:^11}|{:^33}| Event'.format('Time', 'Thread', 'Process'))
610
+ print("{:^32}|{:^11}|{:^33}| Event".format("Time", "Thread", "Process"))
574
611
  else:
575
- print('{:^32}|{:^33}| Event'.format('Time', 'Process'))
612
+ print("{:^32}|{:^33}| Event".format("Time", "Process"))
576
613
 
577
- i = 0
578
- for trace in parser.formatted_traces(tap.get_kdbuf_stream()):
614
+ for i, trace in enumerate(parser.formatted_traces(tap.get_kdbuf_stream())):
579
615
  print(trace, flush=True)
580
- i += 1
581
616
  if i == count:
582
617
  break
583
618
 
@@ -594,152 +629,150 @@ def get_image_name(dsc_uuid_map, image_uuid, current_dsc_map):
594
629
  def format_callstack(callstack, dsc_uuid_map, current_dsc_map):
595
630
  lines = callstack.splitlines()
596
631
  for i, line in enumerate(lines[1:]):
597
- if ':' in line:
598
- uuid = line.split(':')[0].strip()
632
+ if ":" in line:
633
+ uuid = line.split(":")[0].strip()
599
634
  lines[i + 1] = line.replace(uuid, get_image_name(dsc_uuid_map, uuid, current_dsc_map))
600
- return '\n'.join(lines)
635
+ return "\n".join(lines)
601
636
 
602
637
 
603
- @core_profile_session.command('callstacks-live', cls=Command)
604
- @click.option('-c', '--count', type=click.INT, default=-1, help='Number of events to print. Omit to endless sniff.')
605
- @click.option('--process', default=None, help='Process to filter. Omit for all.')
606
- @click.option('--tid', type=click.INT, default=None, help='Thread ID to filter. Omit for all.')
607
- @click.option('--show-tid/--no-show-tid', default=False, help='Whether to print thread id or not.')
638
+ @core_profile_session.command("callstacks-live", cls=Command)
639
+ @click.option("-c", "--count", type=click.INT, default=-1, help="Number of events to print. Omit to endless sniff.")
640
+ @click.option("--process", default=None, help="Process to filter. Omit for all.")
641
+ @click.option("--tid", type=click.INT, default=None, help="Thread ID to filter. Omit for all.")
642
+ @click.option("--show-tid/--no-show-tid", default=False, help="Whether to print thread id or not.")
608
643
  def callstacks_live_profile_session(service_provider: LockdownClient, count, process, tid, show_tid):
609
- """ Print callstacks received from the device in real time. """
644
+ """Print callstacks received from the device in real time."""
610
645
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
611
- print('Receiving time information')
646
+ print("Receiving time information")
612
647
  time_config = CoreProfileSessionTap.get_time_config(dvt)
613
648
  parser = PyKdebugParser()
614
- parser.numer = time_config['numer']
615
- parser.denom = time_config['denom']
616
- parser.mach_absolute_time = time_config['mach_absolute_time']
617
- parser.usecs_since_epoch = time_config['usecs_since_epoch']
618
- parser.timezone = time_config['timezone']
649
+ parser.numer = time_config["numer"]
650
+ parser.denom = time_config["denom"]
651
+ parser.mach_absolute_time = time_config["mach_absolute_time"]
652
+ parser.usecs_since_epoch = time_config["usecs_since_epoch"]
653
+ parser.timezone = time_config["timezone"]
619
654
  parser.filter_tid = tid
620
655
  parser.filter_process = process
621
656
  parser.color = user_requested_colored_output()
622
657
  parser.show_tid = show_tid
623
658
 
624
- with open(os.path.join(pymobiledevice3.__path__[0], 'resources', 'dsc_uuid_map.json')) as fd:
659
+ with open(os.path.join(pymobiledevice3.__path__[0], "resources", "dsc_uuid_map.json")) as fd:
625
660
  dsc_uuid_map = json.load(fd)
626
661
 
627
662
  current_dsc_map = {}
628
663
  with CoreProfileSessionTap(dvt, time_config) as tap:
629
- i = 0
630
- for callstack in parser.formatted_callstacks(tap.get_kdbuf_stream()):
664
+ for i, callstack in enumerate(parser.formatted_callstacks(tap.get_kdbuf_stream())):
631
665
  print(format_callstack(callstack, dsc_uuid_map, current_dsc_map))
632
- i += 1
633
666
  if i == count:
634
667
  break
635
668
 
636
669
 
637
- @dvt.command('trace-codes', cls=Command)
670
+ @dvt.command("trace-codes", cls=Command)
638
671
  def dvt_trace_codes(service_provider: LockdownClient):
639
- """ Print KDebug trace codes. """
672
+ """Print KDebug trace codes."""
640
673
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
641
674
  device_info = DeviceInfo(dvt)
642
675
  print_json({hex(k): v for k, v in device_info.trace_codes().items()})
643
676
 
644
677
 
645
- @dvt.command('name-for-uid', cls=Command)
646
- @click.argument('uid', type=click.INT)
678
+ @dvt.command("name-for-uid", cls=Command)
679
+ @click.argument("uid", type=click.INT)
647
680
  def dvt_name_for_uid(service_provider: LockdownClient, uid):
648
- """ Print the assiciated username for the given uid. """
681
+ """Print the assiciated username for the given uid."""
649
682
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
650
683
  device_info = DeviceInfo(dvt)
651
684
  print(device_info.name_for_uid(uid))
652
685
 
653
686
 
654
- @dvt.command('name-for-gid', cls=Command)
655
- @click.argument('gid', type=click.INT)
687
+ @dvt.command("name-for-gid", cls=Command)
688
+ @click.argument("gid", type=click.INT)
656
689
  def dvt_name_for_gid(service_provider: LockdownClient, gid):
657
- """ Print the assiciated group name for the given gid. """
690
+ """Print the assiciated group name for the given gid."""
658
691
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
659
692
  device_info = DeviceInfo(dvt)
660
693
  print(device_info.name_for_gid(gid))
661
694
 
662
695
 
663
- @dvt.command('oslog', cls=Command)
664
- @click.option('--pid', type=click.INT)
696
+ @dvt.command("oslog", cls=Command)
697
+ @click.option("--pid", type=click.INT)
665
698
  def dvt_oslog(service_provider: LockdownClient, pid):
666
- """ Sniff device oslog (not very stable, but includes more data and normal syslog) """
667
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
668
- with ActivityTraceTap(dvt) as tap:
669
- for message in tap:
670
- message_pid = message.process
671
- # without message_type maybe signpost have event_type
672
- message_type = message.message_type if hasattr(message, 'message_type') else message.event_type \
673
- if hasattr(message, 'event_type') else 'unknown'
674
- sender_image_path = message.sender_image_path
675
- image_name = os.path.basename(sender_image_path)
676
- subsystem = message.subsystem
677
- category = message.category
678
- timestamp = datetime.now()
679
-
680
- if pid is not None and message_pid != pid:
681
- continue
682
-
683
- if message.message:
684
- formatted_message = decode_message_format(message.message)
685
- else:
686
- formatted_message = message.name
687
-
688
- if user_requested_colored_output():
689
- timestamp = click.style(str(timestamp), bold=True)
690
- message_pid = click.style(str(message_pid), 'magenta')
691
- subsystem = click.style(subsystem, 'green')
692
- category = click.style(category, 'green')
693
- image_name = click.style(image_name, 'yellow')
694
- message_type = click.style(message_type, 'cyan')
695
-
696
- print(f'[{timestamp}][{subsystem}][{category}][{message_pid}][{image_name}] '
697
- f'<{message_type}>: {formatted_message}')
699
+ """Sniff device oslog (not very stable, but includes more data and normal syslog)"""
700
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, ActivityTraceTap(dvt) as tap:
701
+ for message in tap:
702
+ message_pid = message.process
703
+ # without message_type maybe signpost have event_type
704
+ message_type = (
705
+ message.message_type
706
+ if hasattr(message, "message_type")
707
+ else message.event_type
708
+ if hasattr(message, "event_type")
709
+ else "unknown"
710
+ )
711
+ sender_image_path = message.sender_image_path
712
+ image_name = os.path.basename(sender_image_path)
713
+ subsystem = message.subsystem
714
+ category = message.category
715
+ timestamp = datetime.now()
716
+
717
+ if pid is not None and message_pid != pid:
718
+ continue
719
+
720
+ formatted_message = decode_message_format(message.message) if message.message else message.name
721
+
722
+ if user_requested_colored_output():
723
+ timestamp = click.style(str(timestamp), bold=True)
724
+ message_pid = click.style(str(message_pid), "magenta")
725
+ subsystem = click.style(subsystem, "green")
726
+ category = click.style(category, "green")
727
+ image_name = click.style(image_name, "yellow")
728
+ message_type = click.style(message_type, "cyan")
729
+
730
+ print(
731
+ f"[{timestamp}][{subsystem}][{category}][{message_pid}][{image_name}] "
732
+ f"<{message_type}>: {formatted_message}"
733
+ )
698
734
 
699
735
 
700
- @dvt.command('energy', cls=Command)
701
- @click.argument('pid-list', nargs=-1)
736
+ @dvt.command("energy", cls=Command)
737
+ @click.argument("pid-list", nargs=-1)
702
738
  def dvt_energy(service_provider: LockdownClient, pid_list):
703
- """ Monitor the energy consumption for given PIDs """
739
+ """Monitor the energy consumption for given PIDs"""
704
740
 
705
741
  if len(pid_list) == 0:
706
- logger.error('pid_list must not be empty')
742
+ logger.error("pid_list must not be empty")
707
743
  return
708
744
 
709
745
  pid_list = [int(pid) for pid in pid_list]
710
746
 
711
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
712
- with EnergyMonitor(dvt, pid_list) as energy_monitor:
713
- for telemetry in energy_monitor:
714
- logger.info(telemetry)
747
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, EnergyMonitor(dvt, pid_list) as energy_monitor:
748
+ for telemetry in energy_monitor:
749
+ logger.info(telemetry)
715
750
 
716
751
 
717
- @dvt.command('notifications', cls=Command)
752
+ @dvt.command("notifications", cls=Command)
718
753
  def dvt_notifications(service_provider: LockdownClient):
719
- """ Monitor memory and app notifications """
720
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
721
- with Notifications(dvt) as notifications:
722
- for notification in notifications:
723
- logger.info(notification)
754
+ """Monitor memory and app notifications"""
755
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Notifications(dvt) as notifications:
756
+ for notification in notifications:
757
+ logger.info(notification)
724
758
 
725
759
 
726
- @dvt.command('graphics', cls=Command)
727
- def dvt_notifications(service_provider: LockdownClient):
728
- """ Monitor graphics-related information """
729
- with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
730
- with Graphics(dvt) as graphics:
731
- for stats in graphics:
732
- logger.info(stats)
760
+ @dvt.command("graphics", cls=Command)
761
+ def dvt_graphics(service_provider: LockdownClient):
762
+ """Monitor graphics-related information"""
763
+ with DvtSecureSocketProxyService(lockdown=service_provider) as dvt, Graphics(dvt) as graphics:
764
+ for stats in graphics:
765
+ logger.info(stats)
733
766
 
734
767
 
735
- @developer.group('fetch-symbols')
768
+ @developer.group("fetch-symbols")
736
769
  def fetch_symbols():
737
- """ Download the DSC (and dyld) from the device """
770
+ """Download the DSC (and dyld) from the device"""
738
771
  pass
739
772
 
740
773
 
741
774
  async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) -> None:
742
- if Version(service_provider.product_version) < Version('17.0'):
775
+ if Version(service_provider.product_version) < Version("17.0"):
743
776
  print_json(DtFetchSymbols(service_provider).list_files())
744
777
  else:
745
778
  if not isinstance(service_provider, RemoteServiceDiscoveryService):
@@ -749,9 +782,9 @@ async def fetch_symbols_list_task(service_provider: LockdownServiceProvider) ->
749
782
  print_json([f.file_path for f in await fetch_symbols.get_dsc_file_list()])
750
783
 
751
784
 
752
- @fetch_symbols.command('list', cls=Command)
785
+ @fetch_symbols.command("list", cls=Command)
753
786
  def fetch_symbols_list(service_provider: LockdownServiceProvider) -> None:
754
- """ list of files to be downloaded """
787
+ """list of files to be downloaded"""
755
788
  asyncio.run(fetch_symbols_list_task(service_provider), debug=True)
756
789
 
757
790
 
@@ -759,14 +792,14 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
759
792
  out = Path(out)
760
793
  out.mkdir(parents=True, exist_ok=True)
761
794
 
762
- if Version(service_provider.product_version) < Version('17.0'):
795
+ if Version(service_provider.product_version) < Version("17.0"):
763
796
  fetch_symbols = DtFetchSymbols(service_provider)
764
797
  files = fetch_symbols.list_files()
765
798
 
766
799
  downloaded_files = set()
767
800
 
768
801
  for i, file in enumerate(files):
769
- if file.startswith('/'):
802
+ if file.startswith("/"):
770
803
  # trim root to allow relative download
771
804
  file = file[1:]
772
805
  file = out / file
@@ -777,9 +810,9 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
777
810
 
778
811
  downloaded_files.add(file)
779
812
  file.parent.mkdir(parents=True, exist_ok=True)
780
- with open(file, 'ab') as f:
813
+ with open(file, "ab") as f:
781
814
  # same file may appear twice, so we'll need to append data into it
782
- logger.info(f'writing to: {file}')
815
+ logger.info(f"writing to: {file}")
783
816
  fetch_symbols.get_file(i, f)
784
817
  else:
785
818
  if not isinstance(service_provider, RemoteServiceDiscoveryService):
@@ -788,28 +821,28 @@ async def fetch_symbols_download_task(service_provider: LockdownServiceProvider,
788
821
  await fetch_symbols.download(out)
789
822
 
790
823
 
791
- @fetch_symbols.command('download', cls=Command)
792
- @click.argument('out', type=click.Path(dir_okay=True, file_okay=False))
824
+ @fetch_symbols.command("download", cls=Command)
825
+ @click.argument("out", type=click.Path(dir_okay=True, file_okay=False))
793
826
  def fetch_symbols_download(service_provider: LockdownServiceProvider, out: str) -> None:
794
- """ download the linker and dyld cache to a specified directory """
827
+ """download the linker and dyld cache to a specified directory"""
795
828
  asyncio.run(fetch_symbols_download_task(service_provider, out), debug=True)
796
829
 
797
830
 
798
- @developer.group('simulate-location')
831
+ @developer.group("simulate-location")
799
832
  def simulate_location():
800
- """ Simulate device location by given input """
833
+ """Simulate device location by given input"""
801
834
  pass
802
835
 
803
836
 
804
- @simulate_location.command('clear', cls=Command)
837
+ @simulate_location.command("clear", cls=Command)
805
838
  def simulate_location_clear(service_provider: LockdownClient):
806
- """ clear simulated location """
839
+ """clear simulated location"""
807
840
  DtSimulateLocation(service_provider).clear()
808
841
 
809
842
 
810
- @simulate_location.command('set', cls=Command)
811
- @click.argument('latitude', type=click.FLOAT)
812
- @click.argument('longitude', type=click.FLOAT)
843
+ @simulate_location.command("set", cls=Command)
844
+ @click.argument("latitude", type=click.FLOAT)
845
+ @click.argument("longitude", type=click.FLOAT)
813
846
  def simulate_location_set(service_provider: LockdownClient, latitude, longitude):
814
847
  """
815
848
  set a simulated location.
@@ -819,58 +852,58 @@ def simulate_location_set(service_provider: LockdownClient, latitude, longitude)
819
852
  DtSimulateLocation(service_provider).set(latitude, longitude)
820
853
 
821
854
 
822
- @simulate_location.command('play', cls=Command)
823
- @click.argument('filename', type=click.Path(exists=True, file_okay=True, dir_okay=False))
824
- @click.argument('timing_randomness_range', type=click.INT)
825
- @click.option('--disable-sleep', is_flag=True, default=False)
855
+ @simulate_location.command("play", cls=Command)
856
+ @click.argument("filename", type=click.Path(exists=True, file_okay=True, dir_okay=False))
857
+ @click.argument("timing_randomness_range", type=click.INT)
858
+ @click.option("--disable-sleep", is_flag=True, default=False)
826
859
  def simulate_location_play(service_provider: LockdownClient, filename, timing_randomness_range, disable_sleep):
827
- """ play a .gpx file """
860
+ """play a .gpx file"""
828
861
  DtSimulateLocation(service_provider).play_gpx_file(filename, timing_randomness_range, disable_sleep=disable_sleep)
829
862
 
830
863
 
831
- @developer.group('accessibility')
864
+ @developer.group("accessibility")
832
865
  def accessibility():
833
- """ Interact with accessibility-related features """
866
+ """Interact with accessibility-related features"""
834
867
  pass
835
868
 
836
869
 
837
- @accessibility.command('run-audit', cls=Command)
838
- @click.argument('test_types', nargs=-1)
870
+ @accessibility.command("run-audit", cls=Command)
871
+ @click.argument("test_types", nargs=-1)
839
872
  def accessibility_run_audit(service_provider: LockdownServiceProvider, test_types):
840
- """ runs accessibility audit tests """
873
+ """runs accessibility audit tests"""
841
874
  param = list(test_types)
842
875
  audit_issues = AccessibilityAudit(service_provider).run_audit(param)
843
876
  print_json([audit_issue.json() for audit_issue in audit_issues], False)
844
877
 
845
878
 
846
- @accessibility.command('supported-audit-types', cls=Command)
879
+ @accessibility.command("supported-audit-types", cls=Command)
847
880
  def accessibility_supported_audit_types(service_provider: LockdownServiceProvider):
848
- """ lists supported accessibility audit test types """
881
+ """lists supported accessibility audit test types"""
849
882
  print_json(AccessibilityAudit(service_provider).supported_audits_types())
850
883
 
851
884
 
852
- @accessibility.command('capabilities', cls=Command)
885
+ @accessibility.command("capabilities", cls=Command)
853
886
  def accessibility_capabilities(service_provider: LockdownClient):
854
- """ display accessibility capabilities """
887
+ """display accessibility capabilities"""
855
888
  print_json(AccessibilityAudit(service_provider).capabilities)
856
889
 
857
890
 
858
- @accessibility.group('settings')
891
+ @accessibility.group("settings")
859
892
  def accessibility_settings():
860
- """ accessibility settings. """
893
+ """accessibility settings."""
861
894
  pass
862
895
 
863
896
 
864
- @accessibility_settings.command('show', cls=Command)
897
+ @accessibility_settings.command("show", cls=Command)
865
898
  def accessibility_settings_show(service_provider: LockdownClient):
866
- """ show current settings """
899
+ """show current settings"""
867
900
  for setting in AccessibilityAudit(service_provider).settings:
868
901
  print(setting)
869
902
 
870
903
 
871
- @accessibility_settings.command('set', cls=Command)
872
- @click.argument('setting')
873
- @click.argument('value')
904
+ @accessibility_settings.command("set", cls=Command)
905
+ @click.argument("setting")
906
+ @click.argument("value")
874
907
  def accessibility_settings_set(service_provider: LockdownClient, setting, value):
875
908
  """
876
909
  change current settings
@@ -882,7 +915,7 @@ def accessibility_settings_set(service_provider: LockdownClient, setting, value)
882
915
  OSUTILS.wait_return()
883
916
 
884
917
 
885
- @accessibility_settings.command('reset', cls=Command)
918
+ @accessibility_settings.command("reset", cls=Command)
886
919
  def accessibility_settings_reset(service_provider: LockdownClient):
887
920
  """
888
921
  reset accessibility settings to default
@@ -891,25 +924,27 @@ def accessibility_settings_reset(service_provider: LockdownClient):
891
924
  service.reset_settings()
892
925
 
893
926
 
894
- @accessibility.command('shell', cls=Command)
927
+ @accessibility.command("shell", cls=Command)
895
928
  def accessibility_shell(service_provider: LockdownClient):
896
- """ start and ipython accessibility shell """
929
+ """start and ipython accessibility shell"""
897
930
  AccessibilityAudit(service_provider).shell()
898
931
 
899
932
 
900
- @accessibility.command('notifications', cls=Command)
933
+ @accessibility.command("notifications", cls=Command)
901
934
  def accessibility_notifications(service_provider: LockdownClient):
902
- """ show notifications """
935
+ """show notifications"""
903
936
 
904
937
  service = AccessibilityAudit(service_provider)
905
938
  for event in service.iter_events():
906
- if event.name in ('hostAppStateChanged:',
907
- 'hostInspectorCurrentElementChanged:',):
939
+ if event.name in (
940
+ "hostAppStateChanged:",
941
+ "hostInspectorCurrentElementChanged:",
942
+ ):
908
943
  for focus_item in event.data:
909
944
  logger.info(focus_item)
910
945
 
911
946
 
912
- @accessibility.command('list-items', cls=Command)
947
+ @accessibility.command("list-items", cls=Command)
913
948
  def accessibility_list_items(service_provider: LockdownClient):
914
949
  """List elements available in the currently shown menu."""
915
950
 
@@ -920,56 +955,56 @@ def accessibility_list_items(service_provider: LockdownClient):
920
955
  print_json(elements)
921
956
 
922
957
 
923
- @developer.group('condition')
958
+ @developer.group("condition")
924
959
  def condition():
925
- """ Force a predefined condition """
960
+ """Force a predefined condition"""
926
961
  pass
927
962
 
928
963
 
929
- @condition.command('list', cls=Command)
964
+ @condition.command("list", cls=Command)
930
965
  def condition_list(service_provider: LockdownServiceProvider) -> None:
931
- """ list all available conditions """
966
+ """list all available conditions"""
932
967
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
933
968
  print_json(ConditionInducer(dvt).list())
934
969
 
935
970
 
936
- @condition.command('clear', cls=Command)
971
+ @condition.command("clear", cls=Command)
937
972
  def condition_clear(service_provider: LockdownClient):
938
- """ clear current condition """
973
+ """clear current condition"""
939
974
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
940
975
  ConditionInducer(dvt).clear()
941
976
 
942
977
 
943
- @condition.command('set', cls=Command)
944
- @click.argument('profile_identifier')
978
+ @condition.command("set", cls=Command)
979
+ @click.argument("profile_identifier")
945
980
  def condition_set(service_provider: LockdownClient, profile_identifier):
946
- """ set a specific condition """
981
+ """set a specific condition"""
947
982
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
948
983
  ConditionInducer(dvt).set(profile_identifier)
949
984
  OSUTILS.wait_return()
950
985
 
951
986
 
952
987
  @developer.command(cls=Command)
953
- @click.argument('out', type=click.File('wb'))
988
+ @click.argument("out", type=click.File("wb"))
954
989
  def screenshot(service_provider: LockdownClient, out):
955
- """ Take a screenshot in PNG format """
990
+ """Take a screenshot in PNG format"""
956
991
  out.write(ScreenshotService(lockdown=service_provider).take_screenshot())
957
992
 
958
993
 
959
- @developer.group('debugserver')
994
+ @developer.group("debugserver")
960
995
  def debugserver():
961
- """ Interact with debugserver """
996
+ """Interact with debugserver"""
962
997
  pass
963
998
 
964
999
 
965
- @debugserver.command('applist', cls=Command)
1000
+ @debugserver.command("applist", cls=Command)
966
1001
  def debugserver_applist(service_provider: LockdownClient):
967
- """ Get applist xml """
1002
+ """Get applist xml"""
968
1003
  print_json(DebugServerAppList(service_provider).get())
969
1004
 
970
1005
 
971
- @debugserver.command('start-server', cls=Command)
972
- @click.argument('local_port', type=click.INT, required=False)
1006
+ @debugserver.command("start-server", cls=Command)
1007
+ @click.argument("local_port", type=click.INT, required=False)
973
1008
  def debugserver_start_server(service_provider: LockdownClient, local_port: Optional[int] = None):
974
1009
  """
975
1010
  if local_port is provided, start a debugserver at remote listening on a given port locally.
@@ -979,17 +1014,17 @@ def debugserver_start_server(service_provider: LockdownClient, local_port: Optio
979
1014
  This can be done using the following commands within lldb shell.
980
1015
  """
981
1016
 
982
- if Version(service_provider.product_version) < Version('17.0'):
983
- service_name = 'com.apple.debugserver.DVTSecureSocketProxy'
1017
+ if Version(service_provider.product_version) < Version("17.0"):
1018
+ service_name = "com.apple.debugserver.DVTSecureSocketProxy"
984
1019
  else:
985
- service_name = 'com.apple.internal.dt.remote.debugproxy'
1020
+ service_name = "com.apple.internal.dt.remote.debugproxy"
986
1021
 
987
1022
  if local_port is not None:
988
- print(DEBUGSERVER_CONNECTION_STEPS.format(host='127.0.0.1', port=local_port))
989
- print('Started port forwarding. Press Ctrl-C to close this shell when done')
1023
+ print(DEBUGSERVER_CONNECTION_STEPS.format(host="127.0.0.1", port=local_port))
1024
+ print("Started port forwarding. Press Ctrl-C to close this shell when done")
990
1025
  sys.stdout.flush()
991
1026
  LockdownTcpForwarder(service_provider, local_port, service_name).start()
992
- elif Version(service_provider.product_version) >= Version('17.0'):
1027
+ elif Version(service_provider.product_version) >= Version("17.0"):
993
1028
  if not isinstance(service_provider, RemoteServiceDiscoveryService):
994
1029
  raise RSDRequiredError(service_provider.identifier)
995
1030
  debugserver_port = service_provider.get_service_port(service_name)
@@ -998,21 +1033,21 @@ def debugserver_start_server(service_provider: LockdownClient, local_port: Optio
998
1033
  print("local_port is required for iOS < 17.0")
999
1034
 
1000
1035
 
1001
- @debugserver.command('lldb', cls=RSDCommand)
1002
- @click.argument('xcodeproj_path', type=click.Path(exists=True, file_okay=False, dir_okay=True))
1003
- @click.option('--configuration', default='Debug', help='Usually Release/Debug')
1004
- @click.option('--lldb-command', default='lldb')
1005
- @click.option('--launch', is_flag=True, default=False, help='Launch the app after connecting to lldb')
1006
- @click.option('breakpoints', '-b', '--break', multiple=True, help='Add multiple startup breakpoints')
1007
- @click.option('user_commands', '--command', '-c', multiple=True, help='Additional commands to run at startup')
1036
+ @debugserver.command("lldb", cls=RSDCommand)
1037
+ @click.argument("xcodeproj_path", type=click.Path(exists=True, file_okay=False, dir_okay=True))
1038
+ @click.option("--configuration", default="Debug", help="Usually Release/Debug")
1039
+ @click.option("--lldb-command", default="lldb")
1040
+ @click.option("--launch", is_flag=True, default=False, help="Launch the app after connecting to lldb")
1041
+ @click.option("breakpoints", "-b", "--break", multiple=True, help="Add multiple startup breakpoints")
1042
+ @click.option("user_commands", "--command", "-c", multiple=True, help="Additional commands to run at startup")
1008
1043
  def debugserver_lldb(
1009
- service_provider: LockdownServiceProvider,
1010
- xcodeproj_path: str,
1011
- configuration: str,
1012
- lldb_command: str,
1013
- launch: bool,
1014
- breakpoints: tuple[str],
1015
- user_commands: tuple[str],
1044
+ service_provider: LockdownServiceProvider,
1045
+ xcodeproj_path: str,
1046
+ configuration: str,
1047
+ lldb_command: str,
1048
+ launch: bool,
1049
+ breakpoints: tuple[str],
1050
+ user_commands: tuple[str],
1016
1051
  ) -> None:
1017
1052
  """
1018
1053
  Automate lldb launch for a given xcodeproj.
@@ -1027,49 +1062,49 @@ def debugserver_lldb(
1027
1062
  - Execute any additional commands if requested
1028
1063
  - Switch to lldb shell
1029
1064
  """
1030
- if Version(service_provider.product_version) < Version('17.0'):
1031
- logger.error('lldb is only supported on iOS >= 17.0')
1065
+ if Version(service_provider.product_version) < Version("17.0"):
1066
+ logger.error("lldb is only supported on iOS >= 17.0")
1032
1067
  return
1033
1068
 
1034
1069
  commands = []
1035
1070
  xcodeproj_path = Path(xcodeproj_path)
1036
1071
  with local.cwd(xcodeproj_path.parent):
1037
- logger.info(f'Building {xcodeproj_path} for {configuration} configuration')
1038
- local['xcodebuild']['-configuration', configuration, 'build']()
1039
- local_app = list(Path(f'build/{configuration}-iphoneos').glob('*.app'))[0]
1040
- logger.info(f'Using app: {local_app}')
1072
+ logger.info(f"Building {xcodeproj_path} for {configuration} configuration")
1073
+ local["xcodebuild"]["-configuration", configuration, "build"]()
1074
+ local_app = next(iter(Path(f"build/{configuration}-iphoneos").glob("*.app")))
1075
+ logger.info(f"Using app: {local_app}")
1041
1076
 
1042
- info_plist_path = local_app / 'Info.plist'
1077
+ info_plist_path = local_app / "Info.plist"
1043
1078
  info_plist = plistlib.loads(info_plist_path.read_bytes())
1044
- bundle_identifier = info_plist['CFBundleIdentifier']
1045
- logger.info(f'Bundle identifier: {bundle_identifier}')
1079
+ bundle_identifier = info_plist["CFBundleIdentifier"]
1080
+ logger.info(f"Bundle identifier: {bundle_identifier}")
1046
1081
 
1047
- commands.append('platform select remote-ios')
1082
+ commands.append("platform select remote-ios")
1048
1083
  commands.append(f'target create "{local_app.absolute()}"')
1049
1084
 
1050
1085
  with InstallationProxyService(create_using_usbmux()) as installation_proxy:
1051
- logger.info('Installing app')
1086
+ logger.info("Installing app")
1052
1087
  installation_proxy.install_from_local(local_app)
1053
- remote_path = installation_proxy.get_apps(bundle_identifiers=[bundle_identifier])[bundle_identifier]['Path']
1054
- logger.info(f'Remote path: {remote_path}')
1088
+ remote_path = installation_proxy.get_apps(bundle_identifiers=[bundle_identifier])[bundle_identifier]["Path"]
1089
+ logger.info(f"Remote path: {remote_path}")
1055
1090
 
1056
1091
  commands.append(f'script lldb.target.module[0].SetPlatformFileSpec(lldb.SBFileSpec("{remote_path}"))')
1057
1092
 
1058
- debugserver_port = service_provider.get_service_port('com.apple.internal.dt.remote.debugproxy')
1093
+ debugserver_port = service_provider.get_service_port("com.apple.internal.dt.remote.debugproxy")
1059
1094
 
1060
1095
  # Add connection and launch commands
1061
- commands.append(f'process connect connect://[{service_provider.service.address[0]}]:{debugserver_port}')
1096
+ commands.append(f"process connect connect://[{service_provider.service.address[0]}]:{debugserver_port}")
1062
1097
 
1063
- for breakpoint in breakpoints:
1064
- commands.append(f'breakpoint set -n "{breakpoint}"')
1098
+ for bp in breakpoints:
1099
+ commands.append(f'breakpoint set -n "{bp}"')
1065
1100
 
1066
1101
  if launch:
1067
- commands.append('process launch')
1102
+ commands.append("process launch")
1068
1103
 
1069
1104
  # Add user commands
1070
1105
  commands += user_commands
1071
1106
 
1072
- logger.info(f'Starting lldb with automated setup and connection')
1107
+ logger.info("Starting lldb with automated setup and connection")
1073
1108
 
1074
1109
  # Works only on unix-based systems, so keep these imports here
1075
1110
  import fcntl
@@ -1085,9 +1120,10 @@ def debugserver_lldb(
1085
1120
  # Copy terminal size from the current terminal to PTY
1086
1121
  def resize_pty() -> None:
1087
1122
  """Update PTY size to match current terminal size"""
1088
- size = struct.unpack('HHHH',
1089
- fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0)))
1090
- fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack('HHHH', *size))
1123
+ size = struct.unpack(
1124
+ "HHHH", fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0))
1125
+ )
1126
+ fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", *size))
1091
1127
  # Send SIGWINCH to the child process to notify it of the resize
1092
1128
  if process is not None and process.poll() is None:
1093
1129
  process.send_signal(signal.SIGWINCH)
@@ -1107,22 +1143,16 @@ def debugserver_lldb(
1107
1143
  try:
1108
1144
  # Set TERM environment variable to enable colors
1109
1145
  env = os.environ.copy()
1110
- env['TERM'] = os.environ.get('TERM', 'xterm-256color')
1111
-
1112
- process = subprocess.Popen(
1113
- [lldb_command],
1114
- stdin=slave,
1115
- stdout=slave,
1116
- stderr=slave,
1117
- env=env
1118
- )
1146
+ env["TERM"] = os.environ.get("TERM", "xterm-256color")
1147
+
1148
+ process = subprocess.Popen([lldb_command], stdin=slave, stdout=slave, stderr=slave, env=env)
1119
1149
  os.close(slave)
1120
1150
 
1121
1151
  # Put terminal in raw mode for proper interaction
1122
1152
  tty.setraw(sys.stdin.fileno())
1123
1153
  # Send all commands through stdin
1124
1154
  for command in commands:
1125
- os.write(master, (command + '\n').encode())
1155
+ os.write(master, (command + "\n").encode())
1126
1156
 
1127
1157
  # Now redirect stdin from the terminal to lldb so user can interact
1128
1158
  while True:
@@ -1157,24 +1187,24 @@ def debugserver_lldb(
1157
1187
  process.wait()
1158
1188
 
1159
1189
 
1160
- @developer.group('arbitration')
1190
+ @developer.group("arbitration")
1161
1191
  def arbitration():
1162
- """ Mark/Unmark device as "in-use" """
1192
+ """Mark/Unmark device as "in-use" """
1163
1193
  pass
1164
1194
 
1165
1195
 
1166
- @arbitration.command('version', cls=Command)
1196
+ @arbitration.command("version", cls=Command)
1167
1197
  def version(service_provider: LockdownClient):
1168
- """ get arbitration version """
1198
+ """get arbitration version"""
1169
1199
  with DtDeviceArbitration(service_provider) as device_arbitration:
1170
1200
  print_json(device_arbitration.version)
1171
1201
 
1172
1202
 
1173
- @arbitration.command('check-in', cls=Command)
1174
- @click.argument('hostname')
1175
- @click.option('-f', '--force', default=False, is_flag=True)
1203
+ @arbitration.command("check-in", cls=Command)
1204
+ @click.argument("hostname")
1205
+ @click.option("-f", "--force", default=False, is_flag=True)
1176
1206
  def check_in(service_provider: LockdownClient, hostname, force):
1177
- """ owner check-in """
1207
+ """owner check-in"""
1178
1208
  with DtDeviceArbitration(service_provider) as device_arbitration:
1179
1209
  try:
1180
1210
  device_arbitration.check_in(hostname, force=force)
@@ -1183,14 +1213,14 @@ def check_in(service_provider: LockdownClient, hostname, force):
1183
1213
  logger.error(e.message)
1184
1214
 
1185
1215
 
1186
- @arbitration.command('check-out', cls=Command)
1216
+ @arbitration.command("check-out", cls=Command)
1187
1217
  def check_out(service_provider: LockdownClient):
1188
- """ owner check-out """
1218
+ """owner check-out"""
1189
1219
  with DtDeviceArbitration(service_provider) as device_arbitration:
1190
1220
  device_arbitration.check_out()
1191
1221
 
1192
1222
 
1193
- @dvt.command('har', cls=Command)
1223
+ @dvt.command("har", cls=Command)
1194
1224
  def dvt_har(service_provider: LockdownClient):
1195
1225
  """
1196
1226
  Enable har-logging
@@ -1199,28 +1229,28 @@ def dvt_har(service_provider: LockdownClient):
1199
1229
  https://github.com/doronz88/harlogger?tab=readme-ov-file#enable-http-instrumentation-method
1200
1230
  """
1201
1231
  with DvtSecureSocketProxyService(lockdown=service_provider) as dvt:
1202
- print('> Press Ctrl-C to abort')
1232
+ print("> Press Ctrl-C to abort")
1203
1233
  with ActivityTraceTap(dvt, enable_http_archive_logging=True) as tap:
1204
1234
  while True:
1205
1235
  tap.channel.receive_message()
1206
1236
 
1207
1237
 
1208
- @dvt.group('simulate-location')
1238
+ @dvt.group("simulate-location")
1209
1239
  def dvt_simulate_location():
1210
- """ Simulate device location by given input """
1240
+ """Simulate device location by given input"""
1211
1241
  pass
1212
1242
 
1213
1243
 
1214
- @dvt_simulate_location.command('clear', cls=Command)
1244
+ @dvt_simulate_location.command("clear", cls=Command)
1215
1245
  def dvt_simulate_location_clear(service_provider: LockdownClient):
1216
- """ Clear currently simulated location """
1246
+ """Clear currently simulated location"""
1217
1247
  with DvtSecureSocketProxyService(service_provider) as dvt:
1218
1248
  LocationSimulation(dvt).clear()
1219
1249
 
1220
1250
 
1221
- @dvt_simulate_location.command('set', cls=Command)
1222
- @click.argument('latitude', type=click.FLOAT)
1223
- @click.argument('longitude', type=click.FLOAT)
1251
+ @dvt_simulate_location.command("set", cls=Command)
1252
+ @click.argument("latitude", type=click.FLOAT)
1253
+ @click.argument("longitude", type=click.FLOAT)
1224
1254
  def dvt_simulate_location_set(service_provider: LockdownClient, latitude, longitude):
1225
1255
  """
1226
1256
  Set a simulated location.
@@ -1232,44 +1262,48 @@ def dvt_simulate_location_set(service_provider: LockdownClient, latitude, longit
1232
1262
  OSUTILS.wait_return()
1233
1263
 
1234
1264
 
1235
- @dvt_simulate_location.command('play', cls=Command)
1236
- @click.argument('filename', type=click.Path(exists=True, file_okay=True, dir_okay=False))
1237
- @click.argument('timing_randomness_range', type=click.INT, default=0)
1238
- @click.option('--disable-sleep', is_flag=True, default=False)
1239
- def dvt_simulate_location_play(service_provider: LockdownClient, filename: str, timing_randomness_range: int,
1240
- disable_sleep: bool) -> None:
1241
- """ Simulate inputs from a given .gpx file """
1265
+ @dvt_simulate_location.command("play", cls=Command)
1266
+ @click.argument("filename", type=click.Path(exists=True, file_okay=True, dir_okay=False))
1267
+ @click.argument("timing_randomness_range", type=click.INT, default=0)
1268
+ @click.option("--disable-sleep", is_flag=True, default=False)
1269
+ def dvt_simulate_location_play(
1270
+ service_provider: LockdownClient, filename: str, timing_randomness_range: int, disable_sleep: bool
1271
+ ) -> None:
1272
+ """Simulate inputs from a given .gpx file"""
1242
1273
  with DvtSecureSocketProxyService(service_provider) as dvt:
1243
- LocationSimulation(dvt).play_gpx_file(filename, disable_sleep=disable_sleep,
1244
- timing_randomness_range=timing_randomness_range)
1274
+ LocationSimulation(dvt).play_gpx_file(
1275
+ filename, disable_sleep=disable_sleep, timing_randomness_range=timing_randomness_range
1276
+ )
1245
1277
  OSUTILS.wait_return()
1246
1278
 
1247
1279
 
1248
1280
  @developer.group()
1249
1281
  def core_device() -> None:
1250
- """ Access features exposed by the DeveloperDiskImage """
1282
+ """Access features exposed by the DeveloperDiskImage"""
1251
1283
  pass
1252
1284
 
1253
1285
 
1254
1286
  async def core_device_list_directory_task(
1255
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str) -> None:
1287
+ service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str
1288
+ ) -> None:
1256
1289
  async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
1257
1290
  print_json(await file_service.retrieve_directory_list(path))
1258
1291
 
1259
1292
 
1260
- @core_device.command('list-directory', cls=RSDCommand)
1261
- @click.argument('domain', type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1262
- @click.argument('path')
1263
- @click.option('--identifier', default='')
1293
+ @core_device.command("list-directory", cls=RSDCommand)
1294
+ @click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1295
+ @click.argument("path")
1296
+ @click.option("--identifier", default="")
1264
1297
  def core_device_list_directory(
1265
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str) -> None:
1266
- """ List directory at given domain-path """
1298
+ service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str
1299
+ ) -> None:
1300
+ """List directory at given domain-path"""
1267
1301
  asyncio.run(core_device_list_directory_task(service_provider, domain, path, identifier))
1268
1302
 
1269
1303
 
1270
1304
  async def core_device_read_file_task(
1271
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str,
1272
- output: Optional[IO]) -> None:
1305
+ service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, output: Optional[IO]
1306
+ ) -> None:
1273
1307
  async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
1274
1308
  buf = await file_service.retrieve_file(path)
1275
1309
  if output is not None:
@@ -1278,65 +1312,110 @@ async def core_device_read_file_task(
1278
1312
  print(try_decode(buf))
1279
1313
 
1280
1314
 
1281
- @core_device.command('read-file', cls=RSDCommand)
1282
- @click.argument('domain', type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1283
- @click.argument('path')
1284
- @click.option('--identifier', default='')
1285
- @click.option('-o', '--output', type=click.File('wb'))
1315
+ @core_device.command("read-file", cls=RSDCommand)
1316
+ @click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1317
+ @click.argument("path")
1318
+ @click.option("--identifier", default="")
1319
+ @click.option("-o", "--output", type=click.File("wb"))
1286
1320
  def core_device_read_file(
1287
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str,
1288
- output: Optional[IO]) -> None:
1289
- """ Read file from given domain-path """
1321
+ service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, output: Optional[IO]
1322
+ ) -> None:
1323
+ """Read file from given domain-path"""
1290
1324
  asyncio.run(core_device_read_file_task(service_provider, domain, path, identifier, output))
1291
1325
 
1292
1326
 
1293
1327
  async def core_device_propose_empty_file_task(
1294
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, file_permissions: int,
1295
- uid: int, gid: int, creation_time: int, last_modification_time: int) -> None:
1328
+ service_provider: RemoteServiceDiscoveryService,
1329
+ domain: str,
1330
+ path: str,
1331
+ identifier: str,
1332
+ file_permissions: int,
1333
+ uid: int,
1334
+ gid: int,
1335
+ creation_time: int,
1336
+ last_modification_time: int,
1337
+ ) -> None:
1296
1338
  async with FileServiceService(service_provider, APPLE_DOMAIN_DICT[domain], identifier) as file_service:
1297
1339
  await file_service.propose_empty_file(path, file_permissions, uid, gid, creation_time, last_modification_time)
1298
1340
 
1299
1341
 
1300
- @core_device.command('propose-empty-file', cls=RSDCommand)
1301
- @click.argument('domain', type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1302
- @click.argument('path')
1303
- @click.option('--identifier', default='')
1304
- @click.option('--file-permissions', type=click.INT, default=0o644)
1305
- @click.option('--uid', type=click.INT, default=501)
1306
- @click.option('--gid', type=click.INT, default=501)
1307
- @click.option('--creation-time', type=click.INT, default=time.time())
1308
- @click.option('--last-modification-time', type=click.INT, default=time.time())
1342
+ @core_device.command("propose-empty-file", cls=RSDCommand)
1343
+ @click.argument("domain", type=click.Choice(APPLE_DOMAIN_DICT.keys()))
1344
+ @click.argument("path")
1345
+ @click.option("--identifier", default="")
1346
+ @click.option("--file-permissions", type=click.INT, default=0o644)
1347
+ @click.option("--uid", type=click.INT, default=501)
1348
+ @click.option("--gid", type=click.INT, default=501)
1349
+ @click.option("--creation-time", type=click.INT, default=time.time())
1350
+ @click.option("--last-modification-time", type=click.INT, default=time.time())
1309
1351
  def core_device_propose_empty_file(
1310
- service_provider: RemoteServiceDiscoveryService, domain: str, path: str, identifier: str, file_permissions: int,
1311
- uid: int, gid: int, creation_time: int, last_modification_time: int) -> None:
1312
- """ Write an empty file to given domain-path """
1313
- asyncio.run(core_device_propose_empty_file_task(service_provider, domain, path, identifier, file_permissions, uid,
1314
- gid, creation_time, last_modification_time))
1352
+ service_provider: RemoteServiceDiscoveryService,
1353
+ domain: str,
1354
+ path: str,
1355
+ identifier: str,
1356
+ file_permissions: int,
1357
+ uid: int,
1358
+ gid: int,
1359
+ creation_time: int,
1360
+ last_modification_time: int,
1361
+ ) -> None:
1362
+ """Write an empty file to given domain-path"""
1363
+ asyncio.run(
1364
+ core_device_propose_empty_file_task(
1365
+ service_provider,
1366
+ domain,
1367
+ path,
1368
+ identifier,
1369
+ file_permissions,
1370
+ uid,
1371
+ gid,
1372
+ creation_time,
1373
+ last_modification_time,
1374
+ )
1375
+ )
1315
1376
 
1316
1377
 
1317
1378
  async def core_device_list_launch_application_task(
1318
- service_provider: RemoteServiceDiscoveryService, bundle_identifier: str, argument: list[str],
1319
- kill_existing: bool, suspended: bool, env: list[tuple[str, str]]) -> None:
1379
+ service_provider: RemoteServiceDiscoveryService,
1380
+ bundle_identifier: str,
1381
+ argument: list[str],
1382
+ kill_existing: bool,
1383
+ suspended: bool,
1384
+ env: list[tuple[str, str]],
1385
+ ) -> None:
1320
1386
  async with AppServiceService(service_provider) as app_service:
1321
- print_json(await app_service.launch_application(bundle_identifier, argument, kill_existing,
1322
- suspended, dict(env)))
1323
-
1324
-
1325
- @core_device.command('launch-application', cls=RSDCommand)
1326
- @click.argument('bundle_identifier')
1327
- @click.argument('argument', nargs=-1)
1328
- @click.option('--kill-existing/--no-kill-existing', default=True,
1329
- help='Whether to kill an existing instance of this process')
1330
- @click.option('--suspended', is_flag=True, help='Same as WaitForDebugger')
1331
- @click.option('--env', multiple=True, type=click.Tuple((str, str)),
1332
- help='Environment variables to pass to process given as a list of key value')
1387
+ print_json(
1388
+ await app_service.launch_application(bundle_identifier, argument, kill_existing, suspended, dict(env))
1389
+ )
1390
+
1391
+
1392
+ @core_device.command("launch-application", cls=RSDCommand)
1393
+ @click.argument("bundle_identifier")
1394
+ @click.argument("argument", nargs=-1)
1395
+ @click.option(
1396
+ "--kill-existing/--no-kill-existing", default=True, help="Whether to kill an existing instance of this process"
1397
+ )
1398
+ @click.option("--suspended", is_flag=True, help="Same as WaitForDebugger")
1399
+ @click.option(
1400
+ "--env",
1401
+ multiple=True,
1402
+ type=click.Tuple((str, str)),
1403
+ help="Environment variables to pass to process given as a list of key value",
1404
+ )
1333
1405
  def core_device_launch_application(
1334
- service_provider: RemoteServiceDiscoveryService, bundle_identifier: str, argument: tuple[str],
1335
- kill_existing: bool, suspended: bool, env: list[tuple[str, str]]) -> None:
1336
- """ Launch application """
1406
+ service_provider: RemoteServiceDiscoveryService,
1407
+ bundle_identifier: str,
1408
+ argument: tuple[str],
1409
+ kill_existing: bool,
1410
+ suspended: bool,
1411
+ env: list[tuple[str, str]],
1412
+ ) -> None:
1413
+ """Launch application"""
1337
1414
  asyncio.run(
1338
1415
  core_device_list_launch_application_task(
1339
- service_provider, bundle_identifier, list(argument), kill_existing, suspended, env))
1416
+ service_provider, bundle_identifier, list(argument), kill_existing, suspended, env
1417
+ )
1418
+ )
1340
1419
 
1341
1420
 
1342
1421
  async def core_device_list_processes_task(service_provider: RemoteServiceDiscoveryService) -> None:
@@ -1344,36 +1423,38 @@ async def core_device_list_processes_task(service_provider: RemoteServiceDiscove
1344
1423
  print_json(await app_service.list_processes())
1345
1424
 
1346
1425
 
1347
- @core_device.command('list-processes', cls=RSDCommand)
1426
+ @core_device.command("list-processes", cls=RSDCommand)
1348
1427
  def core_device_list_processes(service_provider: RemoteServiceDiscoveryService) -> None:
1349
- """ Get process list """
1428
+ """Get process list"""
1350
1429
  asyncio.run(core_device_list_processes_task(service_provider))
1351
1430
 
1352
1431
 
1353
- async def core_device_uninstall_app_task(service_provider: RemoteServiceDiscoveryService,
1354
- bundle_identifier: str) -> None:
1432
+ async def core_device_uninstall_app_task(
1433
+ service_provider: RemoteServiceDiscoveryService, bundle_identifier: str
1434
+ ) -> None:
1355
1435
  async with AppServiceService(service_provider) as app_service:
1356
1436
  await app_service.uninstall_app(bundle_identifier)
1357
1437
 
1358
1438
 
1359
- @core_device.command('uninstall', cls=RSDCommand)
1360
- @click.argument('bundle_identifier')
1439
+ @core_device.command("uninstall", cls=RSDCommand)
1440
+ @click.argument("bundle_identifier")
1361
1441
  def core_device_uninstall_app(service_provider: RemoteServiceDiscoveryService, bundle_identifier: str) -> None:
1362
- """ Uninstall application """
1442
+ """Uninstall application"""
1363
1443
  asyncio.run(core_device_uninstall_app_task(service_provider, bundle_identifier))
1364
1444
 
1365
1445
 
1366
1446
  async def core_device_send_signal_to_process_task(
1367
- service_provider: RemoteServiceDiscoveryService, pid: int, signal: int) -> None:
1447
+ service_provider: RemoteServiceDiscoveryService, pid: int, signal: int
1448
+ ) -> None:
1368
1449
  async with AppServiceService(service_provider) as app_service:
1369
1450
  print_json(await app_service.send_signal_to_process(pid, signal))
1370
1451
 
1371
1452
 
1372
- @core_device.command('send-signal-to-process', cls=RSDCommand)
1373
- @click.argument('pid', type=click.INT)
1374
- @click.argument('signal', type=click.INT)
1453
+ @core_device.command("send-signal-to-process", cls=RSDCommand)
1454
+ @click.argument("pid", type=click.INT)
1455
+ @click.argument("signal", type=click.INT)
1375
1456
  def core_device_send_signal_to_process(service_provider: RemoteServiceDiscoveryService, pid: int, signal: int) -> None:
1376
- """ Send signal to process """
1457
+ """Send signal to process"""
1377
1458
  asyncio.run(core_device_send_signal_to_process_task(service_provider, pid, signal))
1378
1459
 
1379
1460
 
@@ -1382,9 +1463,9 @@ async def core_device_get_device_info_task(service_provider: RemoteServiceDiscov
1382
1463
  print_json(await app_service.get_device_info())
1383
1464
 
1384
1465
 
1385
- @core_device.command('get-device-info', cls=RSDCommand)
1466
+ @core_device.command("get-device-info", cls=RSDCommand)
1386
1467
  def core_device_get_device_info(service_provider: RemoteServiceDiscoveryService) -> None:
1387
- """ Get device information """
1468
+ """Get device information"""
1388
1469
  asyncio.run(core_device_get_device_info_task(service_provider))
1389
1470
 
1390
1471
 
@@ -1393,22 +1474,22 @@ async def core_device_get_display_info_task(service_provider: RemoteServiceDisco
1393
1474
  print_json(await app_service.get_display_info())
1394
1475
 
1395
1476
 
1396
- @core_device.command('get-display-info', cls=RSDCommand)
1477
+ @core_device.command("get-display-info", cls=RSDCommand)
1397
1478
  def core_device_get_display_info(service_provider: RemoteServiceDiscoveryService) -> None:
1398
- """ Get display information """
1479
+ """Get display information"""
1399
1480
  asyncio.run(core_device_get_display_info_task(service_provider))
1400
1481
 
1401
1482
 
1402
1483
  async def core_device_query_mobilegestalt_task(service_provider: RemoteServiceDiscoveryService, key: list[str]) -> None:
1403
- """ Query MobileGestalt """
1484
+ """Query MobileGestalt"""
1404
1485
  async with DeviceInfoService(service_provider) as app_service:
1405
1486
  print_json(await app_service.query_mobilegestalt(key))
1406
1487
 
1407
1488
 
1408
- @core_device.command('query-mobilegestalt', cls=RSDCommand)
1409
- @click.argument('key', nargs=-1, type=click.STRING)
1489
+ @core_device.command("query-mobilegestalt", cls=RSDCommand)
1490
+ @click.argument("key", nargs=-1, type=click.STRING)
1410
1491
  def core_device_query_mobilegestalt(service_provider: RemoteServiceDiscoveryService, key: tuple[str]) -> None:
1411
- """ Query MobileGestalt """
1492
+ """Query MobileGestalt"""
1412
1493
  asyncio.run(core_device_query_mobilegestalt_task(service_provider, list(key)))
1413
1494
 
1414
1495
 
@@ -1417,9 +1498,9 @@ async def core_device_get_lockstate_task(service_provider: RemoteServiceDiscover
1417
1498
  print_json(await app_service.get_lockstate())
1418
1499
 
1419
1500
 
1420
- @core_device.command('get-lockstate', cls=RSDCommand)
1501
+ @core_device.command("get-lockstate", cls=RSDCommand)
1421
1502
  def core_device_get_lockstate(service_provider: RemoteServiceDiscoveryService) -> None:
1422
- """ Get lockstate """
1503
+ """Get lockstate"""
1423
1504
  asyncio.run(core_device_get_lockstate_task(service_provider))
1424
1505
 
1425
1506
 
@@ -1428,9 +1509,9 @@ async def core_device_list_apps_task(service_provider: RemoteServiceDiscoverySer
1428
1509
  print_json(await app_service.list_apps())
1429
1510
 
1430
1511
 
1431
- @core_device.command('list-apps', cls=RSDCommand)
1512
+ @core_device.command("list-apps", cls=RSDCommand)
1432
1513
  def core_device_list_apps(service_provider: RemoteServiceDiscoveryService) -> None:
1433
- """ Get application list """
1514
+ """Get application list"""
1434
1515
  asyncio.run(core_device_list_apps_task(service_provider))
1435
1516
 
1436
1517
 
@@ -1438,20 +1519,21 @@ async def core_device_sysdiagnose_task(service_provider: RemoteServiceDiscoveryS
1438
1519
  output = Path(output)
1439
1520
  async with DiagnosticsServiceService(service_provider) as service:
1440
1521
  response = await service.capture_sysdiagnose(False)
1441
- logger.info(f'Operation response: {response}')
1522
+ logger.info(f"Operation response: {response}")
1442
1523
  if output.is_dir():
1443
1524
  output /= response.preferred_filename
1444
- logger.info(f'Downloading sysdiagnose to: {output}')
1525
+ logger.info(f"Downloading sysdiagnose to: {output}")
1445
1526
 
1446
1527
  # get the file over lockdownd which is WAYYY faster
1447
1528
  lockdown = create_using_usbmux(service_provider.udid)
1448
1529
  with CrashReportsManager(lockdown) as crash_reports_manager:
1449
- crash_reports_manager.afc.pull(posixpath.join(f'/DiagnosticLogs/sysdiagnose/{response.preferred_filename}'),
1450
- output)
1530
+ crash_reports_manager.afc.pull(
1531
+ posixpath.join(f"/DiagnosticLogs/sysdiagnose/{response.preferred_filename}"), output
1532
+ )
1451
1533
 
1452
1534
 
1453
- @core_device.command('sysdiagnose', cls=RSDCommand)
1454
- @click.argument('output', type=click.Path(dir_okay=True, file_okay=True, exists=True))
1535
+ @core_device.command("sysdiagnose", cls=RSDCommand)
1536
+ @click.argument("output", type=click.Path(dir_okay=True, file_okay=True, exists=True))
1455
1537
  def core_device_sysdiagnose(service_provider: RemoteServiceDiscoveryService, output: str) -> None:
1456
- """ Execute sysdiagnose and fetch the output file """
1538
+ """Execute sysdiagnose and fetch the output file"""
1457
1539
  asyncio.run(core_device_sysdiagnose_task(service_provider, output))