pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. misc/plist_sniffer.py +15 -15
  2. misc/remotexpc_sniffer.py +29 -28
  3. misc/understanding_idevice_protocol_layers.md +15 -10
  4. pymobiledevice3/__main__.py +317 -127
  5. pymobiledevice3/_version.py +22 -4
  6. pymobiledevice3/bonjour.py +358 -113
  7. pymobiledevice3/ca.py +253 -16
  8. pymobiledevice3/cli/activation.py +31 -23
  9. pymobiledevice3/cli/afc.py +49 -40
  10. pymobiledevice3/cli/amfi.py +16 -21
  11. pymobiledevice3/cli/apps.py +87 -42
  12. pymobiledevice3/cli/backup.py +160 -90
  13. pymobiledevice3/cli/bonjour.py +44 -40
  14. pymobiledevice3/cli/cli_common.py +204 -198
  15. pymobiledevice3/cli/companion_proxy.py +14 -14
  16. pymobiledevice3/cli/crash.py +105 -56
  17. pymobiledevice3/cli/developer/__init__.py +62 -0
  18. pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
  19. pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
  20. pymobiledevice3/cli/developer/arbitration.py +50 -0
  21. pymobiledevice3/cli/developer/condition.py +33 -0
  22. pymobiledevice3/cli/developer/core_device.py +294 -0
  23. pymobiledevice3/cli/developer/debugserver.py +244 -0
  24. pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
  25. pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
  26. pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
  27. pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
  28. pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
  29. pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
  30. pymobiledevice3/cli/developer/simulate_location.py +51 -0
  31. pymobiledevice3/cli/diagnostics/__init__.py +75 -0
  32. pymobiledevice3/cli/diagnostics/battery.py +47 -0
  33. pymobiledevice3/cli/idam.py +42 -0
  34. pymobiledevice3/cli/lockdown.py +108 -103
  35. pymobiledevice3/cli/mounter.py +158 -99
  36. pymobiledevice3/cli/notification.py +38 -26
  37. pymobiledevice3/cli/pcap.py +45 -24
  38. pymobiledevice3/cli/power_assertion.py +18 -17
  39. pymobiledevice3/cli/processes.py +17 -23
  40. pymobiledevice3/cli/profile.py +165 -109
  41. pymobiledevice3/cli/provision.py +35 -34
  42. pymobiledevice3/cli/remote.py +217 -129
  43. pymobiledevice3/cli/restore.py +159 -143
  44. pymobiledevice3/cli/springboard.py +63 -53
  45. pymobiledevice3/cli/syslog.py +193 -86
  46. pymobiledevice3/cli/usbmux.py +73 -33
  47. pymobiledevice3/cli/version.py +5 -7
  48. pymobiledevice3/cli/webinspector.py +376 -214
  49. pymobiledevice3/common.py +3 -1
  50. pymobiledevice3/exceptions.py +182 -58
  51. pymobiledevice3/irecv.py +52 -53
  52. pymobiledevice3/irecv_devices.py +1489 -464
  53. pymobiledevice3/lockdown.py +473 -275
  54. pymobiledevice3/lockdown_service_provider.py +15 -8
  55. pymobiledevice3/osu/os_utils.py +27 -9
  56. pymobiledevice3/osu/posix_util.py +34 -15
  57. pymobiledevice3/osu/win_util.py +14 -8
  58. pymobiledevice3/pair_records.py +102 -21
  59. pymobiledevice3/remote/common.py +8 -4
  60. pymobiledevice3/remote/core_device/app_service.py +94 -67
  61. pymobiledevice3/remote/core_device/core_device_service.py +17 -14
  62. pymobiledevice3/remote/core_device/device_info.py +5 -5
  63. pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
  64. pymobiledevice3/remote/core_device/file_service.py +53 -23
  65. pymobiledevice3/remote/remote_service_discovery.py +79 -45
  66. pymobiledevice3/remote/remotexpc.py +73 -44
  67. pymobiledevice3/remote/tunnel_service.py +442 -317
  68. pymobiledevice3/remote/utils.py +14 -13
  69. pymobiledevice3/remote/xpc_message.py +145 -125
  70. pymobiledevice3/resources/dsc_uuid_map.py +19 -19
  71. pymobiledevice3/resources/firmware_notifications.py +20 -16
  72. pymobiledevice3/resources/notifications.txt +144 -0
  73. pymobiledevice3/restore/asr.py +27 -27
  74. pymobiledevice3/restore/base_restore.py +110 -21
  75. pymobiledevice3/restore/consts.py +87 -66
  76. pymobiledevice3/restore/device.py +59 -12
  77. pymobiledevice3/restore/fdr.py +46 -48
  78. pymobiledevice3/restore/ftab.py +19 -19
  79. pymobiledevice3/restore/img4.py +163 -0
  80. pymobiledevice3/restore/mbn.py +587 -0
  81. pymobiledevice3/restore/recovery.py +151 -151
  82. pymobiledevice3/restore/restore.py +562 -544
  83. pymobiledevice3/restore/restore_options.py +131 -110
  84. pymobiledevice3/restore/restored_client.py +51 -31
  85. pymobiledevice3/restore/tss.py +385 -267
  86. pymobiledevice3/service_connection.py +252 -59
  87. pymobiledevice3/services/accessibilityaudit.py +202 -120
  88. pymobiledevice3/services/afc.py +962 -365
  89. pymobiledevice3/services/amfi.py +24 -30
  90. pymobiledevice3/services/companion.py +23 -19
  91. pymobiledevice3/services/crash_reports.py +71 -47
  92. pymobiledevice3/services/debugserver_applist.py +3 -3
  93. pymobiledevice3/services/device_arbitration.py +8 -8
  94. pymobiledevice3/services/device_link.py +101 -79
  95. pymobiledevice3/services/diagnostics.py +973 -967
  96. pymobiledevice3/services/dtfetchsymbols.py +8 -8
  97. pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
  98. pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
  99. pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
  100. pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
  101. pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
  102. pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
  103. pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
  104. pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
  105. pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
  106. pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
  107. pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
  108. pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
  109. pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
  110. pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
  111. pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
  112. pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
  113. pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
  114. pymobiledevice3/services/file_relay.py +10 -10
  115. pymobiledevice3/services/heartbeat.py +9 -8
  116. pymobiledevice3/services/house_arrest.py +16 -15
  117. pymobiledevice3/services/idam.py +20 -0
  118. pymobiledevice3/services/installation_proxy.py +173 -81
  119. pymobiledevice3/services/lockdown_service.py +20 -10
  120. pymobiledevice3/services/misagent.py +22 -19
  121. pymobiledevice3/services/mobile_activation.py +147 -64
  122. pymobiledevice3/services/mobile_config.py +331 -294
  123. pymobiledevice3/services/mobile_image_mounter.py +141 -113
  124. pymobiledevice3/services/mobilebackup2.py +203 -145
  125. pymobiledevice3/services/notification_proxy.py +11 -11
  126. pymobiledevice3/services/os_trace.py +134 -74
  127. pymobiledevice3/services/pcapd.py +314 -302
  128. pymobiledevice3/services/power_assertion.py +10 -9
  129. pymobiledevice3/services/preboard.py +4 -4
  130. pymobiledevice3/services/remote_fetch_symbols.py +21 -14
  131. pymobiledevice3/services/remote_server.py +176 -146
  132. pymobiledevice3/services/restore_service.py +16 -16
  133. pymobiledevice3/services/screenshot.py +15 -12
  134. pymobiledevice3/services/simulate_location.py +7 -7
  135. pymobiledevice3/services/springboard.py +15 -15
  136. pymobiledevice3/services/syslog.py +5 -5
  137. pymobiledevice3/services/web_protocol/alert.py +11 -11
  138. pymobiledevice3/services/web_protocol/automation_session.py +251 -239
  139. pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
  140. pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
  141. pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
  142. pymobiledevice3/services/web_protocol/driver.py +114 -111
  143. pymobiledevice3/services/web_protocol/element.py +124 -111
  144. pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
  145. pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
  146. pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
  147. pymobiledevice3/services/web_protocol/switch_to.py +30 -27
  148. pymobiledevice3/services/webinspector.py +189 -155
  149. pymobiledevice3/tcp_forwarder.py +87 -69
  150. pymobiledevice3/tunneld/__init__.py +0 -0
  151. pymobiledevice3/tunneld/api.py +63 -0
  152. pymobiledevice3/tunneld/server.py +603 -0
  153. pymobiledevice3/usbmux.py +198 -147
  154. pymobiledevice3/utils.py +14 -11
  155. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
  156. pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
  157. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
  158. pymobiledevice3/cli/developer.py +0 -1215
  159. pymobiledevice3/cli/diagnostics.py +0 -99
  160. pymobiledevice3/tunneld.py +0 -524
  161. pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
  162. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
  163. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
  164. {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import typing
3
+ from collections.abc import Generator
3
4
  from dataclasses import dataclass
4
5
  from enum import Enum, IntEnum
5
6
 
@@ -21,14 +22,58 @@ class AXAuditInspectorFocus_v1(SerializedObject):
21
22
 
22
23
  @property
23
24
  def caption(self) -> str:
24
- return self._fields.get('CaptionTextValue_v1')
25
+ return self._fields.get("CaptionTextValue_v1")
26
+
27
+ @property
28
+ def spoken_description(self) -> str:
29
+ return self._fields.get("SpokenDescriptionValue_v1")
25
30
 
26
31
  @property
27
32
  def element(self) -> bytes:
28
- return self._fields.get('ElementValue_v1')
33
+ return self._fields.get("ElementValue_v1")
34
+
35
+ @property
36
+ def platform_identifier(self) -> str:
37
+ """Converts the element bytes to a hexadecimal string."""
38
+ return self.element.identifier.hex().upper()
39
+
40
+ @property
41
+ def estimated_uid(self) -> str:
42
+ """Generates a UID from the platform identifier."""
43
+ hex_value = self.platform_identifier
44
+
45
+ if len(hex_value) % 2 != 0:
46
+ raise ValueError("Hex value length must be even.")
47
+
48
+ hex_bytes = bytes.fromhex(hex_value)
49
+
50
+ if len(hex_bytes) < 16:
51
+ raise ValueError("Hex value must contain at least 16 bytes.")
52
+
53
+ # Extract TimeLow bytes (indexes 12 to 15)
54
+ time_low_bytes = hex_bytes[12:16]
55
+ time_low = time_low_bytes.hex().upper()
56
+
57
+ # Extract ClockSeq bytes (indexes 0 to 1)
58
+ clock_seq_bytes = hex_bytes[0:2]
59
+ clock_seq = clock_seq_bytes.hex().upper()
60
+
61
+ # Construct UID with placeholder values for unused parts
62
+ uid = f"{time_low}-0000-0000-{clock_seq}-000000000000"
63
+
64
+ return uid
65
+
66
+ def to_dict(self) -> dict:
67
+ """Serializes the focus element into a dictionary."""
68
+ return {
69
+ "platform_identifier": self.platform_identifier,
70
+ "estimated_uid": self.estimated_uid,
71
+ "caption": self.caption,
72
+ "spoken_description": self.spoken_description,
73
+ }
29
74
 
30
75
  def __str__(self):
31
- return f'<Focused ElementCaption: {self.caption}>'
76
+ return f"<Focused ElementCaption: {self.caption}>"
32
77
 
33
78
 
34
79
  class AXAuditElement_v1(SerializedObject):
@@ -37,10 +82,10 @@ class AXAuditElement_v1(SerializedObject):
37
82
 
38
83
  @property
39
84
  def identifier(self) -> bytes:
40
- return self._fields['PlatformElementValue_v1'].NSdata
85
+ return self._fields["PlatformElementValue_v1"]
41
86
 
42
87
  def __repr__(self):
43
- return f'<Element: {self.identifier}>'
88
+ return f"<Element: {self.identifier}>"
44
89
 
45
90
 
46
91
  class AXAuditInspectorSection_v1(SerializedObject):
@@ -54,7 +99,7 @@ class AXAuditElementAttribute_v1(SerializedObject):
54
99
 
55
100
 
56
101
  class AXAuditDeviceSetting_v1(SerializedObject):
57
- FIELDS = ('IdentiifierValue_v1', 'CurrentValueNumber_v1')
102
+ FIELDS = ("IdentiifierValue_v1", "CurrentValueNumber_v1")
58
103
 
59
104
  def __init__(self, fields):
60
105
  super().__init__(fields)
@@ -64,14 +109,14 @@ class AXAuditDeviceSetting_v1(SerializedObject):
64
109
 
65
110
  @property
66
111
  def key(self) -> str:
67
- return self._fields['IdentiifierValue_v1']
112
+ return self._fields["IdentiifierValue_v1"]
68
113
 
69
114
  @property
70
115
  def value(self) -> typing.Any:
71
- return self._fields['CurrentValueNumber_v1']
116
+ return self._fields["CurrentValueNumber_v1"]
72
117
 
73
118
  def __str__(self) -> str:
74
- return f'<AXAuditDeviceSetting_v1 {self.key} = {self.value}>'
119
+ return f"<AXAuditDeviceSetting_v1 {self.key} = {self.value}>"
75
120
 
76
121
 
77
122
  class AuditType(IntEnum):
@@ -86,21 +131,27 @@ class AuditType(IntEnum):
86
131
 
87
132
 
88
133
  AUDIT_TYPE_DESCRIPTIONS = {
89
- AuditType.DYNAMIC_TEXT: 'testTypeDynamicText',
90
- AuditType.DYNAMIC_TEXT_ALT: 'testTypeDynamicText',
91
- AuditType.TEXT_CLIPPED: 'testTypeTextClipped',
92
- AuditType.ELEMENT_DETECTION: 'testTypeElementDetection',
93
- AuditType.SUFFICIENT_ELEMENT_DESCRIPTION: 'testTypeSufficientElementDescription',
94
- AuditType.HIT_REGION: 'testTypeHitRegion',
95
- AuditType.CONTRAST: 'testTypeContrast',
96
- AuditType.CONTRAST_ALT: 'testTypeContrast'
134
+ AuditType.DYNAMIC_TEXT: "testTypeDynamicText",
135
+ AuditType.DYNAMIC_TEXT_ALT: "testTypeDynamicText",
136
+ AuditType.TEXT_CLIPPED: "testTypeTextClipped",
137
+ AuditType.ELEMENT_DETECTION: "testTypeElementDetection",
138
+ AuditType.SUFFICIENT_ELEMENT_DESCRIPTION: "testTypeSufficientElementDescription",
139
+ AuditType.HIT_REGION: "testTypeHitRegion",
140
+ AuditType.CONTRAST: "testTypeContrast",
141
+ AuditType.CONTRAST_ALT: "testTypeContrast",
97
142
  }
98
143
 
99
144
 
100
145
  class AXAuditIssue_v1(SerializedObject):
101
- FIELDS = ('ElementRectValue_v1', 'IssueClassificationValue_v1',
102
- 'FontSizeValue_v1', 'MLGeneratedDescriptionValue_v1', 'ElementLongDescExtraInfo_v1',
103
- 'BackgroundColorValue_v1', 'ForegroundColorValue_v1')
146
+ FIELDS = (
147
+ "ElementRectValue_v1",
148
+ "IssueClassificationValue_v1",
149
+ "FontSizeValue_v1",
150
+ "MLGeneratedDescriptionValue_v1",
151
+ "ElementLongDescExtraInfo_v1",
152
+ "BackgroundColorValue_v1",
153
+ "ForegroundColorValue_v1",
154
+ )
104
155
 
105
156
  def __init__(self, fields):
106
157
  super().__init__(fields)
@@ -111,11 +162,11 @@ class AXAuditIssue_v1(SerializedObject):
111
162
 
112
163
  @property
113
164
  def rect(self) -> str:
114
- return self._fields['ElementRectValue_v1']
165
+ return self._fields["ElementRectValue_v1"]
115
166
 
116
167
  @property
117
168
  def issue_type(self) -> typing.Any:
118
- issue_classification = self._fields['IssueClassificationValue_v1']
169
+ issue_classification = self._fields["IssueClassificationValue_v1"]
119
170
  if issue_classification in AUDIT_TYPE_DESCRIPTIONS:
120
171
  return AUDIT_TYPE_DESCRIPTIONS[AuditType(issue_classification)]
121
172
  else:
@@ -123,36 +174,36 @@ class AXAuditIssue_v1(SerializedObject):
123
174
 
124
175
  @property
125
176
  def ml_generated_description(self) -> typing.Any:
126
- return self._fields['MLGeneratedDescriptionValue_v1']
177
+ return self._fields["MLGeneratedDescriptionValue_v1"]
127
178
 
128
179
  @property
129
180
  def long_description_extra_info(self) -> typing.Any:
130
- return self._fields['ElementLongDescExtraInfo_v1']
181
+ return self._fields["ElementLongDescExtraInfo_v1"]
131
182
 
132
183
  @property
133
184
  def font_size(self) -> typing.Any:
134
- return self._fields['FontSizeValue_v1']
185
+ return self._fields["FontSizeValue_v1"]
135
186
 
136
187
  @property
137
188
  def foreground_color(self) -> typing.Any:
138
- return self._fields['ForegroundColorValue_v1']
189
+ return self._fields["ForegroundColorValue_v1"]
139
190
 
140
191
  @property
141
192
  def background_color(self) -> typing.Any:
142
- return self._fields['BackgroundColorValue_v1']
193
+ return self._fields["BackgroundColorValue_v1"]
143
194
 
144
195
  def json(self) -> dict:
145
196
  resp = {
146
- 'element_rect_value': self.rect,
147
- 'issue_classification': self.issue_type,
148
- 'font_size': self.font_size,
149
- 'ml_generated_description': self.ml_generated_description,
150
- 'long_description_extra_info': self.long_description_extra_info
197
+ "element_rect_value": self.rect,
198
+ "issue_classification": self.issue_type,
199
+ "font_size": self.font_size,
200
+ "ml_generated_description": self.ml_generated_description,
201
+ "long_description_extra_info": self.long_description_extra_info,
151
202
  }
152
203
  # Include foreground and background colors when issue type is 'testTypeContrast'
153
- if self._fields['IssueClassificationValue_v1'] in {AuditType.CONTRAST, AuditType.CONTRAST_ALT}:
154
- resp['foreground_color'] = self.foreground_color
155
- resp['background_color'] = self.background_color
204
+ if self._fields["IssueClassificationValue_v1"] in {AuditType.CONTRAST, AuditType.CONTRAST_ALT}:
205
+ resp["foreground_color"] = self.foreground_color
206
+ resp["background_color"] = self.background_color
156
207
  return resp
157
208
 
158
209
  def __str__(self) -> str:
@@ -160,12 +211,12 @@ class AXAuditIssue_v1(SerializedObject):
160
211
 
161
212
 
162
213
  SERIALIZABLE_OBJECTS = {
163
- 'AXAuditDeviceSetting_v1': AXAuditDeviceSetting_v1,
164
- 'AXAuditInspectorFocus_v1': AXAuditInspectorFocus_v1,
165
- 'AXAuditElement_v1': AXAuditElement_v1,
166
- 'AXAuditInspectorSection_v1': AXAuditInspectorSection_v1,
167
- 'AXAuditElementAttribute_v1': AXAuditElementAttribute_v1,
168
- 'AXAuditIssue_v1': AXAuditIssue_v1
214
+ "AXAuditDeviceSetting_v1": AXAuditDeviceSetting_v1,
215
+ "AXAuditInspectorFocus_v1": AXAuditInspectorFocus_v1,
216
+ "AXAuditElement_v1": AXAuditElement_v1,
217
+ "AXAuditInspectorSection_v1": AXAuditInspectorSection_v1,
218
+ "AXAuditElementAttribute_v1": AXAuditElementAttribute_v1,
219
+ "AXAuditIssue_v1": AXAuditIssue_v1,
169
220
  }
170
221
 
171
222
 
@@ -188,22 +239,22 @@ def deserialize_object(d):
188
239
  return [deserialize_object(x) for x in d]
189
240
  return d
190
241
 
191
- if 'ObjectType' not in d:
242
+ if "ObjectType" not in d:
192
243
  # simple dictionary
193
244
  new_dict = {}
194
245
  for k, v in d.items():
195
246
  new_dict[k] = deserialize_object(v)
196
247
  return new_dict
197
248
 
198
- if d['ObjectType'] == 'passthrough':
199
- return deserialize_object(d['Value'])
249
+ if d["ObjectType"] == "passthrough":
250
+ return deserialize_object(d["Value"])
200
251
  else:
201
- return SERIALIZABLE_OBJECTS[d['ObjectType']](deserialize_object(d['Value']))
252
+ return SERIALIZABLE_OBJECTS[d["ObjectType"]](deserialize_object(d["Value"]))
202
253
 
203
254
 
204
255
  class AccessibilityAudit(RemoteServer):
205
- SERVICE_NAME = 'com.apple.accessibility.axAuditDaemon.remoteserver'
206
- RSD_SERVICE_NAME = 'com.apple.accessibility.axAuditDaemon.remoteserver.shim.remote'
256
+ SERVICE_NAME = "com.apple.accessibility.axAuditDaemon.remoteserver"
257
+ RSD_SERVICE_NAME = "com.apple.accessibility.axAuditDaemon.remoteserver.shim.remote"
207
258
 
208
259
  def __init__(self, lockdown: LockdownServiceProvider):
209
260
  if isinstance(lockdown, LockdownClient):
@@ -214,7 +265,7 @@ class AccessibilityAudit(RemoteServer):
214
265
  # flush previously received messages
215
266
  self.recv_plist()
216
267
  self.product_version = Version(lockdown.product_version)
217
- if Version(lockdown.product_version) >= Version('15.0'):
268
+ if Version(lockdown.product_version) >= Version("15.0"):
218
269
  self.recv_plist()
219
270
 
220
271
  @property
@@ -223,19 +274,19 @@ class AccessibilityAudit(RemoteServer):
223
274
  return self.recv_plist()[0]
224
275
 
225
276
  def run_audit(self, value: list) -> list[AXAuditIssue_v1]:
226
- if self.product_version >= Version('15.0'):
277
+ if self.product_version >= Version("15.0"):
227
278
  self.broadcast.deviceBeginAuditTypes_(MessageAux().append_obj(value))
228
279
  else:
229
280
  self.broadcast.deviceBeginAuditCaseIDs_(MessageAux().append_obj(value))
230
281
 
231
282
  while True:
232
283
  message = self.recv_plist()
233
- if message[1] is None or message[0] != 'hostDeviceDidCompleteAuditCategoriesWithAuditIssues:':
284
+ if message[1] is None or message[0] != "hostDeviceDidCompleteAuditCategoriesWithAuditIssues:":
234
285
  continue
235
- return deserialize_object(message[1])[0]['value']
286
+ return deserialize_object(message[1])[0]["value"]
236
287
 
237
288
  def supported_audits_types(self) -> None:
238
- if self.product_version >= Version('15.0'):
289
+ if self.product_version >= Version("15.0"):
239
290
  self.broadcast.deviceAllSupportedAuditTypes()
240
291
  else:
241
292
  self.broadcast.deviceAllAuditCaseIDs()
@@ -253,7 +304,7 @@ class AccessibilityAudit(RemoteServer):
253
304
  def set_app_monitoring_enabled(self, value: bool) -> None:
254
305
  self.broadcast.deviceSetAppMonitoringEnabled_(MessageAux().append_obj(value), expects_reply=False)
255
306
 
256
- def set_monitored_event_type(self, event_type: int = None) -> None:
307
+ def set_monitored_event_type(self, event_type: typing.Optional[int] = None) -> None:
257
308
  if event_type is None:
258
309
  event_type = 0
259
310
  self.broadcast.deviceInspectorSetMonitoredEventType_(MessageAux().append_obj(event_type), expects_reply=False)
@@ -264,9 +315,9 @@ class AccessibilityAudit(RemoteServer):
264
315
  def set_show_visuals(self, value: bool) -> None:
265
316
  self.broadcast.deviceInspectorShowVisuals_(MessageAux().append_obj(int(value)), expects_reply=False)
266
317
 
267
- def iter_events(self, app_monitoring_enabled=True, monitored_event_type: int = None) -> \
268
- typing.Generator[Event, None, None]:
269
-
318
+ def iter_events(
319
+ self, app_monitoring_enabled=True, monitored_event_type: typing.Optional[int] = None
320
+ ) -> typing.Generator[Event, None, None]:
270
321
  self.set_app_monitoring_enabled(app_monitoring_enabled)
271
322
  self.set_monitored_event_type(monitored_event_type)
272
323
 
@@ -274,98 +325,129 @@ class AccessibilityAudit(RemoteServer):
274
325
  message = self.recv_plist()
275
326
  if message[1] is None:
276
327
  continue
277
- data = [x['value'] for x in message[1]]
328
+ data = [x["value"] for x in message[1]]
278
329
  yield Event(name=message[0], data=deserialize_object(data))
279
330
 
280
331
  def move_focus_next(self) -> None:
281
332
  self.move_focus(Direction.Next)
282
333
 
283
334
  def perform_press(self, element: bytes) -> None:
284
- """ simulate click (can be used only for processes with task_for_pid-allow """
335
+ """simulate click (can be used only for processes with task_for_pid-allow"""
285
336
  element = {
286
- 'ObjectType': 'AXAuditElement_v1',
287
- 'Value': {
288
- 'ObjectType': 'passthrough',
289
- 'Value': {
290
- 'PlatformElementValue_v1': {
291
- 'ObjectType': 'passthrough'
292
- },
293
- 'Value': element,
294
- }
295
- }
337
+ "ObjectType": "AXAuditElement_v1",
338
+ "Value": {
339
+ "ObjectType": "passthrough",
340
+ "Value": {
341
+ "PlatformElementValue_v1": {"ObjectType": "passthrough"},
342
+ "Value": element,
343
+ },
344
+ },
296
345
  }
297
346
 
298
347
  action = {
299
- 'ObjectType': 'AXAuditElementAttribute_v1',
300
- 'Value': {
301
- 'ObjectType': 'passthrough',
302
- 'Value': {
303
- 'AttributeNameValue_v1': {
304
- 'ObjectType': 'passthrough',
305
- 'Value': 'AXAction-2010',
348
+ "ObjectType": "AXAuditElementAttribute_v1",
349
+ "Value": {
350
+ "ObjectType": "passthrough",
351
+ "Value": {
352
+ "AttributeNameValue_v1": {
353
+ "ObjectType": "passthrough",
354
+ "Value": "AXAction-2010",
306
355
  },
307
- 'DisplayAsTree_v1': {
308
- 'ObjectType': 'passthrough',
309
- 'Value': 0,
356
+ "DisplayAsTree_v1": {
357
+ "ObjectType": "passthrough",
358
+ "Value": 0,
310
359
  },
311
- 'HumanReadableNameValue_v1': {
312
- 'ObjectType': 'passthrough',
313
- 'Value': 'Activate',
360
+ "HumanReadableNameValue_v1": {
361
+ "ObjectType": "passthrough",
362
+ "Value": "Activate",
314
363
  },
315
- 'IsInternal_v1': {
316
- 'ObjectType': 'passthrough',
317
- 'Value': 0,
364
+ "IsInternal_v1": {
365
+ "ObjectType": "passthrough",
366
+ "Value": 0,
318
367
  },
319
- 'PerformsActionValue_v1': {
320
- 'ObjectType': 'passthrough',
321
- 'Value': 1,
368
+ "PerformsActionValue_v1": {
369
+ "ObjectType": "passthrough",
370
+ "Value": 1,
322
371
  },
323
- 'SettableValue_v1': {
324
- 'ObjectType': 'passthrough',
325
- 'Value': 0,
372
+ "SettableValue_v1": {
373
+ "ObjectType": "passthrough",
374
+ "Value": 0,
326
375
  },
327
- 'ValueTypeValue_v1': {
328
- 'ObjectType': 'passthrough',
329
- 'Value': 1,
376
+ "ValueTypeValue_v1": {
377
+ "ObjectType": "passthrough",
378
+ "Value": 1,
330
379
  },
331
- }
332
- }
380
+ },
381
+ },
333
382
  }
334
383
 
335
384
  self.broadcast.deviceElement_performAction_withValue_(
336
- MessageAux().append_obj(element).append_obj(action).append_obj(0), expects_reply=False)
385
+ MessageAux().append_obj(element).append_obj(action).append_obj(0), expects_reply=False
386
+ )
337
387
 
338
388
  def move_focus(self, direction: Direction) -> None:
339
389
  options = {
340
- 'ObjectType': 'passthrough',
341
- 'Value': {
342
- 'allowNonAX': {
343
- 'ObjectType': 'passthrough',
344
- 'Value': 0,
390
+ "ObjectType": "passthrough",
391
+ "Value": {
392
+ "allowNonAX": {
393
+ "ObjectType": "passthrough",
394
+ "Value": 0,
395
+ },
396
+ "direction": {
397
+ "ObjectType": "passthrough",
398
+ "Value": direction.value,
345
399
  },
346
- 'direction': {
347
- 'ObjectType': 'passthrough',
348
- 'Value': direction.value,
400
+ "includeContainers": {
401
+ "ObjectType": "passthrough",
402
+ "Value": 1,
349
403
  },
350
- 'includeContainers': {
351
- 'ObjectType': 'passthrough',
352
- 'Value': 1,
353
- }
354
- }
404
+ },
355
405
  }
356
406
 
357
407
  self.broadcast.deviceInspectorMoveWithOptions_(MessageAux().append_obj(options), expects_reply=False)
358
408
 
359
409
  def set_setting(self, name: str, value: typing.Any) -> None:
360
- setting = {'ObjectType': 'AXAuditDeviceSetting_v1',
361
- 'Value': {'ObjectType': 'passthrough',
362
- 'Value': {'CurrentValueNumber_v1': {'ObjectType': 'passthrough',
363
- 'Value': True},
364
- 'EnabledValue_v1': {'ObjectType': 'passthrough', 'Value': True},
365
- 'IdentiifierValue_v1': {'ObjectType': 'passthrough',
366
- 'Value': name},
367
- 'SettingTypeValue_v1': {'ObjectType': 'passthrough', 'Value': 3},
368
- 'SliderTickMarksValue_v1': {'ObjectType': 'passthrough', 'Value': 0}}}}
410
+ setting = {
411
+ "ObjectType": "AXAuditDeviceSetting_v1",
412
+ "Value": {
413
+ "ObjectType": "passthrough",
414
+ "Value": {
415
+ "CurrentValueNumber_v1": {"ObjectType": "passthrough", "Value": True},
416
+ "EnabledValue_v1": {"ObjectType": "passthrough", "Value": True},
417
+ "IdentiifierValue_v1": {"ObjectType": "passthrough", "Value": name},
418
+ "SettingTypeValue_v1": {"ObjectType": "passthrough", "Value": 3},
419
+ "SliderTickMarksValue_v1": {"ObjectType": "passthrough", "Value": 0},
420
+ },
421
+ },
422
+ }
369
423
  self.broadcast.deviceUpdateAccessibilitySetting_withValue_(
370
- MessageAux().append_obj(setting).append_obj({'ObjectType': 'passthrough', 'Value': value}),
371
- expects_reply=False)
424
+ MessageAux().append_obj(setting).append_obj({"ObjectType": "passthrough", "Value": value}),
425
+ expects_reply=False,
426
+ )
427
+
428
+ def reset_settings(self) -> None:
429
+ self.broadcast.deviceResetToDefaultAccessibilitySettings()
430
+
431
+ def iter_elements(self) -> Generator[AXAuditInspectorFocus_v1, None, None]:
432
+ iterator = self.iter_events()
433
+
434
+ # every focus change is expected publish a "hostInspectorCurrentElementChanged:"
435
+ self.move_focus_next()
436
+
437
+ visited_identifiers = set()
438
+
439
+ for event in iterator:
440
+ if event.name != "hostInspectorCurrentElementChanged:":
441
+ # ignore any other events
442
+ continue
443
+
444
+ # each such event should contain exactly one element that became in focus
445
+ current_item = event.data[0]
446
+ current_identifier = current_item.platform_identifier
447
+
448
+ if current_identifier in visited_identifiers:
449
+ break # Exit if we've seen this element before (loop detected)
450
+
451
+ yield current_item
452
+ visited_identifiers.add(current_identifier)
453
+ self.move_focus_next()