pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.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.

Potentially problematic release.


This version of pymobiledevice3 might be problematic. Click here for more details.

Files changed (143) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. pymobiledevice3/__main__.py +128 -102
  4. pymobiledevice3/_version.py +2 -2
  5. pymobiledevice3/bonjour.py +26 -49
  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 +25 -18
  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 +602 -520
  18. pymobiledevice3/cli/diagnostics.py +38 -33
  19. pymobiledevice3/cli/lockdown.py +79 -74
  20. pymobiledevice3/cli/mounter.py +85 -68
  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 +186 -110
  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 +157 -79
  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 +394 -241
  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 +62 -41
  52. pymobiledevice3/remote/tunnel_service.py +371 -293
  53. pymobiledevice3/remote/utils.py +12 -11
  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 +35 -54
  65. pymobiledevice3/restore/recovery.py +125 -135
  66. pymobiledevice3/restore/restore.py +524 -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 +136 -126
  72. pymobiledevice3/services/afc.py +350 -291
  73. pymobiledevice3/services/amfi.py +21 -18
  74. pymobiledevice3/services/companion.py +23 -19
  75. pymobiledevice3/services/crash_reports.py +60 -46
  76. pymobiledevice3/services/debugserver_applist.py +3 -3
  77. pymobiledevice3/services/device_arbitration.py +8 -8
  78. pymobiledevice3/services/device_link.py +55 -47
  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 +40 -50
  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 +330 -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 +69 -51
  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 +180 -176
  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 +2 -2
  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 +127 -116
  132. pymobiledevice3/tcp_forwarder.py +35 -22
  133. pymobiledevice3/tunneld/api.py +20 -15
  134. pymobiledevice3/tunneld/server.py +212 -133
  135. pymobiledevice3/usbmux.py +183 -138
  136. pymobiledevice3/utils.py +14 -11
  137. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
  138. pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
  139. pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
@@ -8,11 +8,12 @@ from pymobiledevice3.lockdown import LockdownClient
8
8
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
9
9
  from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
10
10
 
11
- source_option = click.option('--source', default='', help='The UDID of the source device.')
12
- password_option = click.option('-p', '--password', default='', help='Backup password.')
13
- backup_directory_arg = click.argument('backup-directory', type=click.Path(exists=True, file_okay=False))
14
- backup_directory_option = click.option('-b', '--backup-directory', type=click.Path(exists=True, file_okay=False),
15
- default='.')
11
+ source_option = click.option("--source", default="", help="The UDID of the source device.")
12
+ password_option = click.option("-p", "--password", default="", help="Backup password.")
13
+ backup_directory_arg = click.argument("backup-directory", type=click.Path(exists=True, file_okay=False))
14
+ backup_directory_option = click.option(
15
+ "-b", "--backup-directory", type=click.Path(exists=True, file_okay=False), default="."
16
+ )
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
@@ -24,14 +25,17 @@ def cli() -> None:
24
25
 
25
26
  @cli.group()
26
27
  def backup2() -> None:
27
- """ Backup/Restore options """
28
+ """Backup/Restore options"""
28
29
  pass
29
30
 
30
31
 
31
32
  @backup2.command(cls=Command)
32
- @click.argument('backup-directory', type=click.Path(file_okay=False))
33
- @click.option('--full', is_flag=True, help=('Whether to do a full backup.'
34
- ' If full is True, any previous backup attempts will be discarded.'))
33
+ @click.argument("backup-directory", type=click.Path(file_okay=False))
34
+ @click.option(
35
+ "--full",
36
+ is_flag=True,
37
+ help=("Whether to do a full backup. If full is True, any previous backup attempts will be discarded."),
38
+ )
35
39
  def backup(service_provider: LockdownServiceProvider, backup_directory: str, full: bool) -> None:
36
40
  """
37
41
  Backup device.
@@ -40,6 +44,7 @@ def backup(service_provider: LockdownServiceProvider, backup_directory: str, ful
40
44
  """
41
45
  backup_client = Mobilebackup2Service(service_provider)
42
46
  with tqdm(total=100, dynamic_ncols=True) as pbar:
47
+
43
48
  def update_bar(percentage):
44
49
  pbar.n = percentage
45
50
  pbar.refresh()
@@ -49,16 +54,26 @@ def backup(service_provider: LockdownServiceProvider, backup_directory: str, ful
49
54
 
50
55
  @backup2.command(cls=Command)
51
56
  @backup_directory_arg
52
- @click.option('--system/--no-system', default=False, help='Restore system files.')
53
- @click.option('--reboot/--no-reboot', default=True, help='Reboot the device when done.')
54
- @click.option('--copy/--no-copy', default=False, help='Create a copy of backup folder before restoring.')
55
- @click.option('--settings/--no-settings', default=True, help='Restore device settings.')
56
- @click.option('--remove/--no-remove', default=False, help='Remove items which aren\'t being restored.')
57
- @click.option('--skip-apps', is_flag=True, help='Do not trigger re-installation of apps after restore')
57
+ @click.option("--system/--no-system", default=False, help="Restore system files.")
58
+ @click.option("--reboot/--no-reboot", default=True, help="Reboot the device when done.")
59
+ @click.option("--copy/--no-copy", default=False, help="Create a copy of backup folder before restoring.")
60
+ @click.option("--settings/--no-settings", default=True, help="Restore device settings.")
61
+ @click.option("--remove/--no-remove", default=False, help="Remove items which aren't being restored.")
62
+ @click.option("--skip-apps", is_flag=True, help="Do not trigger re-installation of apps after restore")
58
63
  @password_option
59
64
  @source_option
60
- def restore(service_provider: LockdownServiceProvider, backup_directory: str, system: bool, reboot: bool, copy: bool,
61
- settings: bool, remove: bool, skip_apps: bool, password: str, source: str) -> None:
65
+ def restore(
66
+ service_provider: LockdownServiceProvider,
67
+ backup_directory: str,
68
+ system: bool,
69
+ reboot: bool,
70
+ copy: bool,
71
+ settings: bool,
72
+ remove: bool,
73
+ skip_apps: bool,
74
+ password: str,
75
+ source: str,
76
+ ) -> None:
62
77
  """
63
78
  Restore a backup to a device.
64
79
 
@@ -66,13 +81,23 @@ def restore(service_provider: LockdownServiceProvider, backup_directory: str, sy
66
81
  """
67
82
  backup_client = Mobilebackup2Service(service_provider)
68
83
  with tqdm(total=100, dynamic_ncols=True) as pbar:
84
+
69
85
  def update_bar(percentage):
70
86
  pbar.n = percentage
71
87
  pbar.refresh()
72
88
 
73
- backup_client.restore(backup_directory=backup_directory, progress_callback=update_bar, system=system,
74
- reboot=reboot, copy=copy, settings=settings, remove=remove, password=password,
75
- source=source, skip_apps=skip_apps)
89
+ backup_client.restore(
90
+ backup_directory=backup_directory,
91
+ progress_callback=update_bar,
92
+ system=system,
93
+ reboot=reboot,
94
+ copy=copy,
95
+ settings=settings,
96
+ remove=remove,
97
+ password=password,
98
+ source=source,
99
+ skip_apps=skip_apps,
100
+ )
76
101
 
77
102
 
78
103
  @backup2.command(cls=Command)
@@ -86,7 +111,7 @@ def info(service_provider: LockdownClient, backup_directory, source):
86
111
  print(backup_client.info(backup_directory=backup_directory, source=source))
87
112
 
88
113
 
89
- @backup2.command('list', cls=Command)
114
+ @backup2.command("list", cls=Command)
90
115
  @backup_directory_arg
91
116
  @source_option
92
117
  def list_(service_provider: LockdownClient, backup_directory, source):
@@ -110,8 +135,8 @@ def unback(service_provider: LockdownClient, backup_directory, password, source)
110
135
 
111
136
 
112
137
  @backup2.command(cls=Command)
113
- @click.argument('domain-name')
114
- @click.argument('relative-path')
138
+ @click.argument("domain-name")
139
+ @click.argument("relative-path")
115
140
  @backup_directory_arg
116
141
  @password_option
117
142
  @source_option
@@ -123,13 +148,14 @@ def extract(service_provider: LockdownClient, domain_name, relative_path, backup
123
148
  will be extracted to the BACKUP_DIRECTORY.
124
149
  """
125
150
  backup_client = Mobilebackup2Service(service_provider)
126
- backup_client.extract(domain_name, relative_path, backup_directory=backup_directory, password=password,
127
- source=source)
151
+ backup_client.extract(
152
+ domain_name, relative_path, backup_directory=backup_directory, password=password, source=source
153
+ )
128
154
 
129
155
 
130
156
  @backup2.command(cls=Command)
131
- @click.argument('mode', type=click.Choice(['on', 'off'], case_sensitive=False))
132
- @click.argument('password')
157
+ @click.argument("mode", type=click.Choice(["on", "off"], case_sensitive=False))
158
+ @click.argument("password")
133
159
  @backup_directory_option
134
160
  def encryption(service_provider: LockdownClient, backup_directory, mode, password):
135
161
  """
@@ -139,9 +165,9 @@ def encryption(service_provider: LockdownClient, backup_directory, mode, passwor
139
165
  When off, PASSWORD is the current backup password.
140
166
  """
141
167
  backup_client = Mobilebackup2Service(service_provider)
142
- should_encrypt = mode.lower() == 'on'
168
+ should_encrypt = mode.lower() == "on"
143
169
  if should_encrypt == backup_client.will_encrypt:
144
- logger.error('Encryption already ' + ('on!' if should_encrypt else 'off!'))
170
+ logger.error("Encryption already " + ("on!" if should_encrypt else "off!"))
145
171
  return
146
172
  if should_encrypt:
147
173
  backup_client.change_password(backup_directory, new=password)
@@ -150,8 +176,8 @@ def encryption(service_provider: LockdownClient, backup_directory, mode, passwor
150
176
 
151
177
 
152
178
  @backup2.command(cls=Command)
153
- @click.argument('old-password')
154
- @click.argument('new-password')
179
+ @click.argument("old-password")
180
+ @click.argument("new-password")
155
181
  @backup_directory_option
156
182
  def change_password(service_provider: LockdownClient, old_password, new_password, backup_directory):
157
183
  """
@@ -159,7 +185,7 @@ def change_password(service_provider: LockdownClient, old_password, new_password
159
185
  """
160
186
  backup_client = Mobilebackup2Service(service_provider)
161
187
  if not backup_client.will_encrypt:
162
- logger.error('Encryption is not turned on!')
188
+ logger.error("Encryption is not turned on!")
163
189
  return
164
190
  backup_client.change_password(backup_directory, old=old_password, new=new_password)
165
191
 
@@ -14,9 +14,9 @@ def cli() -> None:
14
14
  pass
15
15
 
16
16
 
17
- @cli.group('bonjour')
17
+ @cli.group("bonjour")
18
18
  def bonjour_cli() -> None:
19
- """ Browse devices over bonjour """
19
+ """Browse devices over bonjour"""
20
20
  pass
21
21
 
22
22
 
@@ -24,17 +24,20 @@ async def cli_mobdev2_task(timeout: float, pair_records: Optional[str]) -> None:
24
24
  output = []
25
25
  async for ip, lockdown in get_mobdev2_lockdowns(timeout=timeout, pair_records=pair_records):
26
26
  short_info = lockdown.short_info
27
- short_info['ip'] = ip
27
+ short_info["ip"] = ip
28
28
  output.append(short_info)
29
29
  print_json(output)
30
30
 
31
31
 
32
- @bonjour_cli.command('mobdev2', cls=BaseCommand)
33
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.INT)
34
- @click.option('--pair-records', type=click.Path(dir_okay=True, file_okay=False, exists=True),
35
- help='pair records to attempt validation with')
32
+ @bonjour_cli.command("mobdev2", cls=BaseCommand)
33
+ @click.option("--timeout", default=DEFAULT_BONJOUR_TIMEOUT, type=click.INT)
34
+ @click.option(
35
+ "--pair-records",
36
+ type=click.Path(dir_okay=True, file_okay=False, exists=True),
37
+ help="pair records to attempt validation with",
38
+ )
36
39
  def cli_mobdev2(timeout: float, pair_records: Optional[str]) -> None:
37
- """ browse for mobdev2 devices over bonjour """
40
+ """browse for mobdev2 devices over bonjour"""
38
41
  asyncio.run(cli_mobdev2_task(timeout, pair_records))
39
42
 
40
43
 
@@ -42,14 +45,14 @@ async def cli_remotepairing_task(timeout: float) -> None:
42
45
  output = []
43
46
  for answer in await browse_remotepairing(timeout=timeout):
44
47
  for address in answer.addresses:
45
- output.append({'hostname': address.full_ip, 'port': answer.port})
48
+ output.append({"hostname": address.full_ip, "port": answer.port})
46
49
  print_json(output)
47
50
 
48
51
 
49
- @bonjour_cli.command('remotepairing', cls=BaseCommand)
50
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
52
+ @bonjour_cli.command("remotepairing", cls=BaseCommand)
53
+ @click.option("--timeout", default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
51
54
  def cli_remotepairing(timeout: float) -> None:
52
- """ browse for remotepairing devices over bonjour (without attempting pair verification) """
55
+ """browse for remotepairing devices over bonjour (without attempting pair verification)"""
53
56
  asyncio.run(cli_remotepairing_task(timeout=timeout))
54
57
 
55
58
 
@@ -57,14 +60,18 @@ async def cli_remotepairing_manual_pairing_task(timeout: float) -> None:
57
60
  output = []
58
61
  for answer in await browse_remotepairing_manual_pairing(timeout=timeout):
59
62
  for address in answer.addresses:
60
- output.append({'hostname': address.full_ip, 'port': answer.port, 'name': answer.properties[b'name'].decode()})
63
+ output.append({
64
+ "hostname": address.full_ip,
65
+ "port": answer.port,
66
+ "name": answer.properties[b"name"].decode(),
67
+ })
61
68
  print_json(output)
62
69
 
63
70
 
64
- @bonjour_cli.command('remotepairing-manual-pairing', cls=BaseCommand)
65
- @click.option('--timeout', default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
71
+ @bonjour_cli.command("remotepairing-manual-pairing", cls=BaseCommand)
72
+ @click.option("--timeout", default=DEFAULT_BONJOUR_TIMEOUT, type=click.FLOAT)
66
73
  def cli_remotepairing_manual_pairing(timeout: float) -> None:
67
- """ browse for remotepairing-manual-pairing devices over bonjour """
74
+ """browse for remotepairing-manual-pairing devices over bonjour"""
68
75
  asyncio.run(cli_remotepairing_manual_pairing_task(timeout=timeout))
69
76
 
70
77
 
@@ -72,7 +79,7 @@ async def cli_browse_rsd() -> None:
72
79
  print_json(await browse_rsd())
73
80
 
74
81
 
75
- @bonjour_cli.command('rsd', cls=BaseCommand)
82
+ @bonjour_cli.command("rsd", cls=BaseCommand)
76
83
  def cli_rsd() -> None:
77
- """ browse RemoteXPC devices using bonjour """
84
+ """browse RemoteXPC devices using bonjour"""
78
85
  asyncio.run(cli_browse_rsd(), debug=True)
@@ -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>")