pymobiledevice3 5.0.1__py3-none-any.whl → 5.0.3__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 +36 -59
  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 +396 -242
  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 +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 +137 -127
  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 +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 +442 -421
  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 +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 +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 +129 -117
  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.1.dist-info → pymobiledevice3-5.0.3.dist-info}/METADATA +1 -1
  138. pymobiledevice3-5.0.3.dist-info/RECORD +173 -0
  139. pymobiledevice3-5.0.1.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.3.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,20 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import json
3
4
  import logging
4
5
  import uuid
6
+ from collections.abc import Coroutine
5
7
  from dataclasses import dataclass, fields
6
8
  from enum import Enum
7
- from typing import Any, Coroutine, Optional, Union
9
+ from typing import Any, Optional, Union
8
10
 
9
11
  import nest_asyncio
10
12
 
11
- from pymobiledevice3.exceptions import LaunchingApplicationError, RemoteAutomationNotEnabledError, \
12
- WebInspectorNotEnabledError
13
+ from pymobiledevice3.exceptions import (
14
+ LaunchingApplicationError,
15
+ RemoteAutomationNotEnabledError,
16
+ WebInspectorNotEnabledError,
17
+ )
13
18
  from pymobiledevice3.lockdown import LockdownClient
14
19
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
15
20
  from pymobiledevice3.service_connection import ServiceConnection
@@ -17,55 +22,55 @@ from pymobiledevice3.services.web_protocol.automation_session import AutomationS
17
22
  from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
18
23
  from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
19
24
 
20
- SAFARI = 'com.apple.mobilesafari'
25
+ SAFARI = "com.apple.mobilesafari"
21
26
 
22
27
 
23
28
  def key_to_pid(key: str) -> int:
24
- return int(key.split(':')[1])
29
+ return int(key.split(":")[1])
25
30
 
26
31
 
27
32
  class WirTypes(Enum):
28
- AUTOMATION = 'WIRTypeAutomation'
29
- ITML = 'WIRTypeITML'
30
- JAVASCRIPT = 'WIRTypeJavaScript'
31
- PAGE = 'WIRTypePage'
32
- SERVICE_WORKER = 'WIRTypeServiceWorker'
33
- WEB = 'WIRTypeWeb'
34
- WEB_PAGE = 'WIRTypeWebPage'
35
- AUTOMATICALLY_PAUSE = 'WIRAutomaticallyPause'
33
+ AUTOMATION = "WIRTypeAutomation"
34
+ ITML = "WIRTypeITML"
35
+ JAVASCRIPT = "WIRTypeJavaScript"
36
+ PAGE = "WIRTypePage"
37
+ SERVICE_WORKER = "WIRTypeServiceWorker"
38
+ WEB = "WIRTypeWeb"
39
+ WEB_PAGE = "WIRTypeWebPage"
40
+ AUTOMATICALLY_PAUSE = "WIRAutomaticallyPause"
36
41
 
37
42
 
38
43
  class AutomationAvailability(Enum):
39
- NOT_AVAILABLE = 'WIRAutomationAvailabilityNotAvailable'
40
- AVAILABLE = 'WIRAutomationAvailabilityAvailable'
41
- UNKNOWN = 'WIRAutomationAvailabilityUnknown'
44
+ NOT_AVAILABLE = "WIRAutomationAvailabilityNotAvailable"
45
+ AVAILABLE = "WIRAutomationAvailabilityAvailable"
46
+ UNKNOWN = "WIRAutomationAvailabilityUnknown"
42
47
 
43
48
 
44
49
  @dataclass
45
50
  class Page:
46
51
  id_: int
47
52
  type_: WirTypes
48
- web_url: str = ''
49
- web_title: str = ''
53
+ web_url: str = ""
54
+ web_title: str = ""
50
55
  automation_is_paired_key: bool = False
51
- automation_name: str = ''
52
- automation_version: str = ''
53
- automation_session_id: str = ''
54
- automation_connection_id: str = ''
56
+ automation_name: str = ""
57
+ automation_version: str = ""
58
+ automation_session_id: str = ""
59
+ automation_connection_id: str = ""
55
60
 
56
61
  @classmethod
57
- def from_page_dictionary(cls, page_dict: dict) -> 'Page':
58
- p = cls(page_dict['WIRPageIdentifierKey'], WirTypes(page_dict['WIRTypeKey']))
62
+ def from_page_dictionary(cls, page_dict: dict) -> "Page":
63
+ p = cls(page_dict["WIRPageIdentifierKey"], WirTypes(page_dict["WIRTypeKey"]))
59
64
  if p.type_ in (WirTypes.WEB, WirTypes.WEB_PAGE):
60
- p.web_title = page_dict['WIRTitleKey']
61
- p.web_url = page_dict['WIRURLKey']
65
+ p.web_title = page_dict["WIRTitleKey"]
66
+ p.web_url = page_dict["WIRURLKey"]
62
67
  if p.type_ == WirTypes.AUTOMATION:
63
- p.automation_is_paired_key = page_dict['WIRAutomationTargetIsPairedKey']
64
- p.automation_name = page_dict['WIRAutomationTargetNameKey']
65
- p.automation_version = page_dict['WIRAutomationTargetVersionKey']
66
- p.automation_session_id = page_dict['WIRSessionIdentifierKey']
67
- if 'WIRConnectionIdentifierKey' in page_dict:
68
- p.automation_connection_id = page_dict['WIRConnectionIdentifierKey']
68
+ p.automation_is_paired_key = page_dict["WIRAutomationTargetIsPairedKey"]
69
+ p.automation_name = page_dict["WIRAutomationTargetNameKey"]
70
+ p.automation_version = page_dict["WIRAutomationTargetVersionKey"]
71
+ p.automation_session_id = page_dict["WIRSessionIdentifierKey"]
72
+ if "WIRConnectionIdentifierKey" in page_dict:
73
+ p.automation_connection_id = page_dict["WIRConnectionIdentifierKey"]
69
74
  return p
70
75
 
71
76
  def update(self, page_dict: dict):
@@ -74,7 +79,7 @@ class Page:
74
79
  setattr(self, field.name, getattr(new_p, field.name))
75
80
 
76
81
  def __str__(self):
77
- return f'id: {self.id_}, title: {self.web_title}, url: {self.web_url}'
82
+ return f"id: {self.id_}, title: {self.web_title}, url: {self.web_url}"
78
83
 
79
84
 
80
85
  @dataclass
@@ -87,20 +92,20 @@ class Application:
87
92
  active: int
88
93
  proxy: bool
89
94
  ready: bool
90
- host: str = ''
95
+ host: str = ""
91
96
 
92
97
  @classmethod
93
- def from_application_dictionary(cls, app_dict) -> 'Application':
98
+ def from_application_dictionary(cls, app_dict) -> "Application":
94
99
  return cls(
95
- app_dict['WIRApplicationIdentifierKey'],
96
- app_dict['WIRApplicationBundleIdentifierKey'],
97
- key_to_pid(app_dict['WIRApplicationIdentifierKey']),
98
- app_dict['WIRApplicationNameKey'],
99
- AutomationAvailability(app_dict['WIRAutomationAvailabilityKey']),
100
- app_dict['WIRIsApplicationActiveKey'],
101
- app_dict['WIRIsApplicationProxyKey'],
102
- app_dict['WIRIsApplicationReadyKey'],
103
- app_dict.get('WIRHostApplicationIdentifierKey', ''),
100
+ app_dict["WIRApplicationIdentifierKey"],
101
+ app_dict["WIRApplicationBundleIdentifierKey"],
102
+ key_to_pid(app_dict["WIRApplicationIdentifierKey"]),
103
+ app_dict["WIRApplicationNameKey"],
104
+ AutomationAvailability(app_dict["WIRAutomationAvailabilityKey"]),
105
+ app_dict["WIRIsApplicationActiveKey"],
106
+ app_dict["WIRIsApplicationProxyKey"],
107
+ app_dict["WIRIsApplicationReadyKey"],
108
+ app_dict.get("WIRHostApplicationIdentifierKey", ""),
104
109
  )
105
110
 
106
111
 
@@ -110,12 +115,12 @@ class ApplicationPage:
110
115
  page: Page
111
116
 
112
117
  def __str__(self) -> str:
113
- return f'<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>'
118
+ return f"<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>"
114
119
 
115
120
 
116
121
  class WebinspectorService:
117
- SERVICE_NAME = 'com.apple.webinspector'
118
- RSD_SERVICE_NAME = 'com.apple.webinspector.shim.remote'
122
+ SERVICE_NAME = "com.apple.webinspector"
123
+ RSD_SERVICE_NAME = "com.apple.webinspector.shim.remote"
119
124
 
120
125
  def __init__(self, lockdown: LockdownServiceProvider, loop=None):
121
126
  if loop is None:
@@ -142,18 +147,18 @@ class WebinspectorService:
142
147
  self.wir_message_results = {}
143
148
  self.wir_events = []
144
149
  self.receive_handlers = {
145
- '_rpc_reportCurrentState:': self._handle_report_current_state,
146
- '_rpc_reportConnectedApplicationList:': self._handle_report_connected_application_list,
147
- '_rpc_reportConnectedDriverList:': self._handle_report_connected_driver_list,
148
- '_rpc_applicationSentListing:': self._handle_application_sent_listing,
149
- '_rpc_applicationUpdated:': self._handle_application_updated,
150
- '_rpc_applicationConnected:': self._handle_application_connected,
151
- '_rpc_applicationSentData:': self._handle_application_sent_data,
152
- '_rpc_applicationDisconnected:': self._handle_application_disconnected,
150
+ "_rpc_reportCurrentState:": self._handle_report_current_state,
151
+ "_rpc_reportConnectedApplicationList:": self._handle_report_connected_application_list,
152
+ "_rpc_reportConnectedDriverList:": self._handle_report_connected_driver_list,
153
+ "_rpc_applicationSentListing:": self._handle_application_sent_listing,
154
+ "_rpc_applicationUpdated:": self._handle_application_updated,
155
+ "_rpc_applicationConnected:": self._handle_application_connected,
156
+ "_rpc_applicationSentData:": self._handle_application_sent_data,
157
+ "_rpc_applicationDisconnected:": self._handle_application_disconnected,
153
158
  }
154
159
  self._recv_task: Optional[asyncio.Task] = None
155
160
 
156
- def connect(self, timeout: Union[float, int] = None):
161
+ def connect(self, timeout: Optional[Union[float, int]] = None):
157
162
  self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
158
163
  self.await_(self._report_identifier())
159
164
  try:
@@ -164,10 +169,8 @@ class WebinspectorService:
164
169
 
165
170
  def close(self):
166
171
  self._recv_task.cancel()
167
- try:
172
+ with contextlib.suppress(asyncio.CancelledError):
168
173
  self.await_(self._recv_task)
169
- except asyncio.CancelledError:
170
- pass
171
174
  self.await_(self.service.aio_close())
172
175
 
173
176
  async def _recv_message(self):
@@ -182,7 +185,7 @@ class WebinspectorService:
182
185
  self._handle_recv(await self._recv_message())
183
186
 
184
187
  def automation_session(self, app: Application) -> AutomationSession:
185
- if self.state == 'WIRAutomationAvailabilityNotAvailable':
188
+ if self.state == "WIRAutomationAvailabilityNotAvailable":
186
189
  raise RemoteAutomationNotEnabledError()
187
190
  session_id = str(uuid.uuid4()).upper()
188
191
  self.await_(self._forward_automation_session_request(session_id, app.id_))
@@ -196,8 +199,10 @@ class WebinspectorService:
196
199
 
197
200
  async def inspector_session(self, app: Application, page: Page) -> InspectorSession:
198
201
  session_id = str(uuid.uuid4()).upper()
199
- return await InspectorSession.create(SessionProtocol(self, session_id, app, page, method_prefix=''),
200
- wait_target=page.type_ != WirTypes.JAVASCRIPT)
202
+ return await InspectorSession.create(
203
+ SessionProtocol(self, session_id, app, page, method_prefix=""),
204
+ wait_target=page.type_ != WirTypes.JAVASCRIPT,
205
+ )
201
206
 
202
207
  def get_open_pages(self) -> dict:
203
208
  apps = {}
@@ -226,8 +231,8 @@ class WebinspectorService:
226
231
  self.get_open_pages()
227
232
  try:
228
233
  return self._await_with_timeout(self._wait_for_application(bundle), timeout)
229
- except TimeoutError:
230
- raise LaunchingApplicationError()
234
+ except TimeoutError as e:
235
+ raise LaunchingApplicationError() from e
231
236
 
232
237
  async def send_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
233
238
  await self._forward_socket_data(session_id, app_id, page_id, data)
@@ -247,29 +252,27 @@ class WebinspectorService:
247
252
  def await_(self, awaitable: Coroutine) -> Any:
248
253
  return self.loop.run_until_complete(awaitable)
249
254
 
250
- def _await_with_timeout(self, coro: Coroutine, timeout: float = None) -> Any:
255
+ def _await_with_timeout(self, coro: Coroutine, timeout: Optional[float] = None) -> Any:
251
256
  # Create a task explicitly so we're definitely inside a Task
252
257
  task = self.loop.create_task(coro)
253
- done, pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
258
+ done, _pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
254
259
  if not done:
255
260
  task.cancel()
256
261
  # Give the task a chance to cancel cleanly
257
- try:
262
+ with contextlib.suppress(asyncio.CancelledError):
258
263
  self.loop.run_until_complete(task)
259
- except asyncio.CancelledError:
260
- pass
261
264
  raise WebInspectorNotEnabledError()
262
265
  return task.result()
263
266
 
264
267
  def _handle_recv(self, plist):
265
- self.receive_handlers[plist['__selector']](plist['__argument'])
268
+ self.receive_handlers[plist["__selector"]](plist["__argument"])
266
269
 
267
270
  def _handle_report_current_state(self, arg):
268
- self.state = arg['WIRAutomationAvailabilityKey']
271
+ self.state = arg["WIRAutomationAvailabilityKey"]
269
272
 
270
273
  def _handle_report_connected_application_list(self, arg):
271
274
  self.connected_application = {}
272
- for key, application in arg['WIRApplicationDictionaryKey'].items():
275
+ for key, application in arg["WIRApplicationDictionaryKey"].items():
273
276
  self.connected_application[key] = Application.from_application_dictionary(application)
274
277
 
275
278
  # Immediately also query the application pages
@@ -279,19 +282,19 @@ class WebinspectorService:
279
282
  pass
280
283
 
281
284
  def _handle_application_sent_listing(self, arg):
282
- if arg['WIRApplicationIdentifierKey'] in self.application_pages:
285
+ if arg["WIRApplicationIdentifierKey"] in self.application_pages:
283
286
  # Update existing application pages
284
- for id_, page in arg['WIRListingKey'].items():
285
- if id_ in self.application_pages[arg['WIRApplicationIdentifierKey']]:
286
- self.application_pages[arg['WIRApplicationIdentifierKey']][id_].update(page)
287
+ for id_, page in arg["WIRListingKey"].items():
288
+ if id_ in self.application_pages[arg["WIRApplicationIdentifierKey"]]:
289
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_].update(page)
287
290
  else:
288
- self.application_pages[arg['WIRApplicationIdentifierKey']][id_] = Page.from_page_dictionary(page)
291
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
289
292
  else:
290
293
  # Add new application pages
291
294
  pages = {}
292
- for id_, page in arg['WIRListingKey'].items():
295
+ for id_, page in arg["WIRListingKey"].items():
293
296
  pages[id_] = Page.from_page_dictionary(page)
294
- self.application_pages[arg['WIRApplicationIdentifierKey']] = pages
297
+ self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
295
298
 
296
299
  def _handle_application_updated(self, arg):
297
300
  app = Application.from_application_dictionary(arg)
@@ -302,72 +305,81 @@ class WebinspectorService:
302
305
  self.connected_application[app.id_] = app
303
306
 
304
307
  def _handle_application_sent_data(self, arg):
305
- response = json.loads(arg['WIRMessageDataKey'])
308
+ response = json.loads(arg["WIRMessageDataKey"])
306
309
 
307
- if 'id' in response:
308
- self.wir_message_results[response['id']] = response
310
+ if "id" in response:
311
+ self.wir_message_results[response["id"]] = response
309
312
  else:
310
313
  self.wir_events.append(response)
311
314
 
312
315
  def _handle_application_disconnected(self, arg):
313
- self.connected_application.pop(arg['WIRApplicationIdentifierKey'], None)
314
- self.application_pages.pop(arg['WIRApplicationIdentifierKey'], None)
316
+ self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
317
+ self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
315
318
 
316
319
  async def _report_identifier(self):
317
- await self._send_message('_rpc_reportIdentifier:')
320
+ await self._send_message("_rpc_reportIdentifier:")
318
321
 
319
322
  async def _forward_get_listing(self, app_id):
320
- self.logger.debug(f'Listing app with id {app_id}')
321
- await self._send_message('_rpc_forwardGetListing:', {'WIRApplicationIdentifierKey': app_id})
323
+ self.logger.debug(f"Listing app with id {app_id}")
324
+ await self._send_message("_rpc_forwardGetListing:", {"WIRApplicationIdentifierKey": app_id})
322
325
 
323
326
  async def _request_application_launch(self, bundle: str):
324
- await self._send_message('_rpc_requestApplicationLaunch:', {'WIRApplicationBundleIdentifierKey': bundle})
327
+ await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
325
328
 
326
329
  async def _get_connected_applications(self) -> None:
327
- await self._send_message('_rpc_getConnectedApplications:', {})
330
+ await self._send_message("_rpc_getConnectedApplications:", {})
328
331
 
329
332
  async def _forward_automation_session_request(self, session_id: str, app_id: str):
330
- await self._send_message('_rpc_forwardAutomationSessionRequest:', {
331
- 'WIRApplicationIdentifierKey': app_id,
332
- 'WIRSessionCapabilitiesKey': {
333
- 'org.webkit.webdriver.webrtc.allow-insecure-media-capture': True,
334
- 'org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering': False,
333
+ await self._send_message(
334
+ "_rpc_forwardAutomationSessionRequest:",
335
+ {
336
+ "WIRApplicationIdentifierKey": app_id,
337
+ "WIRSessionCapabilitiesKey": {
338
+ "org.webkit.webdriver.webrtc.allow-insecure-media-capture": True,
339
+ "org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering": False,
340
+ },
341
+ "WIRSessionIdentifierKey": session_id,
335
342
  },
336
- 'WIRSessionIdentifierKey': session_id
337
- })
343
+ )
338
344
 
339
345
  async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
340
346
  message = {
341
- 'WIRApplicationIdentifierKey': app_id,
342
- 'WIRPageIdentifierKey': page_id,
343
- 'WIRSenderKey': session_id,
344
- 'WIRMessageDataTypeChunkSupportedKey': 0,
347
+ "WIRApplicationIdentifierKey": app_id,
348
+ "WIRPageIdentifierKey": page_id,
349
+ "WIRSenderKey": session_id,
350
+ "WIRMessageDataTypeChunkSupportedKey": 0,
345
351
  }
346
352
  if not pause:
347
- message['WIRAutomaticallyPause'] = False
348
- await self._send_message('_rpc_forwardSocketSetup:', message)
353
+ message["WIRAutomaticallyPause"] = False
354
+ await self._send_message("_rpc_forwardSocketSetup:", message)
349
355
 
350
356
  async def _forward_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
351
- await self._send_message('_rpc_forwardSocketData:', {
352
- 'WIRApplicationIdentifierKey': app_id,
353
- 'WIRPageIdentifierKey': page_id,
354
- 'WIRSessionIdentifierKey': session_id,
355
- 'WIRSenderKey': session_id,
356
- 'WIRSocketDataKey': json.dumps(data).encode(),
357
- })
357
+ await self._send_message(
358
+ "_rpc_forwardSocketData:",
359
+ {
360
+ "WIRApplicationIdentifierKey": app_id,
361
+ "WIRPageIdentifierKey": page_id,
362
+ "WIRSessionIdentifierKey": session_id,
363
+ "WIRSenderKey": session_id,
364
+ "WIRSocketDataKey": json.dumps(data).encode(),
365
+ },
366
+ )
358
367
 
359
368
  async def _forward_indicate_web_view(self, app_id: str, page_id: int, enable: bool):
360
- await self._send_message('_rpc_forwardIndicateWebView'), {
361
- 'WIRApplicationIdentifierKey': app_id,
362
- 'WIRPageIdentifierKey': page_id,
363
- 'WIRIndicateEnabledKey': enable,
364
- }
369
+ (
370
+ await self._send_message("_rpc_forwardIndicateWebView"),
371
+ {
372
+ "WIRApplicationIdentifierKey": app_id,
373
+ "WIRPageIdentifierKey": page_id,
374
+ "WIRIndicateEnabledKey": enable,
375
+ },
376
+ )
365
377
 
366
378
  async def _send_message(self, selector: str, args=None):
367
379
  if args is None:
368
380
  args = {}
369
- args['WIRConnectionIdentifierKey'] = self.connection_id
370
- await self.service.aio_send_plist({'__selector': selector, '__argument': args})
381
+ args["WIRConnectionIdentifierKey"] = self.connection_id
382
+ await self.service.aio_send_plist({"__selector": selector, "__argument": args})
371
383
 
372
384
  def _page_by_automation_session(self, session_id: str) -> Page:
373
385
  for app_id in self.application_pages:
@@ -383,7 +395,7 @@ class WebinspectorService:
383
395
  return page
384
396
  await asyncio.sleep(0)
385
397
 
386
- async def _wait_for_application(self, bundle: str = '', app_id: str = '') -> Application:
398
+ async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
387
399
  while True:
388
400
  for app in self.connected_application.values():
389
401
  if bundle and app.bundle == bundle:
@@ -19,7 +19,7 @@ class TcpForwarderBase:
19
19
  MAX_FORWARDED_CONNECTIONS = 200
20
20
  TIMEOUT = 1
21
21
 
22
- def __init__(self, src_port: int, listening_event: threading.Event = None):
22
+ def __init__(self, src_port: int, listening_event: Optional[threading.Event] = None):
23
23
  """
24
24
  Initialize a new tcp forwarder
25
25
 
@@ -38,8 +38,8 @@ class TcpForwarderBase:
38
38
  # socket to its remote socket and vice versa
39
39
  self.connections = {}
40
40
 
41
- def start(self, address='0.0.0.0'):
42
- """ forward each connection from given local machine port to remote device port """
41
+ def start(self, address="0.0.0.0"):
42
+ """forward each connection from given local machine port to remote device port"""
43
43
  # create local tcp server socket
44
44
  self.server_socket = socket.socket()
45
45
  self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -55,7 +55,7 @@ class TcpForwarderBase:
55
55
  while self.inputs:
56
56
  # will only perform the socket select on the inputs. the outputs will handled
57
57
  # as synchronous blocking
58
- readable, writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
58
+ readable, _writable, exceptional = select.select(self.inputs, [], self.inputs, self.TIMEOUT)
59
59
  if self.stopped.is_set():
60
60
  self.logger.debug("Closing since stopped is set")
61
61
  break
@@ -85,7 +85,7 @@ class TcpForwarderBase:
85
85
  current_sock.close()
86
86
 
87
87
  def _handle_close_or_error(self, from_sock):
88
- """ if an error occurred its time to close the two sockets """
88
+ """if an error occurred its time to close the two sockets"""
89
89
  other_sock = self.connections[from_sock]
90
90
 
91
91
  other_sock.close()
@@ -93,7 +93,7 @@ class TcpForwarderBase:
93
93
  self.inputs.remove(other_sock)
94
94
  self.inputs.remove(from_sock)
95
95
 
96
- self.logger.info(f'connection {other_sock} was closed')
96
+ self.logger.info(f"connection {other_sock} was closed")
97
97
 
98
98
  def _handle_data(self, from_sock, closed_sockets):
99
99
  self.logger.debug(f"Handling data from {from_sock}")
@@ -104,8 +104,8 @@ class TcpForwarderBase:
104
104
  except BlockingIOError:
105
105
  self.logger.warning(f"Non-blocking read failed on {from_sock}, retrying later.")
106
106
  return
107
- except OSError as e:
108
- self.logger.error(f"Error reading from socket {from_sock}: {e}")
107
+ except OSError:
108
+ self.logger.exception(f"Error reading from socket {from_sock}")
109
109
  self._handle_close_or_error(from_sock)
110
110
  closed_sockets.add(from_sock)
111
111
  return
@@ -125,10 +125,10 @@ class TcpForwarderBase:
125
125
  self.logger.warning(f"Socket buffer full for {other_sock}, retrying in 100ms.")
126
126
  time.sleep(0.1) # Introduce a small delay
127
127
  except BrokenPipeError:
128
- self.logger.error(f"Broken pipe error on {other_sock}.")
128
+ self.logger.exception(f"Broken pipe error on {other_sock}.")
129
129
  raise
130
- except OSError as e:
131
- self.logger.error(f"Unhandled error while forwarding data: {e}")
130
+ except OSError:
131
+ self.logger.exception("Unhandled error while forwarding data")
132
132
  self._handle_close_or_error(from_sock)
133
133
  closed_sockets.add(from_sock)
134
134
  closed_sockets.add(other_sock)
@@ -138,14 +138,14 @@ class TcpForwarderBase:
138
138
  pass
139
139
 
140
140
  def _handle_server_connection(self):
141
- """ accept the connection from local machine and attempt to connect at remote """
142
- local_connection, client_address = self.server_socket.accept()
141
+ """accept the connection from local machine and attempt to connect at remote"""
142
+ local_connection, _client_address = self.server_socket.accept()
143
143
  local_connection.setblocking(False)
144
144
 
145
145
  try:
146
146
  remote_connection = self._establish_remote_connection()
147
147
  except ConnectionFailedError:
148
- self.logger.error(f'failed to connect to port: {self.dst_port}')
148
+ self.logger.exception(f"failed to connect to port: {self.dst_port}")
149
149
  local_connection.close()
150
150
  return
151
151
 
@@ -159,10 +159,10 @@ class TcpForwarderBase:
159
159
  self.connections[remote_connection] = local_connection
160
160
  self.connections[local_connection] = remote_connection
161
161
 
162
- self.logger.info('connection established from local to remote')
162
+ self.logger.info("connection established from local to remote")
163
163
 
164
164
  def stop(self):
165
- """ stop forwarding """
165
+ """stop forwarding"""
166
166
  self.stopped.set()
167
167
 
168
168
 
@@ -171,8 +171,15 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
171
171
  Allows forwarding local tcp connection into the device via a given lockdown connection
172
172
  """
173
173
 
174
- def __init__(self, serial: str, dst_port: int, src_port: int, listening_event: threading.Event = None,
175
- usbmux_connection_type: str = None, usbmux_address: Optional[str] = None):
174
+ def __init__(
175
+ self,
176
+ serial: str,
177
+ dst_port: int,
178
+ src_port: int,
179
+ listening_event: Optional[threading.Event] = None,
180
+ usbmux_connection_type: Optional[str] = None,
181
+ usbmux_address: Optional[str] = None,
182
+ ):
176
183
  """
177
184
  Initialize a new tcp forwarder
178
185
 
@@ -191,8 +198,9 @@ class UsbmuxTcpForwarder(TcpForwarderBase):
191
198
 
192
199
  def _establish_remote_connection(self) -> socket.socket:
193
200
  # connect directly using usbmuxd
194
- mux_device = usbmux.select_device(self.serial, connection_type=self.usbmux_connection_type,
195
- usbmux_address=self.usbmux_address)
201
+ mux_device = usbmux.select_device(
202
+ self.serial, connection_type=self.usbmux_connection_type, usbmux_address=self.usbmux_address
203
+ )
196
204
  self.logger.debug("Selected device: %r", mux_device)
197
205
  if mux_device is None:
198
206
  raise ConnectionFailedError()
@@ -204,8 +212,13 @@ class LockdownTcpForwarder(TcpForwarderBase):
204
212
  Allows forwarding local tcp connection into the device via a given lockdown connection
205
213
  """
206
214
 
207
- def __init__(self, service_provider: LockdownServiceProvider, src_port: int, service_name: str,
208
- listening_event: threading.Event = None):
215
+ def __init__(
216
+ self,
217
+ service_provider: LockdownServiceProvider,
218
+ src_port: int,
219
+ service_name: str,
220
+ listening_event: Optional[threading.Event] = None,
221
+ ):
209
222
  """
210
223
  Initialize a new tcp forwarder
211
224
 
@@ -6,22 +6,25 @@ from pymobiledevice3.exceptions import TunneldConnectionError
6
6
  from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
7
7
  from pymobiledevice3.utils import get_asyncio_loop
8
8
 
9
- TUNNELD_DEFAULT_ADDRESS = ('127.0.0.1', 49151)
9
+ TUNNELD_DEFAULT_ADDRESS = ("127.0.0.1", 49151)
10
10
 
11
11
 
12
- async def async_get_tunneld_devices(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
13
- -> list[RemoteServiceDiscoveryService]:
12
+ async def async_get_tunneld_devices(
13
+ tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
14
+ ) -> list[RemoteServiceDiscoveryService]:
14
15
  tunnels = _list_tunnels(tunneld_address)
15
16
  return await _create_rsds_from_tunnels(tunnels)
16
17
 
17
18
 
18
- def get_tunneld_devices(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
19
- -> list[RemoteServiceDiscoveryService]:
19
+ def get_tunneld_devices(
20
+ tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS,
21
+ ) -> list[RemoteServiceDiscoveryService]:
20
22
  return get_asyncio_loop().run_until_complete(async_get_tunneld_devices(tunneld_address))
21
23
 
22
24
 
23
- async def async_get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
24
- -> Optional[RemoteServiceDiscoveryService]:
25
+ async def async_get_tunneld_device_by_udid(
26
+ udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
27
+ ) -> Optional[RemoteServiceDiscoveryService]:
25
28
  tunnels = _list_tunnels(tunneld_address)
26
29
  if udid not in tunnels:
27
30
  return None
@@ -29,27 +32,29 @@ async def async_get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str
29
32
  return rsds[0]
30
33
 
31
34
 
32
- def get_tunneld_device_by_udid(udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) \
33
- -> Optional[RemoteServiceDiscoveryService]:
35
+ def get_tunneld_device_by_udid(
36
+ udid: str, tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS
37
+ ) -> Optional[RemoteServiceDiscoveryService]:
34
38
  return get_asyncio_loop().run_until_complete(async_get_tunneld_device_by_udid(udid, tunneld_address))
35
39
 
36
40
 
37
41
  def _list_tunnels(tunneld_address: tuple[str, int] = TUNNELD_DEFAULT_ADDRESS) -> dict[str, list[dict]]:
38
42
  try:
39
43
  # Get the list of tunnels from the specified address
40
- resp = requests.get(f'http://{tunneld_address[0]}:{tunneld_address[1]}')
44
+ resp = requests.get(f"http://{tunneld_address[0]}:{tunneld_address[1]}")
41
45
  tunnels = resp.json()
42
- except requests.exceptions.ConnectionError:
43
- raise TunneldConnectionError()
46
+ except requests.exceptions.ConnectionError as e:
47
+ raise TunneldConnectionError() from e
44
48
  return tunnels
45
49
 
46
50
 
47
51
  async def _create_rsds_from_tunnels(tunnels: dict[str, list[dict]]) -> list[RemoteServiceDiscoveryService]:
48
52
  rsds = []
49
- for udid, details in tunnels.items():
53
+ for _udid, details in tunnels.items():
50
54
  for tunnel_details in details:
51
- rsd = RemoteServiceDiscoveryService((tunnel_details['tunnel-address'], tunnel_details['tunnel-port']),
52
- name=tunnel_details['interface'])
55
+ rsd = RemoteServiceDiscoveryService(
56
+ (tunnel_details["tunnel-address"], tunnel_details["tunnel-port"]), name=tunnel_details["interface"]
57
+ )
53
58
  try:
54
59
  await rsd.connect()
55
60
  rsds.append(rsd)