pymobiledevice3 5.0.1__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 +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 +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.1.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.1.dist-info/RECORD +0 -173
  140. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
  141. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
  142. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
  143. {pymobiledevice3-5.0.1.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import contextlib
2
3
  import json
3
4
  import logging
4
5
  import uuid
@@ -8,8 +9,11 @@ from typing import Any, Coroutine, Optional, Union
8
9
 
9
10
  import nest_asyncio
10
11
 
11
- from pymobiledevice3.exceptions import LaunchingApplicationError, RemoteAutomationNotEnabledError, \
12
- WebInspectorNotEnabledError
12
+ from pymobiledevice3.exceptions import (
13
+ LaunchingApplicationError,
14
+ RemoteAutomationNotEnabledError,
15
+ WebInspectorNotEnabledError,
16
+ )
13
17
  from pymobiledevice3.lockdown import LockdownClient
14
18
  from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
15
19
  from pymobiledevice3.service_connection import ServiceConnection
@@ -17,55 +21,55 @@ from pymobiledevice3.services.web_protocol.automation_session import AutomationS
17
21
  from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
18
22
  from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
19
23
 
20
- SAFARI = 'com.apple.mobilesafari'
24
+ SAFARI = "com.apple.mobilesafari"
21
25
 
22
26
 
23
27
  def key_to_pid(key: str) -> int:
24
- return int(key.split(':')[1])
28
+ return int(key.split(":")[1])
25
29
 
26
30
 
27
31
  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'
32
+ AUTOMATION = "WIRTypeAutomation"
33
+ ITML = "WIRTypeITML"
34
+ JAVASCRIPT = "WIRTypeJavaScript"
35
+ PAGE = "WIRTypePage"
36
+ SERVICE_WORKER = "WIRTypeServiceWorker"
37
+ WEB = "WIRTypeWeb"
38
+ WEB_PAGE = "WIRTypeWebPage"
39
+ AUTOMATICALLY_PAUSE = "WIRAutomaticallyPause"
36
40
 
37
41
 
38
42
  class AutomationAvailability(Enum):
39
- NOT_AVAILABLE = 'WIRAutomationAvailabilityNotAvailable'
40
- AVAILABLE = 'WIRAutomationAvailabilityAvailable'
41
- UNKNOWN = 'WIRAutomationAvailabilityUnknown'
43
+ NOT_AVAILABLE = "WIRAutomationAvailabilityNotAvailable"
44
+ AVAILABLE = "WIRAutomationAvailabilityAvailable"
45
+ UNKNOWN = "WIRAutomationAvailabilityUnknown"
42
46
 
43
47
 
44
48
  @dataclass
45
49
  class Page:
46
50
  id_: int
47
51
  type_: WirTypes
48
- web_url: str = ''
49
- web_title: str = ''
52
+ web_url: str = ""
53
+ web_title: str = ""
50
54
  automation_is_paired_key: bool = False
51
- automation_name: str = ''
52
- automation_version: str = ''
53
- automation_session_id: str = ''
54
- automation_connection_id: str = ''
55
+ automation_name: str = ""
56
+ automation_version: str = ""
57
+ automation_session_id: str = ""
58
+ automation_connection_id: str = ""
55
59
 
56
60
  @classmethod
57
- def from_page_dictionary(cls, page_dict: dict) -> 'Page':
58
- p = cls(page_dict['WIRPageIdentifierKey'], WirTypes(page_dict['WIRTypeKey']))
61
+ def from_page_dictionary(cls, page_dict: dict) -> "Page":
62
+ p = cls(page_dict["WIRPageIdentifierKey"], WirTypes(page_dict["WIRTypeKey"]))
59
63
  if p.type_ in (WirTypes.WEB, WirTypes.WEB_PAGE):
60
- p.web_title = page_dict['WIRTitleKey']
61
- p.web_url = page_dict['WIRURLKey']
64
+ p.web_title = page_dict["WIRTitleKey"]
65
+ p.web_url = page_dict["WIRURLKey"]
62
66
  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']
67
+ p.automation_is_paired_key = page_dict["WIRAutomationTargetIsPairedKey"]
68
+ p.automation_name = page_dict["WIRAutomationTargetNameKey"]
69
+ p.automation_version = page_dict["WIRAutomationTargetVersionKey"]
70
+ p.automation_session_id = page_dict["WIRSessionIdentifierKey"]
71
+ if "WIRConnectionIdentifierKey" in page_dict:
72
+ p.automation_connection_id = page_dict["WIRConnectionIdentifierKey"]
69
73
  return p
70
74
 
71
75
  def update(self, page_dict: dict):
@@ -74,7 +78,7 @@ class Page:
74
78
  setattr(self, field.name, getattr(new_p, field.name))
75
79
 
76
80
  def __str__(self):
77
- return f'id: {self.id_}, title: {self.web_title}, url: {self.web_url}'
81
+ return f"id: {self.id_}, title: {self.web_title}, url: {self.web_url}"
78
82
 
79
83
 
80
84
  @dataclass
@@ -87,20 +91,20 @@ class Application:
87
91
  active: int
88
92
  proxy: bool
89
93
  ready: bool
90
- host: str = ''
94
+ host: str = ""
91
95
 
92
96
  @classmethod
93
- def from_application_dictionary(cls, app_dict) -> 'Application':
97
+ def from_application_dictionary(cls, app_dict) -> "Application":
94
98
  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', ''),
99
+ app_dict["WIRApplicationIdentifierKey"],
100
+ app_dict["WIRApplicationBundleIdentifierKey"],
101
+ key_to_pid(app_dict["WIRApplicationIdentifierKey"]),
102
+ app_dict["WIRApplicationNameKey"],
103
+ AutomationAvailability(app_dict["WIRAutomationAvailabilityKey"]),
104
+ app_dict["WIRIsApplicationActiveKey"],
105
+ app_dict["WIRIsApplicationProxyKey"],
106
+ app_dict["WIRIsApplicationReadyKey"],
107
+ app_dict.get("WIRHostApplicationIdentifierKey", ""),
104
108
  )
105
109
 
106
110
 
@@ -110,12 +114,12 @@ class ApplicationPage:
110
114
  page: Page
111
115
 
112
116
  def __str__(self) -> str:
113
- return f'<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>'
117
+ return f"<{self.application.name}({self.application.pid}) TYPE:{self.page.type_.value} URL:{self.page.web_url}>"
114
118
 
115
119
 
116
120
  class WebinspectorService:
117
- SERVICE_NAME = 'com.apple.webinspector'
118
- RSD_SERVICE_NAME = 'com.apple.webinspector.shim.remote'
121
+ SERVICE_NAME = "com.apple.webinspector"
122
+ RSD_SERVICE_NAME = "com.apple.webinspector.shim.remote"
119
123
 
120
124
  def __init__(self, lockdown: LockdownServiceProvider, loop=None):
121
125
  if loop is None:
@@ -142,18 +146,18 @@ class WebinspectorService:
142
146
  self.wir_message_results = {}
143
147
  self.wir_events = []
144
148
  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,
149
+ "_rpc_reportCurrentState:": self._handle_report_current_state,
150
+ "_rpc_reportConnectedApplicationList:": self._handle_report_connected_application_list,
151
+ "_rpc_reportConnectedDriverList:": self._handle_report_connected_driver_list,
152
+ "_rpc_applicationSentListing:": self._handle_application_sent_listing,
153
+ "_rpc_applicationUpdated:": self._handle_application_updated,
154
+ "_rpc_applicationConnected:": self._handle_application_connected,
155
+ "_rpc_applicationSentData:": self._handle_application_sent_data,
156
+ "_rpc_applicationDisconnected:": self._handle_application_disconnected,
153
157
  }
154
158
  self._recv_task: Optional[asyncio.Task] = None
155
159
 
156
- def connect(self, timeout: Union[float, int] = None):
160
+ def connect(self, timeout: Optional[Union[float, int]] = None):
157
161
  self.service = self.await_(self.lockdown.aio_start_lockdown_service(self.service_name))
158
162
  self.await_(self._report_identifier())
159
163
  try:
@@ -164,10 +168,8 @@ class WebinspectorService:
164
168
 
165
169
  def close(self):
166
170
  self._recv_task.cancel()
167
- try:
171
+ with contextlib.suppress(asyncio.CancelledError):
168
172
  self.await_(self._recv_task)
169
- except asyncio.CancelledError:
170
- pass
171
173
  self.await_(self.service.aio_close())
172
174
 
173
175
  async def _recv_message(self):
@@ -182,7 +184,7 @@ class WebinspectorService:
182
184
  self._handle_recv(await self._recv_message())
183
185
 
184
186
  def automation_session(self, app: Application) -> AutomationSession:
185
- if self.state == 'WIRAutomationAvailabilityNotAvailable':
187
+ if self.state == "WIRAutomationAvailabilityNotAvailable":
186
188
  raise RemoteAutomationNotEnabledError()
187
189
  session_id = str(uuid.uuid4()).upper()
188
190
  self.await_(self._forward_automation_session_request(session_id, app.id_))
@@ -196,8 +198,10 @@ class WebinspectorService:
196
198
 
197
199
  async def inspector_session(self, app: Application, page: Page) -> InspectorSession:
198
200
  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)
201
+ return await InspectorSession.create(
202
+ SessionProtocol(self, session_id, app, page, method_prefix=""),
203
+ wait_target=page.type_ != WirTypes.JAVASCRIPT,
204
+ )
201
205
 
202
206
  def get_open_pages(self) -> dict:
203
207
  apps = {}
@@ -226,8 +230,8 @@ class WebinspectorService:
226
230
  self.get_open_pages()
227
231
  try:
228
232
  return self._await_with_timeout(self._wait_for_application(bundle), timeout)
229
- except TimeoutError:
230
- raise LaunchingApplicationError()
233
+ except TimeoutError as e:
234
+ raise LaunchingApplicationError() from e
231
235
 
232
236
  async def send_socket_data(self, session_id: str, app_id: str, page_id: int, data: dict):
233
237
  await self._forward_socket_data(session_id, app_id, page_id, data)
@@ -247,29 +251,27 @@ class WebinspectorService:
247
251
  def await_(self, awaitable: Coroutine) -> Any:
248
252
  return self.loop.run_until_complete(awaitable)
249
253
 
250
- def _await_with_timeout(self, coro: Coroutine, timeout: float = None) -> Any:
254
+ def _await_with_timeout(self, coro: Coroutine, timeout: Optional[float] = None) -> Any:
251
255
  # Create a task explicitly so we're definitely inside a Task
252
256
  task = self.loop.create_task(coro)
253
- done, pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
257
+ done, _pending = self.loop.run_until_complete(asyncio.wait({task}, timeout=timeout))
254
258
  if not done:
255
259
  task.cancel()
256
260
  # Give the task a chance to cancel cleanly
257
- try:
261
+ with contextlib.suppress(asyncio.CancelledError):
258
262
  self.loop.run_until_complete(task)
259
- except asyncio.CancelledError:
260
- pass
261
263
  raise WebInspectorNotEnabledError()
262
264
  return task.result()
263
265
 
264
266
  def _handle_recv(self, plist):
265
- self.receive_handlers[plist['__selector']](plist['__argument'])
267
+ self.receive_handlers[plist["__selector"]](plist["__argument"])
266
268
 
267
269
  def _handle_report_current_state(self, arg):
268
- self.state = arg['WIRAutomationAvailabilityKey']
270
+ self.state = arg["WIRAutomationAvailabilityKey"]
269
271
 
270
272
  def _handle_report_connected_application_list(self, arg):
271
273
  self.connected_application = {}
272
- for key, application in arg['WIRApplicationDictionaryKey'].items():
274
+ for key, application in arg["WIRApplicationDictionaryKey"].items():
273
275
  self.connected_application[key] = Application.from_application_dictionary(application)
274
276
 
275
277
  # Immediately also query the application pages
@@ -279,19 +281,19 @@ class WebinspectorService:
279
281
  pass
280
282
 
281
283
  def _handle_application_sent_listing(self, arg):
282
- if arg['WIRApplicationIdentifierKey'] in self.application_pages:
284
+ if arg["WIRApplicationIdentifierKey"] in self.application_pages:
283
285
  # 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)
286
+ for id_, page in arg["WIRListingKey"].items():
287
+ if id_ in self.application_pages[arg["WIRApplicationIdentifierKey"]]:
288
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_].update(page)
287
289
  else:
288
- self.application_pages[arg['WIRApplicationIdentifierKey']][id_] = Page.from_page_dictionary(page)
290
+ self.application_pages[arg["WIRApplicationIdentifierKey"]][id_] = Page.from_page_dictionary(page)
289
291
  else:
290
292
  # Add new application pages
291
293
  pages = {}
292
- for id_, page in arg['WIRListingKey'].items():
294
+ for id_, page in arg["WIRListingKey"].items():
293
295
  pages[id_] = Page.from_page_dictionary(page)
294
- self.application_pages[arg['WIRApplicationIdentifierKey']] = pages
296
+ self.application_pages[arg["WIRApplicationIdentifierKey"]] = pages
295
297
 
296
298
  def _handle_application_updated(self, arg):
297
299
  app = Application.from_application_dictionary(arg)
@@ -302,72 +304,81 @@ class WebinspectorService:
302
304
  self.connected_application[app.id_] = app
303
305
 
304
306
  def _handle_application_sent_data(self, arg):
305
- response = json.loads(arg['WIRMessageDataKey'])
307
+ response = json.loads(arg["WIRMessageDataKey"])
306
308
 
307
- if 'id' in response:
308
- self.wir_message_results[response['id']] = response
309
+ if "id" in response:
310
+ self.wir_message_results[response["id"]] = response
309
311
  else:
310
312
  self.wir_events.append(response)
311
313
 
312
314
  def _handle_application_disconnected(self, arg):
313
- self.connected_application.pop(arg['WIRApplicationIdentifierKey'], None)
314
- self.application_pages.pop(arg['WIRApplicationIdentifierKey'], None)
315
+ self.connected_application.pop(arg["WIRApplicationIdentifierKey"], None)
316
+ self.application_pages.pop(arg["WIRApplicationIdentifierKey"], None)
315
317
 
316
318
  async def _report_identifier(self):
317
- await self._send_message('_rpc_reportIdentifier:')
319
+ await self._send_message("_rpc_reportIdentifier:")
318
320
 
319
321
  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})
322
+ self.logger.debug(f"Listing app with id {app_id}")
323
+ await self._send_message("_rpc_forwardGetListing:", {"WIRApplicationIdentifierKey": app_id})
322
324
 
323
325
  async def _request_application_launch(self, bundle: str):
324
- await self._send_message('_rpc_requestApplicationLaunch:', {'WIRApplicationBundleIdentifierKey': bundle})
326
+ await self._send_message("_rpc_requestApplicationLaunch:", {"WIRApplicationBundleIdentifierKey": bundle})
325
327
 
326
328
  async def _get_connected_applications(self) -> None:
327
- await self._send_message('_rpc_getConnectedApplications:', {})
329
+ await self._send_message("_rpc_getConnectedApplications:", {})
328
330
 
329
331
  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,
332
+ await self._send_message(
333
+ "_rpc_forwardAutomationSessionRequest:",
334
+ {
335
+ "WIRApplicationIdentifierKey": app_id,
336
+ "WIRSessionCapabilitiesKey": {
337
+ "org.webkit.webdriver.webrtc.allow-insecure-media-capture": True,
338
+ "org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering": False,
339
+ },
340
+ "WIRSessionIdentifierKey": session_id,
335
341
  },
336
- 'WIRSessionIdentifierKey': session_id
337
- })
342
+ )
338
343
 
339
344
  async def _forward_socket_setup(self, session_id: str, app_id: str, page_id: int, pause: bool = True):
340
345
  message = {
341
- 'WIRApplicationIdentifierKey': app_id,
342
- 'WIRPageIdentifierKey': page_id,
343
- 'WIRSenderKey': session_id,
344
- 'WIRMessageDataTypeChunkSupportedKey': 0,
346
+ "WIRApplicationIdentifierKey": app_id,
347
+ "WIRPageIdentifierKey": page_id,
348
+ "WIRSenderKey": session_id,
349
+ "WIRMessageDataTypeChunkSupportedKey": 0,
345
350
  }
346
351
  if not pause:
347
- message['WIRAutomaticallyPause'] = False
348
- await self._send_message('_rpc_forwardSocketSetup:', message)
352
+ message["WIRAutomaticallyPause"] = False
353
+ await self._send_message("_rpc_forwardSocketSetup:", message)
349
354
 
350
355
  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
- })
356
+ await self._send_message(
357
+ "_rpc_forwardSocketData:",
358
+ {
359
+ "WIRApplicationIdentifierKey": app_id,
360
+ "WIRPageIdentifierKey": page_id,
361
+ "WIRSessionIdentifierKey": session_id,
362
+ "WIRSenderKey": session_id,
363
+ "WIRSocketDataKey": json.dumps(data).encode(),
364
+ },
365
+ )
358
366
 
359
367
  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
- }
368
+ (
369
+ await self._send_message("_rpc_forwardIndicateWebView"),
370
+ {
371
+ "WIRApplicationIdentifierKey": app_id,
372
+ "WIRPageIdentifierKey": page_id,
373
+ "WIRIndicateEnabledKey": enable,
374
+ },
375
+ )
365
376
 
366
377
  async def _send_message(self, selector: str, args=None):
367
378
  if args is None:
368
379
  args = {}
369
- args['WIRConnectionIdentifierKey'] = self.connection_id
370
- await self.service.aio_send_plist({'__selector': selector, '__argument': args})
380
+ args["WIRConnectionIdentifierKey"] = self.connection_id
381
+ await self.service.aio_send_plist({"__selector": selector, "__argument": args})
371
382
 
372
383
  def _page_by_automation_session(self, session_id: str) -> Page:
373
384
  for app_id in self.application_pages:
@@ -383,7 +394,7 @@ class WebinspectorService:
383
394
  return page
384
395
  await asyncio.sleep(0)
385
396
 
386
- async def _wait_for_application(self, bundle: str = '', app_id: str = '') -> Application:
397
+ async def _wait_for_application(self, bundle: str = "", app_id: str = "") -> Application:
387
398
  while True:
388
399
  for app in self.connected_application.values():
389
400
  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)