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
misc/plist_sniffer.py CHANGED
@@ -7,8 +7,8 @@ import click
7
7
  from scapy.packet import Packet, Raw
8
8
  from scapy.sendrecv import sniff
9
9
 
10
- BPLIST_MAGIC = b'bplist'
11
- PLIST_MAGIC = b'<plist'
10
+ BPLIST_MAGIC = b"bplist"
11
+ PLIST_MAGIC = b"<plist"
12
12
 
13
13
 
14
14
  class PcapSniffer:
@@ -20,13 +20,13 @@ class PcapSniffer:
20
20
 
21
21
  if BPLIST_MAGIC in packet:
22
22
  try:
23
- plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC):])
23
+ plist = plistlib.loads(packet[packet.find(BPLIST_MAGIC) :])
24
24
  self.report(plist)
25
25
  except plistlib.InvalidFileException:
26
26
  pass
27
27
  if PLIST_MAGIC in packet:
28
28
  try:
29
- plist = plistlib.loads(packet[packet.find(PLIST_MAGIC):])
29
+ plist = plistlib.loads(packet[packet.find(PLIST_MAGIC) :])
30
30
  self.report(plist)
31
31
  except xml.parsers.expat.ExpatError:
32
32
  pass
@@ -35,37 +35,37 @@ class PcapSniffer:
35
35
  try:
36
36
  print(plist)
37
37
  if self.file is not None:
38
- self.file.write('---\n')
38
+ self.file.write("---\n")
39
39
  self.file.write(pprint.pformat(plist))
40
- self.file.write('\n---\n')
40
+ self.file.write("\n---\n")
41
41
  except ValueError:
42
- print('failed to print plist')
42
+ print("failed to print plist")
43
43
 
44
44
 
45
45
  @click.group()
46
46
  def cli():
47
- """ Parse RemoteXPC traffic """
47
+ """Parse RemoteXPC traffic"""
48
48
  pass
49
49
 
50
50
 
51
51
  @cli.command()
52
- @click.argument('pcap', type=click.Path(exists=True, file_okay=True, dir_okay=False))
53
- @click.option('-o', '--out', type=click.File('wt'))
52
+ @click.argument("pcap", type=click.Path(exists=True, file_okay=True, dir_okay=False))
53
+ @click.option("-o", "--out", type=click.File("wt"))
54
54
  def offline(pcap: str, out: IO):
55
- """ Parse plists traffic from a .pcap file """
55
+ """Parse plists traffic from a .pcap file"""
56
56
  sniffer = PcapSniffer(out)
57
57
  for p in sniff(offline=pcap):
58
58
  sniffer.process_packet(p)
59
59
 
60
60
 
61
61
  @cli.command()
62
- @click.argument('iface')
63
- @click.option('-o', '--out', type=click.File('wt'))
62
+ @click.argument("iface")
63
+ @click.option("-o", "--out", type=click.File("wt"))
64
64
  def live(iface: str, out: IO):
65
- """ Parse plists live from a given network interface """
65
+ """Parse plists live from a given network interface"""
66
66
  sniffer = PcapSniffer(out)
67
67
  sniff(iface=iface, prn=sniffer.process_packet)
68
68
 
69
69
 
70
- if __name__ == '__main__':
70
+ if __name__ == "__main__":
71
71
  cli()
misc/remotexpc_sniffer.py CHANGED
@@ -22,7 +22,7 @@ FRAME_HEADER_SIZE = 9
22
22
 
23
23
 
24
24
  def create_stream_key(src: str, sport: int, dst: str, dport: int) -> str:
25
- return f'{src}/{sport}//{dst}/{dport}'
25
+ return f"{src}/{sport}//{dst}/{dport}"
26
26
 
27
27
 
28
28
  class TCPStream:
@@ -37,7 +37,7 @@ class TCPStream:
37
37
  self.segments = {} # data segments to add later
38
38
 
39
39
  def __repr__(self) -> str:
40
- return f'Stream<{self.key}>'
40
+ return f"Stream<{self.key}>"
41
41
 
42
42
  def __len__(self) -> int:
43
43
  return len(self.data)
@@ -58,12 +58,12 @@ class TCPStream:
58
58
  return False
59
59
  else:
60
60
  # if this data is in order (has a place to be inserted)
61
- self.data[seq_offset:seq_offset + data_len] = data
61
+ self.data[seq_offset : seq_offset + data_len] = data
62
62
  # check if there are any waiting data segments to add
63
63
  for seq_offset in sorted(self.segments.keys()):
64
64
  if seq_offset <= len(self.data): # if we can add this segment to the stream
65
65
  segment_payload = self.segments[seq_offset]
66
- self.data[seq_offset:seq_offset + len(segment_payload)] = segment_payload
66
+ self.data[seq_offset : seq_offset + len(segment_payload)] = segment_payload
67
67
  self.segments.pop(seq_offset)
68
68
  else:
69
69
  break # short circuit because list is sorted
@@ -72,12 +72,12 @@ class TCPStream:
72
72
 
73
73
  class H2Stream(TCPStream):
74
74
  def pop_frames(self) -> list[Frame]:
75
- """ Pop all available H2Frames """
75
+ """Pop all available H2Frames"""
76
76
 
77
77
  # If self.data starts with the http/2 magic bytes, pop them off
78
78
  if self.data.startswith(HTTP2_MAGIC):
79
- logger.debug('HTTP/2 magic bytes')
80
- self.data = self.data[len(HTTP2_MAGIC):]
79
+ logger.debug("HTTP/2 magic bytes")
80
+ self.data = self.data[len(HTTP2_MAGIC) :]
81
81
  self.seq += len(HTTP2_MAGIC)
82
82
 
83
83
  frames = []
@@ -120,13 +120,14 @@ class RemoteXPCSniffer:
120
120
  tcp_pkt = pkt[TCP]
121
121
  stream_key = create_stream_key(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
122
122
  stream = self._h2_streams.setdefault(
123
- stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport))
123
+ stream_key, H2Stream(net_pkt.src, tcp_pkt.sport, net_pkt.dst, tcp_pkt.dport)
124
+ )
124
125
  stream_finished_assembling = stream.add(tcp_pkt)
125
126
  if stream_finished_assembling: # if we just added something in order
126
127
  self._process_stream(stream)
127
128
 
128
129
  def _handle_data_frame(self, stream: H2Stream, frame: DataFrame) -> None:
129
- previous_frame_data = self._previous_frame_data.get(stream.key, b'')
130
+ previous_frame_data = self._previous_frame_data.get(stream.key, b"")
130
131
  try:
131
132
  payload = XpcWrapper.parse(previous_frame_data + frame.data).message.payload
132
133
  if payload is None:
@@ -134,10 +135,11 @@ class RemoteXPCSniffer:
134
135
  xpc_message = decode_xpc_object(payload.obj)
135
136
  except ConstError: # if we don't know what this payload is
136
137
  logger.debug(
137
- f'New Data frame {stream.src}->{stream.dst} on HTTP/2 stream {frame.stream_id} TCP port {stream.dport}')
138
+ f"New Data frame {stream.src}->{stream.dst} on HTTP/2 stream {frame.stream_id} TCP port {stream.dport}"
139
+ )
138
140
  hexdump(frame.data[:64])
139
141
  if len(frame.data) > 64:
140
- logger.debug(f'... {len(frame.data)} bytes')
142
+ logger.debug(f"... {len(frame.data)} bytes")
141
143
  return
142
144
  except StreamError:
143
145
  self._previous_frame_data[stream.key] = previous_frame_data + frame.data
@@ -149,27 +151,26 @@ class RemoteXPCSniffer:
149
151
  if xpc_message is None:
150
152
  return
151
153
 
152
- logger.info(f'As Python Object (#{frame.stream_id}): {pformat(xpc_message)}')
154
+ logger.info(f"As Python Object (#{frame.stream_id}): {pformat(xpc_message)}")
153
155
 
154
156
  # print `pairingData` if exists, since it contains an inner struct
155
- if 'value' not in xpc_message:
157
+ if "value" not in xpc_message:
156
158
  return
157
- message = xpc_message['value']['message']
158
- if 'plain' not in message:
159
+ message = xpc_message["value"]["message"]
160
+ if "plain" not in message:
159
161
  return
160
- plain = message['plain']['_0']
161
- if 'event' not in plain:
162
+ plain = message["plain"]["_0"]
163
+ if "event" not in plain:
162
164
  return
163
- pairing_data = plain['event']['_0']['pairingData']['_0']['data']
165
+ pairing_data = plain["event"]["_0"]["pairingData"]["_0"]["data"]
164
166
  logger.info(PairingDataComponentTLVBuf.parse(pairing_data))
165
167
 
166
168
  def _handle_single_frame(self, stream: H2Stream, frame: Frame) -> None:
167
- logger.debug(f'New HTTP/2 frame: {stream.key} ({frame})')
169
+ logger.debug(f"New HTTP/2 frame: {stream.key} ({frame})")
168
170
  if isinstance(frame, HeadersFrame):
169
- logger.debug(
170
- f'{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}')
171
+ logger.debug(f"{stream.src} opening stream {frame.stream_id} for communication on port {stream.dport}")
171
172
  elif isinstance(frame, GoAwayFrame):
172
- logger.debug(f'{stream.src} closing stream {frame.stream_id} on port {stream.sport}')
173
+ logger.debug(f"{stream.src} closing stream {frame.stream_id} on port {stream.sport}")
173
174
  elif isinstance(frame, DataFrame):
174
175
  self._handle_data_frame(stream, frame)
175
176
 
@@ -180,27 +181,27 @@ class RemoteXPCSniffer:
180
181
 
181
182
  @click.group()
182
183
  def cli():
183
- """ Parse RemoteXPC traffic """
184
+ """Parse RemoteXPC traffic"""
184
185
  pass
185
186
 
186
187
 
187
188
  @cli.command()
188
- @click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
189
+ @click.argument("file", type=click.Path(exists=True, file_okay=True, dir_okay=False))
189
190
  def offline(file: str):
190
- """ Parse RemoteXPC traffic from a .pcap file """
191
+ """Parse RemoteXPC traffic from a .pcap file"""
191
192
  sniffer = RemoteXPCSniffer()
192
193
  for p in sniff(offline=file):
193
194
  sniffer.process_packet(p)
194
195
 
195
196
 
196
197
  @cli.command()
197
- @click.argument('iface')
198
+ @click.argument("iface")
198
199
  def live(iface: str):
199
- """ Parse RemoteXPC live from a given network interface """
200
+ """Parse RemoteXPC live from a given network interface"""
200
201
  sniffer = RemoteXPCSniffer()
201
202
  sniff(iface=iface, prn=sniffer.process_packet)
202
203
 
203
204
 
204
- if __name__ == '__main__':
205
+ if __name__ == "__main__":
205
206
  coloredlogs.install(level=logging.DEBUG)
206
207
  cli()
@@ -13,34 +13,53 @@ import click
13
13
  import coloredlogs
14
14
 
15
15
  from pymobiledevice3.cli.cli_common import TUNNEL_ENV_VAR, isatty
16
- from pymobiledevice3.exceptions import AccessDeniedError, CloudConfigurationAlreadyPresentError, \
17
- ConnectionFailedError, ConnectionFailedToUsbmuxdError, DeprecationError, DeveloperModeError, \
18
- DeveloperModeIsNotEnabledError, DeviceHasPasscodeSetError, DeviceNotFoundError, FeatureNotSupportedError, \
19
- InternalError, InvalidServiceError, MessageNotSupportedError, MissingValueError, NoDeviceConnectedError, \
20
- NotEnoughDiskSpaceError, NotPairedError, OSNotSupportedError, PairingDialogResponsePendingError, \
21
- PasswordRequiredError, QuicProtocolNotSupportedError, RSDRequiredError, SetProhibitedError, \
22
- TunneldConnectionError, UserDeniedPairingError
16
+ from pymobiledevice3.exceptions import (
17
+ AccessDeniedError,
18
+ CloudConfigurationAlreadyPresentError,
19
+ ConnectionFailedError,
20
+ ConnectionFailedToUsbmuxdError,
21
+ DeprecationError,
22
+ DeveloperModeError,
23
+ DeveloperModeIsNotEnabledError,
24
+ DeviceHasPasscodeSetError,
25
+ DeviceNotFoundError,
26
+ FeatureNotSupportedError,
27
+ InternalError,
28
+ InvalidServiceError,
29
+ MessageNotSupportedError,
30
+ MissingValueError,
31
+ NoDeviceConnectedError,
32
+ NotEnoughDiskSpaceError,
33
+ NotPairedError,
34
+ OSNotSupportedError,
35
+ PairingDialogResponsePendingError,
36
+ PasswordRequiredError,
37
+ QuicProtocolNotSupportedError,
38
+ RSDRequiredError,
39
+ SetProhibitedError,
40
+ TunneldConnectionError,
41
+ UserDeniedPairingError,
42
+ )
23
43
  from pymobiledevice3.lockdown import retry_create_using_usbmux
24
44
  from pymobiledevice3.osu.os_utils import get_os_utils
25
45
 
26
46
  coloredlogs.install(level=logging.INFO)
27
47
 
28
- logging.getLogger('quic').disabled = True
29
- logging.getLogger('asyncio').disabled = True
30
- logging.getLogger('zeroconf').disabled = True
31
- logging.getLogger('parso.cache').disabled = True
32
- logging.getLogger('parso.cache.pickle').disabled = True
33
- logging.getLogger('parso.python.diff').disabled = True
34
- logging.getLogger('humanfriendly.prompts').disabled = True
35
- logging.getLogger('blib2to3.pgen2.driver').disabled = True
36
- logging.getLogger('urllib3.connectionpool').disabled = True
48
+ logging.getLogger("quic").disabled = True
49
+ logging.getLogger("asyncio").disabled = True
50
+ logging.getLogger("parso.cache").disabled = True
51
+ logging.getLogger("parso.cache.pickle").disabled = True
52
+ logging.getLogger("parso.python.diff").disabled = True
53
+ logging.getLogger("humanfriendly.prompts").disabled = True
54
+ logging.getLogger("blib2to3.pgen2.driver").disabled = True
55
+ logging.getLogger("urllib3.connectionpool").disabled = True
37
56
 
38
57
  logger = logging.getLogger(__name__)
39
58
 
40
59
  # For issue https://github.com/doronz88/pymobiledevice3/issues/1217, details: https://bugs.python.org/issue37373
41
- if sys.platform == 'win32':
60
+ if sys.platform == "win32":
42
61
  with warnings.catch_warnings():
43
- warnings.simplefilter('ignore', category=DeprecationWarning)
62
+ warnings.simplefilter("ignore", category=DeprecationWarning)
44
63
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
45
64
 
46
65
  INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
@@ -62,36 +81,36 @@ INVALID_SERVICE_MESSAGE = """Failed to start service. Possible reasons are:
62
81
  https://github.com/doronz88/pymobiledevice3/issues/new?assignees=&labels=&projects=&template=bug_report.md&title=
63
82
  """
64
83
 
65
- CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'], max_content_width=400)
84
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "max_content_width": 400}
66
85
 
67
86
  # Mapping of index options to import file names
68
87
  CLI_GROUPS = {
69
- 'activation': 'activation',
70
- 'afc': 'afc',
71
- 'amfi': 'amfi',
72
- 'apps': 'apps',
73
- 'backup2': 'backup',
74
- 'bonjour': 'bonjour',
75
- 'companion': 'companion_proxy',
76
- 'crash': 'crash',
77
- 'developer': 'developer',
78
- 'diagnostics': 'diagnostics',
79
- 'lockdown': 'lockdown',
80
- 'mounter': 'mounter',
81
- 'notification': 'notification',
82
- 'pcap': 'pcap',
83
- 'power-assertion': 'power_assertion',
84
- 'processes': 'processes',
85
- 'profile': 'profile',
86
- 'provision': 'provision',
87
- 'remote': 'remote',
88
- 'restore': 'restore',
89
- 'springboard': 'springboard',
90
- 'syslog': 'syslog',
91
- 'usbmux': 'usbmux',
92
- 'webinspector': 'webinspector',
93
- 'version': 'version',
94
- 'install-completions': 'completions',
88
+ "activation": "activation",
89
+ "afc": "afc",
90
+ "amfi": "amfi",
91
+ "apps": "apps",
92
+ "backup2": "backup",
93
+ "bonjour": "bonjour",
94
+ "companion": "companion_proxy",
95
+ "crash": "crash",
96
+ "developer": "developer",
97
+ "diagnostics": "diagnostics",
98
+ "lockdown": "lockdown",
99
+ "mounter": "mounter",
100
+ "notification": "notification",
101
+ "pcap": "pcap",
102
+ "power-assertion": "power_assertion",
103
+ "processes": "processes",
104
+ "profile": "profile",
105
+ "provision": "provision",
106
+ "remote": "remote",
107
+ "restore": "restore",
108
+ "springboard": "springboard",
109
+ "syslog": "syslog",
110
+ "usbmux": "usbmux",
111
+ "webinspector": "webinspector",
112
+ "version": "version",
113
+ "install-completions": "completions",
95
114
  }
96
115
 
97
116
  # Set if used the `--reconnect` option
@@ -103,26 +122,26 @@ class Pmd3Cli(click.Group):
103
122
  return CLI_GROUPS.keys()
104
123
 
105
124
  def get_command(self, ctx: click.Context, name: str) -> click.Command:
106
- if name not in CLI_GROUPS.keys():
125
+ if name not in CLI_GROUPS:
107
126
  self.handle_invalid_command(ctx, name)
108
127
  return self.import_and_get_command(ctx, name)
109
128
 
110
129
  def handle_invalid_command(self, ctx: click.Context, name: str) -> None:
111
130
  suggested_commands = self.search_commands(name)
112
131
  suggestion = self.format_suggestions(suggested_commands)
113
- ctx.fail(f'No such command {name!r}{suggestion}')
132
+ ctx.fail(f"No such command {name!r}{suggestion}")
114
133
 
115
134
  @staticmethod
116
135
  def format_suggestions(suggestions: list[str]) -> str:
117
136
  if not suggestions:
118
- return ''
119
- cmds = textwrap.indent('\n'.join(suggestions), ' ' * 4)
120
- return f'\nDid you mean this?\n{cmds}'
137
+ return ""
138
+ cmds = textwrap.indent("\n".join(suggestions), " " * 4)
139
+ return f"\nDid you mean this?\n{cmds}"
121
140
 
122
141
  @staticmethod
123
142
  def import_and_get_command(ctx: click.Context, name: str) -> click.Command:
124
- module_name = f'pymobiledevice3.cli.{CLI_GROUPS[name]}'
125
- mod = __import__(module_name, None, None, ['cli'])
143
+ module_name = f"pymobiledevice3.cli.{CLI_GROUPS[name]}"
144
+ mod = __import__(module_name, None, None, ["cli"])
126
145
  command = mod.cli.get_command(ctx, name)
127
146
  if not command:
128
147
  command_name = mod.cli.list_commands(ctx)[0]
@@ -131,20 +150,20 @@ class Pmd3Cli(click.Group):
131
150
 
132
151
  @staticmethod
133
152
  def highlight_keyword(text: str, keyword: str) -> str:
134
- return re.sub(f'({keyword})', click.style('\\1', bold=True), text, flags=re.IGNORECASE)
153
+ return re.sub(f"({keyword})", click.style("\\1", bold=True), text, flags=re.IGNORECASE)
135
154
 
136
155
  @staticmethod
137
156
  def collect_commands(command: click.Command) -> Union[str, list[str]]:
138
157
  commands = []
139
158
  if isinstance(command, click.Group):
140
- for k, v in command.commands.items():
159
+ for _k, v in command.commands.items():
141
160
  cmd = Pmd3Cli.collect_commands(v)
142
161
  if isinstance(cmd, list):
143
- commands.extend([f'{command.name} {c}' for c in cmd])
162
+ commands.extend([f"{command.name} {c}" for c in cmd])
144
163
  else:
145
- commands.append(f'{command.name} {cmd}')
164
+ commands.append(f"{command.name} {cmd}")
146
165
  return commands
147
- return f'{command.name}'
166
+ return f"{command.name}"
148
167
 
149
168
  @staticmethod
150
169
  def search_commands(pattern: str) -> list[str]:
@@ -159,9 +178,9 @@ class Pmd3Cli(click.Group):
159
178
  @staticmethod
160
179
  def load_all_commands() -> list[str]:
161
180
  all_commands = []
162
- for key in CLI_GROUPS.keys():
163
- module_name = f'pymobiledevice3.cli.{CLI_GROUPS[key]}'
164
- mod = __import__(module_name, None, None, ['cli'])
181
+ for key in CLI_GROUPS:
182
+ module_name = f"pymobiledevice3.cli.{CLI_GROUPS[key]}"
183
+ mod = __import__(module_name, None, None, ["cli"])
165
184
  cmd = Pmd3Cli.collect_commands(mod.cli.commands[key])
166
185
  if isinstance(cmd, list):
167
186
  all_commands.extend(cmd)
@@ -171,7 +190,7 @@ class Pmd3Cli(click.Group):
171
190
 
172
191
 
173
192
  @click.command(cls=Pmd3Cli, context_settings=CONTEXT_SETTINGS)
174
- @click.option('--reconnect', is_flag=True, default=False, help='Reconnect to device when disconnected.')
193
+ @click.option("--reconnect", is_flag=True, default=False, help="Reconnect to device when disconnected.")
175
194
  def cli(reconnect: bool) -> None:
176
195
  """
177
196
  \b
@@ -191,81 +210,87 @@ def invoke_cli_with_error_handling() -> bool:
191
210
  try:
192
211
  cli()
193
212
  except NoDeviceConnectedError:
194
- logger.error('Device is not connected')
213
+ logger.error("Device is not connected")
195
214
  return True
196
215
  except ConnectionAbortedError:
197
- logger.error('Device was disconnected')
216
+ logger.error("Device was disconnected")
198
217
  return True
199
218
  except NotPairedError:
200
- logger.error('Device is not paired')
219
+ logger.error("Device is not paired")
201
220
  except UserDeniedPairingError:
202
- logger.error('User refused to trust this computer')
221
+ logger.error("User refused to trust this computer")
203
222
  except PairingDialogResponsePendingError:
204
- logger.error('Waiting for user dialog approval')
223
+ logger.error("Waiting for user dialog approval")
205
224
  except SetProhibitedError:
206
- logger.error('lockdownd denied the access')
225
+ logger.error("lockdownd denied the access")
207
226
  except MissingValueError:
208
- logger.error('No such value')
227
+ logger.error("No such value")
209
228
  except DeviceHasPasscodeSetError:
210
- logger.error('Cannot enable developer-mode when passcode is set')
211
- except DeveloperModeError as e:
212
- logger.error(f'Failed to enable developer-mode. Error: {e}')
229
+ logger.error("Cannot enable developer-mode when passcode is set")
230
+ except DeveloperModeError:
231
+ logger.error("Failed to enable developer-mode.")
213
232
  except ConnectionFailedToUsbmuxdError:
214
- logger.error('Failed to connect to usbmuxd socket. Make sure it\'s running.')
233
+ logger.error("Failed to connect to usbmuxd socket. Make sure it's running.")
215
234
  except ConnectionFailedError:
216
- logger.error('Failed to connect to service port.')
235
+ logger.error("Failed to connect to service port.")
217
236
  return True
218
237
  except MessageNotSupportedError:
219
- logger.error('Message not supported for this iOS version')
238
+ logger.error("Message not supported for this iOS version")
220
239
  traceback.print_exc()
221
240
  except InternalError:
222
- logger.error('Internal Error')
241
+ logger.error("Internal Error")
223
242
  except DeveloperModeIsNotEnabledError:
224
- logger.error('Developer Mode is disabled. You can try to enable it using: '
225
- 'python3 -m pymobiledevice3 amfi enable-developer-mode')
243
+ logger.error(
244
+ "Developer Mode is disabled. You can try to enable it using: "
245
+ "python3 -m pymobiledevice3 amfi enable-developer-mode"
246
+ )
226
247
  except (InvalidServiceError, RSDRequiredError) as e:
227
248
  should_retry_over_tunneld = False
228
249
  if isinstance(e, RSDRequiredError):
229
- logger.warning('Trying again over tunneld since RSD is required for this command')
250
+ logger.warning("Trying again over tunneld since RSD is required for this command")
230
251
  should_retry_over_tunneld = True
231
- elif (e.identifier is not None) and ('developer' in sys.argv) and ('--tunnel' not in sys.argv):
232
- logger.warning('Got an InvalidServiceError. Trying again over tunneld since it is a developer command')
252
+ elif (e.identifier is not None) and ("developer" in sys.argv) and ("--tunnel" not in sys.argv):
253
+ logger.warning("Got an InvalidServiceError. Trying again over tunneld since it is a developer command")
233
254
  should_retry_over_tunneld = True
234
255
  if should_retry_over_tunneld:
235
256
  # use a single space because click will ignore envvars of empty strings
236
- os.environ[TUNNEL_ENV_VAR] = e.identifier or ' '
257
+ os.environ[TUNNEL_ENV_VAR] = e.identifier or " "
237
258
  return main()
238
259
  logger.error(INVALID_SERVICE_MESSAGE)
239
260
  except PasswordRequiredError:
240
- logger.error('Device is password protected. Please unlock and retry')
261
+ logger.error("Device is password protected. Please unlock and retry")
241
262
  except AccessDeniedError:
242
263
  logger.error(get_os_utils().access_denied_error)
243
264
  except BrokenPipeError:
244
265
  traceback.print_exc()
245
266
  except TunneldConnectionError:
246
267
  logger.error(
247
- 'Unable to connect to Tunneld. You can start one using:\n'
248
- 'sudo python3 -m pymobiledevice3 remote tunneld')
268
+ "Unable to connect to Tunneld. You can start one using:\nsudo python3 -m pymobiledevice3 remote tunneld"
269
+ )
249
270
  except DeviceNotFoundError as e:
250
- logger.error(f'Device not found: {e.udid}')
271
+ logger.error(f"Device not found: {e.udid}")
251
272
  except NotEnoughDiskSpaceError:
252
- logger.error('Not enough disk space')
273
+ logger.error("Not enough disk space")
253
274
  except DeprecationError:
254
- logger.error('failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).')
275
+ logger.error("failed to query MobileGestalt, MobileGestalt deprecated (iOS >= 17.4).")
255
276
  except OSNotSupportedError as e:
256
277
  logger.error(
257
- f'Unsupported OS - {e.os_name}. To add support, consider contributing at '
258
- f'https://github.com/doronz88/pymobiledevice3.')
278
+ f"Unsupported OS - {e.os_name}. To add support, consider contributing at "
279
+ f"https://github.com/doronz88/pymobiledevice3."
280
+ )
259
281
  except CloudConfigurationAlreadyPresentError:
260
- logger.error('A cloud configuration is already present on device. You must first erase the device in order '
261
- 'to install new one:\n'
262
- '> pymobiledevice3 profile erase-device')
282
+ logger.error(
283
+ "A cloud configuration is already present on device. You must first erase the device in order "
284
+ "to install new one:\n"
285
+ "> pymobiledevice3 profile erase-device"
286
+ )
263
287
  except FeatureNotSupportedError as e:
264
288
  logger.error(
265
- f'Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at '
266
- f'https://github.com/doronz88/pymobiledevice3.')
267
- except QuicProtocolNotSupportedError as e:
268
- logger.error(str(e))
289
+ f"Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at "
290
+ f"https://github.com/doronz88/pymobiledevice3."
291
+ )
292
+ except QuicProtocolNotSupportedError:
293
+ logger.error("Encountered a QUIC protocol error.")
269
294
 
270
295
  return False
271
296
 
@@ -282,9 +307,9 @@ def main() -> None:
282
307
  lockdown = retry_create_using_usbmux(None)
283
308
  lockdown.close()
284
309
  except KeyboardInterrupt:
285
- print('Aborted.')
310
+ print("Aborted.")
286
311
  break
287
312
 
288
313
 
289
- if __name__ == '__main__':
314
+ if __name__ == "__main__":
290
315
  main()
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '4.27.4'
32
- __version_tuple__ = version_tuple = (4, 27, 4)
31
+ __version__ = version = '5.1.2'
32
+ __version_tuple__ = version_tuple = (5, 1, 2)
33
33
 
34
34
  __commit_id__ = commit_id = None