autoglm-gui 1.0.0__py3-none-any.whl → 1.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.
- AutoGLM_GUI/api/devices.py +49 -0
- AutoGLM_GUI/schemas.py +16 -0
- AutoGLM_GUI/static/assets/{about-29B5FDM8.js → about-BOnRPlKQ.js} +1 -1
- AutoGLM_GUI/static/assets/chat-CGW6uMKB.js +149 -0
- AutoGLM_GUI/static/assets/{index-mVNV0VwM.js → index-CRFVU0eu.js} +1 -1
- AutoGLM_GUI/static/assets/{index-wu8Wjf12.js → index-DH-Dl4tK.js} +5 -5
- AutoGLM_GUI/static/assets/index-DzUQ89YC.css +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.2.dist-info}/METADATA +9 -4
- autoglm_gui-1.0.2.dist-info/RECORD +73 -0
- phone_agent/__init__.py +3 -2
- phone_agent/actions/handler.py +124 -31
- phone_agent/actions/handler_ios.py +278 -0
- phone_agent/adb/connection.py +14 -5
- phone_agent/adb/device.py +47 -16
- phone_agent/agent.py +8 -8
- phone_agent/agent_ios.py +277 -0
- phone_agent/config/__init__.py +18 -0
- phone_agent/config/apps.py +1 -1
- phone_agent/config/apps_harmonyos.py +256 -0
- phone_agent/config/apps_ios.py +339 -0
- phone_agent/config/i18n.py +8 -0
- phone_agent/config/timing.py +167 -0
- phone_agent/device_factory.py +166 -0
- phone_agent/hdc/__init__.py +53 -0
- phone_agent/hdc/connection.py +384 -0
- phone_agent/hdc/device.py +269 -0
- phone_agent/hdc/input.py +145 -0
- phone_agent/hdc/screenshot.py +127 -0
- phone_agent/model/client.py +104 -4
- phone_agent/xctest/__init__.py +47 -0
- phone_agent/xctest/connection.py +379 -0
- phone_agent/xctest/device.py +472 -0
- phone_agent/xctest/input.py +311 -0
- phone_agent/xctest/screenshot.py +226 -0
- AutoGLM_GUI/static/assets/chat-DTN2oKtA.js +0 -149
- AutoGLM_GUI/static/assets/index-Dy550Qqg.css +0 -1
- autoglm_gui-1.0.0.dist-info/RECORD +0 -57
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.2.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.2.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Input utilities for iOS device text input via WebDriverAgent."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _get_wda_session_url(wda_url: str, session_id: str | None, endpoint: str) -> str:
|
|
7
|
+
"""
|
|
8
|
+
Get the correct WDA URL for a session endpoint.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
wda_url: Base WDA URL.
|
|
12
|
+
session_id: Optional session ID.
|
|
13
|
+
endpoint: The endpoint path.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Full URL for the endpoint.
|
|
17
|
+
"""
|
|
18
|
+
base = wda_url.rstrip("/")
|
|
19
|
+
if session_id:
|
|
20
|
+
return f"{base}/session/{session_id}/{endpoint}"
|
|
21
|
+
else:
|
|
22
|
+
# Try to use WDA endpoints without session when possible
|
|
23
|
+
return f"{base}/{endpoint}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def type_text(
|
|
27
|
+
text: str,
|
|
28
|
+
wda_url: str = "http://localhost:8100",
|
|
29
|
+
session_id: str | None = None,
|
|
30
|
+
frequency: int = 60,
|
|
31
|
+
) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Type text into the currently focused input field.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
text: The text to type.
|
|
37
|
+
wda_url: WebDriverAgent URL.
|
|
38
|
+
session_id: Optional WDA session ID.
|
|
39
|
+
frequency: Typing frequency (keys per minute). Default is 60.
|
|
40
|
+
|
|
41
|
+
Note:
|
|
42
|
+
The input field must be focused before calling this function.
|
|
43
|
+
Use tap() to focus on the input field first.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
import requests
|
|
47
|
+
|
|
48
|
+
url = _get_wda_session_url(wda_url, session_id, "wda/keys")
|
|
49
|
+
|
|
50
|
+
# Send text to WDA
|
|
51
|
+
response = requests.post(
|
|
52
|
+
url,
|
|
53
|
+
json={"value": list(text), "frequency": frequency},
|
|
54
|
+
timeout=30,
|
|
55
|
+
verify=False,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if response.status_code not in (200, 201):
|
|
59
|
+
print(
|
|
60
|
+
f"Warning: Text input may have failed. Status: {response.status_code}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
except ImportError:
|
|
64
|
+
print("Error: requests library required. Install: pip install requests")
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(f"Error typing text: {e}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def clear_text(
|
|
70
|
+
wda_url: str = "http://localhost:8100",
|
|
71
|
+
session_id: str | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Clear text in the currently focused input field.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
wda_url: WebDriverAgent URL.
|
|
78
|
+
session_id: Optional WDA session ID.
|
|
79
|
+
|
|
80
|
+
Note:
|
|
81
|
+
This sends a clear command to the active element.
|
|
82
|
+
The input field must be focused before calling this function.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
import requests
|
|
86
|
+
|
|
87
|
+
# First, try to get the active element
|
|
88
|
+
url = _get_wda_session_url(wda_url, session_id, "element/active")
|
|
89
|
+
|
|
90
|
+
response = requests.get(url, timeout=10, verify=False)
|
|
91
|
+
|
|
92
|
+
if response.status_code == 200:
|
|
93
|
+
data = response.json()
|
|
94
|
+
element_id = data.get("value", {}).get("ELEMENT") or data.get(
|
|
95
|
+
"value", {}
|
|
96
|
+
).get("element-6066-11e4-a52e-4f735466cecf")
|
|
97
|
+
|
|
98
|
+
if element_id:
|
|
99
|
+
# Clear the element
|
|
100
|
+
clear_url = _get_wda_session_url(
|
|
101
|
+
wda_url, session_id, f"element/{element_id}/clear"
|
|
102
|
+
)
|
|
103
|
+
requests.post(clear_url, timeout=10, verify=False)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
# Fallback: send backspace commands
|
|
107
|
+
_clear_with_backspace(wda_url, session_id)
|
|
108
|
+
|
|
109
|
+
except ImportError:
|
|
110
|
+
print("Error: requests library required. Install: pip install requests")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Error clearing text: {e}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _clear_with_backspace(
|
|
116
|
+
wda_url: str = "http://localhost:8100",
|
|
117
|
+
session_id: str | None = None,
|
|
118
|
+
max_backspaces: int = 100,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""
|
|
121
|
+
Clear text by sending backspace keys.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
wda_url: WebDriverAgent URL.
|
|
125
|
+
session_id: Optional WDA session ID.
|
|
126
|
+
max_backspaces: Maximum number of backspaces to send.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
import requests
|
|
130
|
+
|
|
131
|
+
url = _get_wda_session_url(wda_url, session_id, "wda/keys")
|
|
132
|
+
|
|
133
|
+
# Send backspace character multiple times
|
|
134
|
+
backspace_char = "\u0008" # Backspace Unicode character
|
|
135
|
+
requests.post(
|
|
136
|
+
url,
|
|
137
|
+
json={"value": [backspace_char] * max_backspaces},
|
|
138
|
+
timeout=10,
|
|
139
|
+
verify=False,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"Error clearing with backspace: {e}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def send_keys(
|
|
147
|
+
keys: list[str],
|
|
148
|
+
wda_url: str = "http://localhost:8100",
|
|
149
|
+
session_id: str | None = None,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Send a sequence of keys.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
keys: List of keys to send.
|
|
156
|
+
wda_url: WebDriverAgent URL.
|
|
157
|
+
session_id: Optional WDA session ID.
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
>>> send_keys(["H", "e", "l", "l", "o"])
|
|
161
|
+
>>> send_keys(["\n"]) # Send enter key
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
import requests
|
|
165
|
+
|
|
166
|
+
url = _get_wda_session_url(wda_url, session_id, "wda/keys")
|
|
167
|
+
|
|
168
|
+
requests.post(url, json={"value": keys}, timeout=10, verify=False)
|
|
169
|
+
|
|
170
|
+
except ImportError:
|
|
171
|
+
print("Error: requests library required. Install: pip install requests")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
print(f"Error sending keys: {e}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def press_enter(
|
|
177
|
+
wda_url: str = "http://localhost:8100",
|
|
178
|
+
session_id: str | None = None,
|
|
179
|
+
delay: float = 0.5,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Press the Enter/Return key.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
wda_url: WebDriverAgent URL.
|
|
186
|
+
session_id: Optional WDA session ID.
|
|
187
|
+
delay: Delay in seconds after pressing enter.
|
|
188
|
+
"""
|
|
189
|
+
send_keys(["\n"], wda_url, session_id)
|
|
190
|
+
time.sleep(delay)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def hide_keyboard(
|
|
194
|
+
wda_url: str = "http://localhost:8100",
|
|
195
|
+
session_id: str | None = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Hide the on-screen keyboard.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
wda_url: WebDriverAgent URL.
|
|
202
|
+
session_id: Optional WDA session ID.
|
|
203
|
+
"""
|
|
204
|
+
try:
|
|
205
|
+
import requests
|
|
206
|
+
|
|
207
|
+
url = f"{wda_url.rstrip('/')}/wda/keyboard/dismiss"
|
|
208
|
+
|
|
209
|
+
requests.post(url, timeout=10, verify=False)
|
|
210
|
+
|
|
211
|
+
except ImportError:
|
|
212
|
+
print("Error: requests library required. Install: pip install requests")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"Error hiding keyboard: {e}")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_keyboard_shown(
|
|
218
|
+
wda_url: str = "http://localhost:8100",
|
|
219
|
+
session_id: str | None = None,
|
|
220
|
+
) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Check if the on-screen keyboard is currently shown.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
wda_url: WebDriverAgent URL.
|
|
226
|
+
session_id: Optional WDA session ID.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if keyboard is shown, False otherwise.
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
import requests
|
|
233
|
+
|
|
234
|
+
url = _get_wda_session_url(wda_url, session_id, "wda/keyboard/shown")
|
|
235
|
+
|
|
236
|
+
response = requests.get(url, timeout=5, verify=False)
|
|
237
|
+
|
|
238
|
+
if response.status_code == 200:
|
|
239
|
+
data = response.json()
|
|
240
|
+
return data.get("value", False)
|
|
241
|
+
|
|
242
|
+
except ImportError:
|
|
243
|
+
print("Error: requests library required. Install: pip install requests")
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def set_pasteboard(
|
|
251
|
+
text: str,
|
|
252
|
+
wda_url: str = "http://localhost:8100",
|
|
253
|
+
) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Set the device pasteboard (clipboard) content.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
text: Text to set in pasteboard.
|
|
259
|
+
wda_url: WebDriverAgent URL.
|
|
260
|
+
|
|
261
|
+
Note:
|
|
262
|
+
This can be useful for inputting large amounts of text.
|
|
263
|
+
After setting pasteboard, you can simulate paste gesture.
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
import requests
|
|
267
|
+
|
|
268
|
+
url = f"{wda_url.rstrip('/')}/wda/setPasteboard"
|
|
269
|
+
|
|
270
|
+
requests.post(
|
|
271
|
+
url,
|
|
272
|
+
json={"content": text, "contentType": "plaintext"},
|
|
273
|
+
timeout=10,
|
|
274
|
+
verify=False,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
except ImportError:
|
|
278
|
+
print("Error: requests library required. Install: pip install requests")
|
|
279
|
+
except Exception as e:
|
|
280
|
+
print(f"Error setting pasteboard: {e}")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_pasteboard(
|
|
284
|
+
wda_url: str = "http://localhost:8100",
|
|
285
|
+
) -> str | None:
|
|
286
|
+
"""
|
|
287
|
+
Get the device pasteboard (clipboard) content.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
wda_url: WebDriverAgent URL.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Pasteboard content or None if failed.
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
import requests
|
|
297
|
+
|
|
298
|
+
url = f"{wda_url.rstrip('/')}/wda/getPasteboard"
|
|
299
|
+
|
|
300
|
+
response = requests.post(url, timeout=10, verify=False)
|
|
301
|
+
|
|
302
|
+
if response.status_code == 200:
|
|
303
|
+
data = response.json()
|
|
304
|
+
return data.get("value")
|
|
305
|
+
|
|
306
|
+
except ImportError:
|
|
307
|
+
print("Error: requests library required. Install: pip install requests")
|
|
308
|
+
except Exception as e:
|
|
309
|
+
print(f"Error getting pasteboard: {e}")
|
|
310
|
+
|
|
311
|
+
return None
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Screenshot utilities for capturing iOS device screen."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from io import BytesIO
|
|
10
|
+
|
|
11
|
+
from PIL import Image
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Screenshot:
|
|
16
|
+
"""Represents a captured screenshot."""
|
|
17
|
+
|
|
18
|
+
base64_data: str
|
|
19
|
+
width: int
|
|
20
|
+
height: int
|
|
21
|
+
is_sensitive: bool = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_screenshot(
|
|
25
|
+
wda_url: str = "http://localhost:8100",
|
|
26
|
+
session_id: str | None = None,
|
|
27
|
+
device_id: str | None = None,
|
|
28
|
+
timeout: int = 10,
|
|
29
|
+
) -> Screenshot:
|
|
30
|
+
"""
|
|
31
|
+
Capture a screenshot from the connected iOS device.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
wda_url: WebDriverAgent URL.
|
|
35
|
+
session_id: Optional WDA session ID.
|
|
36
|
+
device_id: Optional device UDID (for idevicescreenshot fallback).
|
|
37
|
+
timeout: Timeout in seconds for screenshot operations.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Screenshot object containing base64 data and dimensions.
|
|
41
|
+
|
|
42
|
+
Note:
|
|
43
|
+
Tries WebDriverAgent first, falls back to idevicescreenshot if available.
|
|
44
|
+
If both fail, returns a black fallback image.
|
|
45
|
+
"""
|
|
46
|
+
# Try WebDriverAgent first (preferred method)
|
|
47
|
+
screenshot = _get_screenshot_wda(wda_url, session_id, timeout)
|
|
48
|
+
if screenshot:
|
|
49
|
+
return screenshot
|
|
50
|
+
|
|
51
|
+
# Fallback to idevicescreenshot
|
|
52
|
+
screenshot = _get_screenshot_idevice(device_id, timeout)
|
|
53
|
+
if screenshot:
|
|
54
|
+
return screenshot
|
|
55
|
+
|
|
56
|
+
# Return fallback black image
|
|
57
|
+
return _create_fallback_screenshot(is_sensitive=False)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_screenshot_wda(
|
|
61
|
+
wda_url: str, session_id: str | None, timeout: int
|
|
62
|
+
) -> Screenshot | None:
|
|
63
|
+
"""
|
|
64
|
+
Capture screenshot using WebDriverAgent.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
wda_url: WebDriverAgent URL.
|
|
68
|
+
session_id: Optional WDA session ID.
|
|
69
|
+
timeout: Timeout in seconds.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Screenshot object or None if failed.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
import requests
|
|
76
|
+
|
|
77
|
+
url = f"{wda_url.rstrip('/')}/screenshot"
|
|
78
|
+
|
|
79
|
+
response = requests.get(url, timeout=timeout, verify=False)
|
|
80
|
+
|
|
81
|
+
if response.status_code == 200:
|
|
82
|
+
data = response.json()
|
|
83
|
+
base64_data = data.get("value", "")
|
|
84
|
+
|
|
85
|
+
if base64_data:
|
|
86
|
+
# Decode to get dimensions
|
|
87
|
+
img_data = base64.b64decode(base64_data)
|
|
88
|
+
img = Image.open(BytesIO(img_data))
|
|
89
|
+
width, height = img.size
|
|
90
|
+
|
|
91
|
+
return Screenshot(
|
|
92
|
+
base64_data=base64_data,
|
|
93
|
+
width=width,
|
|
94
|
+
height=height,
|
|
95
|
+
is_sensitive=False,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
except ImportError:
|
|
99
|
+
print("Note: requests library not installed. Install: pip install requests")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"WDA screenshot failed: {e}")
|
|
102
|
+
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_screenshot_idevice(device_id: str | None, timeout: int) -> Screenshot | None:
|
|
107
|
+
"""
|
|
108
|
+
Capture screenshot using idevicescreenshot (libimobiledevice).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
device_id: Optional device UDID.
|
|
112
|
+
timeout: Timeout in seconds.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Screenshot object or None if failed.
|
|
116
|
+
"""
|
|
117
|
+
try:
|
|
118
|
+
temp_path = os.path.join(
|
|
119
|
+
tempfile.gettempdir(), f"ios_screenshot_{uuid.uuid4()}.png"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
cmd = ["idevicescreenshot"]
|
|
123
|
+
if device_id:
|
|
124
|
+
cmd.extend(["-u", device_id])
|
|
125
|
+
cmd.append(temp_path)
|
|
126
|
+
|
|
127
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
128
|
+
|
|
129
|
+
if result.returncode == 0 and os.path.exists(temp_path):
|
|
130
|
+
# Read and encode image
|
|
131
|
+
img = Image.open(temp_path)
|
|
132
|
+
width, height = img.size
|
|
133
|
+
|
|
134
|
+
buffered = BytesIO()
|
|
135
|
+
img.save(buffered, format="PNG")
|
|
136
|
+
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
137
|
+
|
|
138
|
+
# Cleanup
|
|
139
|
+
os.remove(temp_path)
|
|
140
|
+
|
|
141
|
+
return Screenshot(
|
|
142
|
+
base64_data=base64_data, width=width, height=height, is_sensitive=False
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except FileNotFoundError:
|
|
146
|
+
print(
|
|
147
|
+
"Note: idevicescreenshot not found. Install: brew install libimobiledevice"
|
|
148
|
+
)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f"idevicescreenshot failed: {e}")
|
|
151
|
+
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _create_fallback_screenshot(is_sensitive: bool) -> Screenshot:
|
|
156
|
+
"""
|
|
157
|
+
Create a black fallback image when screenshot fails.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
is_sensitive: Whether the failure was due to sensitive content.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Screenshot object with black image.
|
|
164
|
+
"""
|
|
165
|
+
# Default iPhone screen size (iPhone 14 Pro)
|
|
166
|
+
default_width, default_height = 1179, 2556
|
|
167
|
+
|
|
168
|
+
black_img = Image.new("RGB", (default_width, default_height), color="black")
|
|
169
|
+
buffered = BytesIO()
|
|
170
|
+
black_img.save(buffered, format="PNG")
|
|
171
|
+
base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
|
172
|
+
|
|
173
|
+
return Screenshot(
|
|
174
|
+
base64_data=base64_data,
|
|
175
|
+
width=default_width,
|
|
176
|
+
height=default_height,
|
|
177
|
+
is_sensitive=is_sensitive,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def save_screenshot(
|
|
182
|
+
screenshot: Screenshot,
|
|
183
|
+
file_path: str,
|
|
184
|
+
) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Save a screenshot to a file.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
screenshot: Screenshot object.
|
|
190
|
+
file_path: Path to save the screenshot.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if successful, False otherwise.
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
img_data = base64.b64decode(screenshot.base64_data)
|
|
197
|
+
img = Image.open(BytesIO(img_data))
|
|
198
|
+
img.save(file_path)
|
|
199
|
+
return True
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"Error saving screenshot: {e}")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_screenshot_png(
|
|
206
|
+
wda_url: str = "http://localhost:8100",
|
|
207
|
+
session_id: str | None = None,
|
|
208
|
+
device_id: str | None = None,
|
|
209
|
+
) -> bytes | None:
|
|
210
|
+
"""
|
|
211
|
+
Get screenshot as PNG bytes.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
wda_url: WebDriverAgent URL.
|
|
215
|
+
session_id: Optional WDA session ID.
|
|
216
|
+
device_id: Optional device UDID.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
PNG bytes or None if failed.
|
|
220
|
+
"""
|
|
221
|
+
screenshot = get_screenshot(wda_url, session_id, device_id)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
return base64.b64decode(screenshot.base64_data)
|
|
225
|
+
except Exception:
|
|
226
|
+
return None
|