pymobiledevice3 4.27.0__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 +363 -293
  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 +64 -50
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +315 -193
  135. pymobiledevice3/usbmux.py +197 -148
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/METADATA +2 -6
  138. pymobiledevice3-5.1.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-4.27.0.dist-info/RECORD +0 -172
  140. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-4.27.0.dist-info → pymobiledevice3-5.1.2.dist-info}/top_level.txt +0 -0
@@ -24,51 +24,50 @@ from pymobiledevice3.tunneld.api import TUNNELD_DEFAULT_ADDRESS, async_get_tunne
24
24
  from pymobiledevice3.usbmux import select_devices_by_connection_type
25
25
 
26
26
  COLORED_OUTPUT = True
27
- UDID_ENV_VAR = 'PYMOBILEDEVICE3_UDID'
28
- TUNNEL_ENV_VAR = 'PYMOBILEDEVICE3_TUNNEL'
29
- USBMUX_ENV_VAR = 'PYMOBILEDEVICE3_USBMUX'
27
+ UDID_ENV_VAR = "PYMOBILEDEVICE3_UDID"
28
+ TUNNEL_ENV_VAR = "PYMOBILEDEVICE3_TUNNEL"
29
+ USBMUX_ENV_VAR = "PYMOBILEDEVICE3_USBMUX"
30
30
  OSUTILS = get_os_utils()
31
31
 
32
- USBMUX_OPTION_HELP = (f'usbmuxd listener address (in the form of either /path/to/unix/socket OR HOST:PORT). '
33
- f'Can be specified via {USBMUX_ENV_VAR} envvar')
32
+ USBMUX_OPTION_HELP = (
33
+ f"usbmuxd listener address (in the form of either /path/to/unix/socket OR HOST:PORT). "
34
+ f"Can be specified via {USBMUX_ENV_VAR} envvar"
35
+ )
34
36
 
35
37
 
36
38
  class RSDOption(Option):
37
39
  def __init__(self, *args, **kwargs):
38
- self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
39
- help = kwargs.get('help', '')
40
+ self.mutually_exclusive = set(kwargs.pop("mutually_exclusive", []))
41
+ help_option = kwargs.get("help", "")
40
42
  if self.mutually_exclusive:
41
- ex_str = ', '.join(self.mutually_exclusive)
42
- kwargs['help'] = help + (
43
- '\nNOTE: This argument is mutually exclusive with '
44
- ' arguments: [' + ex_str + '].'
43
+ ex_str = ", ".join(self.mutually_exclusive)
44
+ kwargs["help"] = help_option + (
45
+ "\nNOTE: This argument is mutually exclusive with arguments: [" + ex_str + "]."
45
46
  )
46
47
  super().__init__(*args, **kwargs)
47
48
 
48
49
  def handle_parse_result(self, ctx, opts, args):
49
- if (isinstance(ctx.command, RSDCommand) and not (isinstance(ctx.command, Command)) and
50
- ('rsd_service_provider_using_tunneld' not in opts) and ('rsd_service_provider_manually' not in opts)):
50
+ if (
51
+ isinstance(ctx.command, RSDCommand)
52
+ and not (isinstance(ctx.command, Command))
53
+ and ("rsd_service_provider_using_tunneld" not in opts)
54
+ and ("rsd_service_provider_manually" not in opts)
55
+ ):
51
56
  # defaulting to `--tunnel ''` if no remote option was specified
52
- opts['rsd_service_provider_using_tunneld'] = ''
57
+ opts["rsd_service_provider_using_tunneld"] = ""
53
58
  if self.mutually_exclusive.intersection(opts) and self.name in opts:
54
59
  raise UsageError(
55
- 'Illegal usage: `{}` is mutually exclusive with '
56
- 'arguments `{}`.'.format(
57
- self.name,
58
- ', '.join(self.mutually_exclusive)
60
+ "Illegal usage: `{}` is mutually exclusive with arguments `{}`.".format(
61
+ self.name, ", ".join(self.mutually_exclusive)
59
62
  )
60
63
  )
61
64
 
62
- return super().handle_parse_result(
63
- ctx,
64
- opts,
65
- args
66
- )
65
+ return super().handle_parse_result(ctx, opts, args)
67
66
 
68
67
 
69
68
  def default_json_encoder(obj):
70
69
  if isinstance(obj, bytes):
71
- return f'<{obj.hex()}>'
70
+ return f"<{obj.hex()}>"
72
71
  if isinstance(obj, datetime.datetime):
73
72
  return str(obj)
74
73
  if isinstance(obj, uuid.UUID):
@@ -81,8 +80,9 @@ def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder
81
80
  colored = user_requested_colored_output()
82
81
  formatted_json = json.dumps(buf, sort_keys=True, indent=4, default=default)
83
82
  if colored and os.isatty(sys.stdout.fileno()):
84
- colorful_json = highlight(formatted_json, lexers.JsonLexer(),
85
- formatters.Terminal256Formatter(style='stata-dark'))
83
+ colorful_json = highlight(
84
+ formatted_json, lexers.JsonLexer(), formatters.Terminal256Formatter(style="stata-dark")
85
+ )
86
86
  print(colorful_json)
87
87
  return colorful_json
88
88
  else:
@@ -91,11 +91,11 @@ def print_json(buf, colored: Optional[bool] = None, default=default_json_encoder
91
91
 
92
92
 
93
93
  def print_hex(data, colored=True):
94
- hex_dump = hexdump.hexdump(data, result='return')
94
+ hex_dump = hexdump.hexdump(data, result="return")
95
95
  if colored:
96
- print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style='native')))
96
+ print(highlight(hex_dump, lexers.HexdumpLexer(), formatters.Terminal256Formatter(style="native")))
97
97
  else:
98
- print(hex_dump, end='\n\n')
98
+ print(hex_dump, end="\n\n")
99
99
 
100
100
 
101
101
  def set_verbosity(ctx, param, value):
@@ -116,7 +116,7 @@ def user_requested_colored_output() -> bool:
116
116
 
117
117
 
118
118
  def get_last_used_terminal_formatting(buf: str) -> str:
119
- return '\x1b' + buf.rsplit('\x1b', 1)[1].split('m')[0] + 'm'
119
+ return "\x1b" + buf.rsplit("\x1b", 1)[1].split("m")[0] + "m"
120
120
 
121
121
 
122
122
  def sudo_required(func):
@@ -131,24 +131,24 @@ def sudo_required(func):
131
131
 
132
132
 
133
133
  def prompt_selection(choices: list[Any], message: str, idx: bool = False) -> Any:
134
- question = [inquirer3.List('selection', message=message, choices=choices, carousel=True)]
134
+ question = [inquirer3.List("selection", message=message, choices=choices, carousel=True)]
135
135
  try:
136
136
  result = inquirer3.prompt(question, theme=GreenPassion(), raise_keyboard_interrupt=True)
137
- except KeyboardInterrupt:
138
- raise click.ClickException('No selection was made')
139
- return result['selection'] if not idx else choices.index(result['selection'])
137
+ except KeyboardInterrupt as e:
138
+ raise click.ClickException("No selection was made") from e
139
+ return result["selection"] if not idx else choices.index(result["selection"])
140
140
 
141
141
 
142
142
  def prompt_device_list(device_list: list):
143
- return prompt_selection(device_list, 'Choose device')
143
+ return prompt_selection(device_list, "Choose device")
144
144
 
145
145
 
146
146
  def choose_service_provider(callback: Callable):
147
147
  def wrap_callback_calling(**kwargs: dict) -> None:
148
148
  service_provider = None
149
- lockdown_service_provider = kwargs.pop('lockdown_service_provider', None)
150
- rsd_service_provider_manually = kwargs.pop('rsd_service_provider_manually', None)
151
- rsd_service_provider_using_tunneld = kwargs.pop('rsd_service_provider_using_tunneld', None)
149
+ lockdown_service_provider = kwargs.pop("lockdown_service_provider", None)
150
+ rsd_service_provider_manually = kwargs.pop("rsd_service_provider_manually", None)
151
+ rsd_service_provider_using_tunneld = kwargs.pop("rsd_service_provider_using_tunneld", None)
152
152
  if lockdown_service_provider is not None:
153
153
  service_provider = lockdown_service_provider
154
154
  if rsd_service_provider_manually is not None:
@@ -161,20 +161,23 @@ def choose_service_provider(callback: Callable):
161
161
 
162
162
 
163
163
  def is_invoked_for_completion() -> bool:
164
- """ Returns True if the command is ivoked for autocompletion. """
165
- for env in os.environ.keys():
166
- if env.startswith('_') and env.endswith('_COMPLETE'):
167
- return True
168
- return False
164
+ """Returns True if the command is ivoked for autocompletion."""
165
+ return any(env.startswith("_") and env.endswith("_COMPLETE") for env in os.environ)
169
166
 
170
167
 
171
168
  class BaseCommand(click.Command):
172
169
  def __init__(self, *args, **kwargs):
173
170
  super().__init__(*args, **kwargs)
174
171
  self.params[:0] = [
175
- click.Option(('verbosity', '-v', '--verbose'), count=True, callback=set_verbosity, expose_value=False),
176
- click.Option(('color', '--color/--no-color'), default=True, callback=set_color_flag, is_flag=True,
177
- expose_value=False, help='colorize output'),
172
+ click.Option(("verbosity", "-v", "--verbose"), count=True, callback=set_verbosity, expose_value=False),
173
+ click.Option(
174
+ ("color", "--color/--no-color"),
175
+ default=True,
176
+ callback=set_color_flag,
177
+ is_flag=True,
178
+ expose_value=False,
179
+ help="colorize output",
180
+ ),
178
181
  ]
179
182
 
180
183
 
@@ -191,20 +194,35 @@ class LockdownCommand(BaseServiceProviderCommand):
191
194
  self.usbmux_address = None
192
195
  self.mobdev2_option = None
193
196
  self.params[:0] = [
194
- click.Option(('mobdev2', '--mobdev2'), callback=self.mobdev2, expose_value=False, default=None,
195
- help='Use bonjour browse for mobdev2 devices. Expected value IP address of the interface to '
196
- 'use. Leave empty to browse through all interfaces'),
197
- click.Option(('usbmux', '--usbmux'), callback=self.usbmux, expose_value=False,
198
- envvar=USBMUX_ENV_VAR, help=USBMUX_OPTION_HELP),
199
- click.Option(('lockdown_service_provider', '--udid'), envvar=UDID_ENV_VAR, callback=self.udid,
200
- help=f'Device unique identifier. You may pass {UDID_ENV_VAR} environment variable to pass this'
201
- f' option as well'),
197
+ click.Option(
198
+ ("mobdev2", "--mobdev2"),
199
+ callback=self.mobdev2,
200
+ expose_value=False,
201
+ default=None,
202
+ help="Use bonjour browse for mobdev2 devices. Expected value IP address of the interface to "
203
+ "use. Leave empty to browse through all interfaces",
204
+ ),
205
+ click.Option(
206
+ ("usbmux", "--usbmux"),
207
+ callback=self.usbmux,
208
+ expose_value=False,
209
+ envvar=USBMUX_ENV_VAR,
210
+ help=USBMUX_OPTION_HELP,
211
+ ),
212
+ click.Option(
213
+ ("lockdown_service_provider", "--udid"),
214
+ envvar=UDID_ENV_VAR,
215
+ callback=self.udid,
216
+ help=f"Device unique identifier. You may pass {UDID_ENV_VAR} environment variable to pass this"
217
+ f" option as well",
218
+ ),
202
219
  ]
203
220
 
204
- async def get_mobdev2_devices(self, udid: Optional[str] = None, ips: Optional[list[str]] = None) \
205
- -> list[TcpLockdownClient]:
221
+ async def get_mobdev2_devices(
222
+ self, udid: Optional[str] = None, ips: Optional[list[str]] = None
223
+ ) -> list[TcpLockdownClient]:
206
224
  result = []
207
- async for ip, lockdown in get_mobdev2_lockdowns(udid=udid, ips=ips):
225
+ async for _ip, lockdown in get_mobdev2_lockdowns(udid=udid, ips=ips):
208
226
  result.append(lockdown)
209
227
  return result
210
228
 
@@ -225,8 +243,11 @@ class LockdownCommand(BaseServiceProviderCommand):
225
243
  return self.service_provider
226
244
 
227
245
  if self.mobdev2_option is not None:
228
- devices = asyncio.run(self.get_mobdev2_devices(
229
- udid=value if value else None, ips=[self.mobdev2_option] if self.mobdev2_option else None))
246
+ devices = asyncio.run(
247
+ self.get_mobdev2_devices(
248
+ udid=value if value else None, ips=[self.mobdev2_option] if self.mobdev2_option else None
249
+ )
250
+ )
230
251
  if not devices:
231
252
  raise NoDeviceConnectedError()
232
253
 
@@ -240,29 +261,37 @@ class LockdownCommand(BaseServiceProviderCommand):
240
261
  if value is not None:
241
262
  return create_using_usbmux(serial=value)
242
263
 
243
- devices = select_devices_by_connection_type(connection_type='USB', usbmux_address=self.usbmux_address)
264
+ devices = select_devices_by_connection_type(connection_type="USB", usbmux_address=self.usbmux_address)
244
265
  if len(devices) <= 1:
245
266
  return create_using_usbmux(usbmux_address=self.usbmux_address)
246
267
 
247
- return prompt_device_list(
248
- [create_using_usbmux(serial=device.serial, usbmux_address=self.usbmux_address) for device in devices])
268
+ return prompt_device_list([
269
+ create_using_usbmux(serial=device.serial, usbmux_address=self.usbmux_address) for device in devices
270
+ ])
249
271
 
250
272
 
251
273
  class RSDCommand(BaseServiceProviderCommand):
252
274
  def __init__(self, *args, **kwargs):
253
275
  super().__init__(*args, **kwargs)
254
276
  self.params[:0] = [
255
- RSDOption(('rsd_service_provider_manually', '--rsd'), type=(str, int), callback=self.rsd,
256
- mutually_exclusive=['rsd_service_provider_using_tunneld'],
257
- help='\b\n'
258
- 'RSD hostname and port number (as provided by a `start-tunnel` subcommand).'),
259
- RSDOption(('rsd_service_provider_using_tunneld', '--tunnel'), callback=self.tunneld,
260
- mutually_exclusive=['rsd_service_provider_manually'], envvar=TUNNEL_ENV_VAR,
261
- help='\b\n'
262
- 'Either an empty string to force tunneld device selection, or a UDID of a tunneld '
263
- 'discovered device.\n'
264
- 'The string may be suffixed with :PORT in case tunneld is not serving at the default port.\n'
265
- f'This option may also be transferred as an environment variable: {TUNNEL_ENV_VAR}')
277
+ RSDOption(
278
+ ("rsd_service_provider_manually", "--rsd"),
279
+ type=(str, int),
280
+ callback=self.rsd,
281
+ mutually_exclusive=["rsd_service_provider_using_tunneld"],
282
+ help="\b\nRSD hostname and port number (as provided by a `start-tunnel` subcommand).",
283
+ ),
284
+ RSDOption(
285
+ ("rsd_service_provider_using_tunneld", "--tunnel"),
286
+ callback=self.tunneld,
287
+ mutually_exclusive=["rsd_service_provider_manually"],
288
+ envvar=TUNNEL_ENV_VAR,
289
+ help="\b\n"
290
+ "Either an empty string to force tunneld device selection, or a UDID of a tunneld "
291
+ "discovered device.\n"
292
+ "The string may be suffixed with :PORT in case tunneld is not serving at the default port.\n"
293
+ f"This option may also be transferred as an environment variable: {TUNNEL_ENV_VAR}",
294
+ ),
266
295
  ]
267
296
 
268
297
  def rsd(self, ctx, param: str, value: Optional[tuple[str, int]]) -> Optional[RemoteServiceDiscoveryService]:
@@ -278,19 +307,21 @@ class RSDCommand(BaseServiceProviderCommand):
278
307
 
279
308
  udid = udid.strip()
280
309
  port = TUNNELD_DEFAULT_ADDRESS[1]
281
- if ':' in udid:
282
- udid, port = udid.split(':')
310
+ if ":" in udid:
311
+ udid, port = udid.split(":")
283
312
 
284
313
  rsds = await async_get_tunneld_devices((TUNNELD_DEFAULT_ADDRESS[0], port))
285
314
  if len(rsds) == 0:
286
315
  raise NoDeviceConnectedError()
287
316
 
288
- if udid != '':
317
+ if udid != "":
289
318
  try:
290
319
  # Connect to the specified device
291
- self.service_provider = [rsd for rsd in rsds if rsd.udid == udid or rsd.udid.replace('-', '') == udid][0]
292
- except IndexError:
293
- raise DeviceNotFoundError(udid)
320
+ self.service_provider = next(
321
+ rsd for rsd in rsds if rsd.udid == udid or rsd.udid.replace("-", "") == udid
322
+ )
323
+ except IndexError as e:
324
+ raise DeviceNotFoundError(udid) from e
294
325
  else:
295
326
  if len(rsds) == 1:
296
327
  self.service_provider = rsds[0]
@@ -323,13 +354,13 @@ class CommandWithoutAutopair(Command):
323
354
 
324
355
 
325
356
  class BasedIntParamType(click.ParamType):
326
- name = 'based int'
357
+ name = "based int"
327
358
 
328
359
  def convert(self, value, param, ctx):
329
360
  try:
330
361
  return int(value, 0)
331
362
  except ValueError:
332
- self.fail(f'{value!r} is not a valid int.', param, ctx)
363
+ self.fail(f"{value!r} is not a valid int.", param, ctx)
333
364
 
334
365
 
335
366
  BASED_INT = BasedIntParamType()
@@ -12,11 +12,11 @@ def cli() -> None:
12
12
 
13
13
  @cli.group()
14
14
  def companion() -> None:
15
- """ List paired "companion" devices """
15
+ """List paired "companion" devices"""
16
16
  pass
17
17
 
18
18
 
19
- @companion.command('list', cls=Command)
19
+ @companion.command("list", cls=Command)
20
20
  def companion_list(service_provider: LockdownClient):
21
- """ list all paired companion devices """
22
- print_json(CompanionProxyService(service_provider).list(), default=lambda x: '<non-serializable>')
21
+ """list all paired companion devices"""
22
+ print_json(CompanionProxyService(service_provider).list(), default=lambda x: "<non-serializable>")
@@ -7,12 +7,12 @@ from plumbum import CommandNotFound, local
7
7
 
8
8
  logger = logging.getLogger(__name__)
9
9
 
10
- ShellCompletion = namedtuple('ShellCompletion', ['source', 'rc', 'path'])
10
+ ShellCompletion = namedtuple("ShellCompletion", ["source", "rc", "path"])
11
11
 
12
12
  COMPLETIONS = [
13
- ShellCompletion('zsh_source', Path('~/.zshrc').expanduser(), Path('~/.pymobiledevice3.zsh').expanduser()),
14
- ShellCompletion('bash_source', Path('~/.bashrc').expanduser(), Path('~/.pymobiledevice3.bash').expanduser()),
15
- ShellCompletion('fish_source', None, Path('~/.config/fish/completions/pymobiledevice3.fish').expanduser()),
13
+ ShellCompletion("zsh_source", Path("~/.zshrc").expanduser(), Path("~/.pymobiledevice3.zsh").expanduser()),
14
+ ShellCompletion("bash_source", Path("~/.bashrc").expanduser(), Path("~/.pymobiledevice3.bash").expanduser()),
15
+ ShellCompletion("fish_source", None, Path("~/.config/fish/completions/pymobiledevice3.fish").expanduser()),
16
16
  ]
17
17
 
18
18
 
@@ -29,9 +29,9 @@ def install_completions() -> None:
29
29
  If supplying an explicit shell script to write, install it there, otherwise install globally.
30
30
  """
31
31
  try:
32
- pymobiledevice3 = local['pymobiledevice3']
32
+ pymobiledevice3 = local["pymobiledevice3"]
33
33
  except CommandNotFound:
34
- logger.error('pymobiledevice3 main binary could not be found in your path.')
34
+ logger.error("pymobiledevice3 main binary could not be found in your path.")
35
35
  return
36
36
 
37
37
  for completion in COMPLETIONS:
@@ -39,12 +39,12 @@ def install_completions() -> None:
39
39
  if not completion.path.parent.exists():
40
40
  # fish is not installed, skip
41
41
  continue
42
- logger.info(f'Writing shell completions to: {completion.path}')
42
+ logger.info(f"Writing shell completions to: {completion.path}")
43
43
  completion.path.write_text(pymobiledevice3())
44
- line = f'source {completion.path}'
44
+ line = f"source {completion.path}"
45
45
 
46
46
  if not completion.rc.exists() or line in completion.rc.read_text():
47
47
  continue
48
48
 
49
- logger.info(f'Adding source line to {completion.rc}')
50
- completion.rc.write_text(f'{completion.rc.read_text()}\n{line}')
49
+ logger.info(f"Adding source line to {completion.rc}")
50
+ completion.rc.write_text(f"{completion.rc.read_text()}\n{line}")
@@ -13,70 +13,76 @@ def cli() -> None:
13
13
 
14
14
  @cli.group()
15
15
  def crash() -> None:
16
- """ Manage crash reports """
16
+ """Manage crash reports"""
17
17
  pass
18
18
 
19
19
 
20
- @crash.command('clear', cls=Command)
21
- @click.option('-f', '--flush', is_flag=True, default=False, help='flush before clear')
20
+ @crash.command("clear", cls=Command)
21
+ @click.option("-f", "--flush", is_flag=True, default=False, help="flush before clear")
22
22
  def crash_clear(service_provider: LockdownClient, flush):
23
- """ clear(/remove) all crash reports """
23
+ """clear(/remove) all crash reports"""
24
24
  crash_manager = CrashReportsManager(service_provider)
25
25
  if flush:
26
26
  crash_manager.flush()
27
27
  crash_manager.clear()
28
28
 
29
29
 
30
- @crash.command('pull', cls=Command)
31
- @click.argument('out', type=click.Path(file_okay=False))
32
- @click.argument('remote_file', type=click.Path(), required=False)
33
- @click.option('-e', '--erase', is_flag=True)
34
- @click.option('-m', '--match', help='Match given regex over enumerated basenames')
30
+ @crash.command("pull", cls=Command)
31
+ @click.argument("out", type=click.Path(file_okay=False))
32
+ @click.argument("remote_file", type=click.Path(), required=False)
33
+ @click.option("-e", "--erase", is_flag=True)
34
+ @click.option("-m", "--match", help="Match given regex over enumerated basenames")
35
35
  def crash_pull(service_provider: LockdownServiceProvider, out, remote_file, erase, match) -> None:
36
- """ pull all crash reports """
36
+ """pull all crash reports"""
37
37
  if remote_file is None:
38
- remote_file = '/'
38
+ remote_file = "/"
39
39
  CrashReportsManager(service_provider).pull(out, remote_file, erase, match)
40
40
 
41
41
 
42
- @crash.command('shell', cls=Command)
42
+ @crash.command("shell", cls=Command)
43
43
  def crash_shell(service_provider: LockdownClient):
44
- """ start an afc shell """
44
+ """start an afc shell"""
45
45
  CrashReportsShell.create(service_provider)
46
46
 
47
47
 
48
- @crash.command('ls', cls=Command)
49
- @click.argument('remote_file', type=click.Path(), required=False)
50
- @click.option('-d', '--depth', type=click.INT, default=1)
48
+ @crash.command("ls", cls=Command)
49
+ @click.argument("remote_file", type=click.Path(), required=False)
50
+ @click.option("-d", "--depth", type=click.INT, default=1)
51
51
  def crash_ls(service_provider: LockdownClient, remote_file, depth):
52
- """ List """
52
+ """List"""
53
53
  if remote_file is None:
54
- remote_file = '/'
54
+ remote_file = "/"
55
55
  for path in CrashReportsManager(service_provider).ls(remote_file, depth):
56
56
  print(path)
57
57
 
58
58
 
59
- @crash.command('flush', cls=Command)
59
+ @crash.command("flush", cls=Command)
60
60
  def crash_mover_flush(service_provider: LockdownClient):
61
- """ trigger com.apple.crashreportmover to flush all products into CrashReports directory """
61
+ """trigger com.apple.crashreportmover to flush all products into CrashReports directory"""
62
62
  CrashReportsManager(service_provider).flush()
63
63
 
64
64
 
65
- @crash.command('watch', cls=Command)
66
- @click.argument('name', required=False)
67
- @click.option('-r', '--raw', is_flag=True)
65
+ @crash.command("watch", cls=Command)
66
+ @click.argument("name", required=False)
67
+ @click.option("-r", "--raw", is_flag=True)
68
68
  def crash_mover_watch(service_provider: LockdownClient, name, raw):
69
- """ watch for crash report generation """
69
+ """watch for crash report generation"""
70
70
  for crash_report in CrashReportsManager(service_provider).watch(name=name, raw=raw):
71
71
  print(crash_report)
72
72
 
73
73
 
74
- @crash.command('sysdiagnose', cls=Command)
75
- @click.argument('out', type=click.Path(exists=False, dir_okay=True, file_okay=True))
76
- @click.option('-e', '--erase', is_flag=True, help='erase file after pulling')
77
- @click.option('-t', '--timeout', default=None, show_default=True, type=click.FLOAT,
78
- help='Maximum time in seconds to wait for the completion of sysdiagnose archive')
74
+ @crash.command("sysdiagnose", cls=Command)
75
+ @click.argument("out", type=click.Path(exists=False, dir_okay=True, file_okay=True))
76
+ @click.option("-e", "--erase", is_flag=True, help="erase file after pulling")
77
+ @click.option(
78
+ "-t",
79
+ "--timeout",
80
+ default=None,
81
+ show_default=True,
82
+ type=click.FLOAT,
83
+ help="Maximum time in seconds to wait for the completion of sysdiagnose archive",
84
+ )
79
85
  def crash_sysdiagnose(service_provider: LockdownClient, out, erase, timeout):
80
- """ get a sysdiagnose archive from device (requires user interaction) """
81
- print('Press Power+VolUp+VolDown for 0.215 seconds')
86
+ """get a sysdiagnose archive from device (requires user interaction)"""
87
+ print("Press Power+VolUp+VolDown for 0.215 seconds")
82
88
  CrashReportsManager(service_provider).get_new_sysdiagnose(out, erase=erase, timeout=timeout)