autoglm-gui 1.0.0__py3-none-any.whl → 1.0.1__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.1.dist-info}/METADATA +3 -3
- autoglm_gui-1.0.1.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.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.0.0.dist-info → autoglm_gui-1.0.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""HDC utilities for HarmonyOS device interaction."""
|
|
2
|
+
|
|
3
|
+
from phone_agent.hdc.connection import (
|
|
4
|
+
HDCConnection,
|
|
5
|
+
ConnectionType,
|
|
6
|
+
DeviceInfo,
|
|
7
|
+
list_devices,
|
|
8
|
+
quick_connect,
|
|
9
|
+
set_hdc_verbose,
|
|
10
|
+
)
|
|
11
|
+
from phone_agent.hdc.device import (
|
|
12
|
+
back,
|
|
13
|
+
double_tap,
|
|
14
|
+
get_current_app,
|
|
15
|
+
home,
|
|
16
|
+
launch_app,
|
|
17
|
+
long_press,
|
|
18
|
+
swipe,
|
|
19
|
+
tap,
|
|
20
|
+
)
|
|
21
|
+
from phone_agent.hdc.input import (
|
|
22
|
+
clear_text,
|
|
23
|
+
detect_and_set_adb_keyboard,
|
|
24
|
+
restore_keyboard,
|
|
25
|
+
type_text,
|
|
26
|
+
)
|
|
27
|
+
from phone_agent.hdc.screenshot import get_screenshot
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Screenshot
|
|
31
|
+
"get_screenshot",
|
|
32
|
+
# Input
|
|
33
|
+
"type_text",
|
|
34
|
+
"clear_text",
|
|
35
|
+
"detect_and_set_adb_keyboard",
|
|
36
|
+
"restore_keyboard",
|
|
37
|
+
# Device control
|
|
38
|
+
"get_current_app",
|
|
39
|
+
"tap",
|
|
40
|
+
"swipe",
|
|
41
|
+
"back",
|
|
42
|
+
"home",
|
|
43
|
+
"double_tap",
|
|
44
|
+
"long_press",
|
|
45
|
+
"launch_app",
|
|
46
|
+
# Connection management
|
|
47
|
+
"HDCConnection",
|
|
48
|
+
"DeviceInfo",
|
|
49
|
+
"ConnectionType",
|
|
50
|
+
"quick_connect",
|
|
51
|
+
"list_devices",
|
|
52
|
+
"set_hdc_verbose",
|
|
53
|
+
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""HDC connection management for HarmonyOS devices."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
from phone_agent.config.timing import TIMING_CONFIG
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Global flag to control HDC command output
|
|
13
|
+
_HDC_VERBOSE = os.getenv("HDC_VERBOSE", "false").lower() in ("true", "1", "yes")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _run_hdc_command(cmd: list, **kwargs) -> subprocess.CompletedProcess:
|
|
17
|
+
"""
|
|
18
|
+
Run HDC command with optional verbose output.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
cmd: Command list to execute.
|
|
22
|
+
**kwargs: Additional arguments for subprocess.run.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
CompletedProcess result.
|
|
26
|
+
"""
|
|
27
|
+
if _HDC_VERBOSE:
|
|
28
|
+
print(f"[HDC] Running command: {' '.join(cmd)}")
|
|
29
|
+
|
|
30
|
+
result = subprocess.run(cmd, **kwargs)
|
|
31
|
+
|
|
32
|
+
if _HDC_VERBOSE and result.returncode != 0:
|
|
33
|
+
print(f"[HDC] Command failed with return code {result.returncode}")
|
|
34
|
+
if hasattr(result, "stderr") and result.stderr:
|
|
35
|
+
print(f"[HDC] Error: {result.stderr}")
|
|
36
|
+
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def set_hdc_verbose(verbose: bool):
|
|
41
|
+
"""Set HDC verbose mode globally."""
|
|
42
|
+
global _HDC_VERBOSE
|
|
43
|
+
_HDC_VERBOSE = verbose
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ConnectionType(Enum):
|
|
47
|
+
"""Type of HDC connection."""
|
|
48
|
+
|
|
49
|
+
USB = "usb"
|
|
50
|
+
WIFI = "wifi"
|
|
51
|
+
REMOTE = "remote"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class DeviceInfo:
|
|
56
|
+
"""Information about a connected device."""
|
|
57
|
+
|
|
58
|
+
device_id: str
|
|
59
|
+
status: str
|
|
60
|
+
connection_type: ConnectionType
|
|
61
|
+
model: str | None = None
|
|
62
|
+
harmony_version: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HDCConnection:
|
|
66
|
+
"""
|
|
67
|
+
Manages HDC connections to HarmonyOS devices.
|
|
68
|
+
|
|
69
|
+
Supports USB, WiFi, and remote TCP/IP connections.
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
>>> conn = HDCConnection()
|
|
73
|
+
>>> # Connect to remote device
|
|
74
|
+
>>> conn.connect("192.168.1.100:5555")
|
|
75
|
+
>>> # List devices
|
|
76
|
+
>>> devices = conn.list_devices()
|
|
77
|
+
>>> # Disconnect
|
|
78
|
+
>>> conn.disconnect("192.168.1.100:5555")
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(self, hdc_path: str = "hdc"):
|
|
82
|
+
"""
|
|
83
|
+
Initialize HDC connection manager.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
hdc_path: Path to HDC executable.
|
|
87
|
+
"""
|
|
88
|
+
self.hdc_path = hdc_path
|
|
89
|
+
|
|
90
|
+
def connect(self, address: str, timeout: int = 10) -> tuple[bool, str]:
|
|
91
|
+
"""
|
|
92
|
+
Connect to a remote device via TCP/IP.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
address: Device address in format "host:port" (e.g., "192.168.1.100:5555").
|
|
96
|
+
timeout: Connection timeout in seconds.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Tuple of (success, message).
|
|
100
|
+
|
|
101
|
+
Note:
|
|
102
|
+
The remote device must have TCP/IP debugging enabled.
|
|
103
|
+
"""
|
|
104
|
+
# Validate address format
|
|
105
|
+
if ":" not in address:
|
|
106
|
+
address = f"{address}:5555" # Default HDC port
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
result = _run_hdc_command(
|
|
110
|
+
[self.hdc_path, "tconn", address],
|
|
111
|
+
capture_output=True,
|
|
112
|
+
text=True,
|
|
113
|
+
timeout=timeout,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
output = result.stdout + result.stderr
|
|
117
|
+
|
|
118
|
+
if "Connect OK" in output or "connected" in output.lower():
|
|
119
|
+
return True, f"Connected to {address}"
|
|
120
|
+
elif "already connected" in output.lower():
|
|
121
|
+
return True, f"Already connected to {address}"
|
|
122
|
+
else:
|
|
123
|
+
return False, output.strip()
|
|
124
|
+
|
|
125
|
+
except subprocess.TimeoutExpired:
|
|
126
|
+
return False, f"Connection timeout after {timeout}s"
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return False, f"Connection error: {e}"
|
|
129
|
+
|
|
130
|
+
def disconnect(self, address: str | None = None) -> tuple[bool, str]:
|
|
131
|
+
"""
|
|
132
|
+
Disconnect from a remote device.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
address: Device address to disconnect. If None, disconnects all.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Tuple of (success, message).
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
if address:
|
|
142
|
+
cmd = [self.hdc_path, "tdisconn", address]
|
|
143
|
+
else:
|
|
144
|
+
# HDC doesn't have a "disconnect all" command, so we need to list and disconnect each
|
|
145
|
+
devices = self.list_devices()
|
|
146
|
+
for device in devices:
|
|
147
|
+
if ":" in device.device_id: # Remote device
|
|
148
|
+
_run_hdc_command(
|
|
149
|
+
[self.hdc_path, "tdisconn", device.device_id],
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
timeout=5,
|
|
153
|
+
)
|
|
154
|
+
return True, "Disconnected all remote devices"
|
|
155
|
+
|
|
156
|
+
result = _run_hdc_command(
|
|
157
|
+
cmd, capture_output=True, text=True, encoding="utf-8", timeout=5
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
output = result.stdout + result.stderr
|
|
161
|
+
return True, output.strip() or "Disconnected"
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
return False, f"Disconnect error: {e}"
|
|
165
|
+
|
|
166
|
+
def list_devices(self) -> list[DeviceInfo]:
|
|
167
|
+
"""
|
|
168
|
+
List all connected devices.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of DeviceInfo objects.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
result = _run_hdc_command(
|
|
175
|
+
[self.hdc_path, "list", "targets"],
|
|
176
|
+
capture_output=True,
|
|
177
|
+
text=True,
|
|
178
|
+
timeout=5,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
devices = []
|
|
182
|
+
for line in result.stdout.strip().split("\n"):
|
|
183
|
+
if not line.strip():
|
|
184
|
+
continue
|
|
185
|
+
|
|
186
|
+
# HDC output format: device_id (status)
|
|
187
|
+
# Example: "192.168.1.100:5555" or "FMR0223C13000649"
|
|
188
|
+
device_id = line.strip()
|
|
189
|
+
|
|
190
|
+
# Determine connection type
|
|
191
|
+
if ":" in device_id:
|
|
192
|
+
conn_type = ConnectionType.REMOTE
|
|
193
|
+
else:
|
|
194
|
+
conn_type = ConnectionType.USB
|
|
195
|
+
|
|
196
|
+
# HDC doesn't provide detailed status in list command
|
|
197
|
+
# We assume "Connected" status for devices that appear
|
|
198
|
+
devices.append(
|
|
199
|
+
DeviceInfo(
|
|
200
|
+
device_id=device_id,
|
|
201
|
+
status="device",
|
|
202
|
+
connection_type=conn_type,
|
|
203
|
+
model=None,
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return devices
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
print(f"Error listing devices: {e}")
|
|
211
|
+
return []
|
|
212
|
+
|
|
213
|
+
def get_device_info(self, device_id: str | None = None) -> DeviceInfo | None:
|
|
214
|
+
"""
|
|
215
|
+
Get detailed information about a device.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
device_id: Device ID. If None, uses first available device.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
DeviceInfo or None if not found.
|
|
222
|
+
"""
|
|
223
|
+
devices = self.list_devices()
|
|
224
|
+
|
|
225
|
+
if not devices:
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
if device_id is None:
|
|
229
|
+
return devices[0]
|
|
230
|
+
|
|
231
|
+
for device in devices:
|
|
232
|
+
if device.device_id == device_id:
|
|
233
|
+
return device
|
|
234
|
+
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def is_connected(self, device_id: str | None = None) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Check if a device is connected.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
device_id: Device ID to check. If None, checks if any device is connected.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
True if connected, False otherwise.
|
|
246
|
+
"""
|
|
247
|
+
devices = self.list_devices()
|
|
248
|
+
|
|
249
|
+
if not devices:
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
if device_id is None:
|
|
253
|
+
return len(devices) > 0
|
|
254
|
+
|
|
255
|
+
return any(d.device_id == device_id for d in devices)
|
|
256
|
+
|
|
257
|
+
def enable_tcpip(
|
|
258
|
+
self, port: int = 5555, device_id: str | None = None
|
|
259
|
+
) -> tuple[bool, str]:
|
|
260
|
+
"""
|
|
261
|
+
Enable TCP/IP debugging on a USB-connected device.
|
|
262
|
+
|
|
263
|
+
This allows subsequent wireless connections to the device.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
port: TCP port for HDC (default: 5555).
|
|
267
|
+
device_id: Device ID. If None, uses first available device.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Tuple of (success, message).
|
|
271
|
+
|
|
272
|
+
Note:
|
|
273
|
+
The device must be connected via USB first.
|
|
274
|
+
After this, you can disconnect USB and connect via WiFi.
|
|
275
|
+
"""
|
|
276
|
+
try:
|
|
277
|
+
cmd = [self.hdc_path]
|
|
278
|
+
if device_id:
|
|
279
|
+
cmd.extend(["-t", device_id])
|
|
280
|
+
cmd.extend(["tmode", "port", str(port)])
|
|
281
|
+
|
|
282
|
+
result = _run_hdc_command(
|
|
283
|
+
cmd, capture_output=True, text=True, encoding="utf-8", timeout=10
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
output = result.stdout + result.stderr
|
|
287
|
+
|
|
288
|
+
if result.returncode == 0 or "success" in output.lower():
|
|
289
|
+
time.sleep(TIMING_CONFIG.connection.adb_restart_delay)
|
|
290
|
+
return True, f"TCP/IP mode enabled on port {port}"
|
|
291
|
+
else:
|
|
292
|
+
return False, output.strip()
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
return False, f"Error enabling TCP/IP: {e}"
|
|
296
|
+
|
|
297
|
+
def get_device_ip(self, device_id: str | None = None) -> str | None:
|
|
298
|
+
"""
|
|
299
|
+
Get the IP address of a connected device.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
device_id: Device ID. If None, uses first available device.
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
IP address string or None if not found.
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
cmd = [self.hdc_path]
|
|
309
|
+
if device_id:
|
|
310
|
+
cmd.extend(["-t", device_id])
|
|
311
|
+
cmd.extend(["shell", "ifconfig"])
|
|
312
|
+
|
|
313
|
+
result = _run_hdc_command(
|
|
314
|
+
cmd, capture_output=True, text=True, encoding="utf-8", timeout=5
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Parse IP from ifconfig output
|
|
318
|
+
for line in result.stdout.split("\n"):
|
|
319
|
+
if "inet addr:" in line or "inet " in line:
|
|
320
|
+
parts = line.strip().split()
|
|
321
|
+
for i, part in enumerate(parts):
|
|
322
|
+
if "addr:" in part:
|
|
323
|
+
ip = part.split(":")[1]
|
|
324
|
+
# Filter out localhost
|
|
325
|
+
if not ip.startswith("127."):
|
|
326
|
+
return ip
|
|
327
|
+
elif part == "inet" and i + 1 < len(parts):
|
|
328
|
+
ip = parts[i + 1].split("/")[0]
|
|
329
|
+
if not ip.startswith("127."):
|
|
330
|
+
return ip
|
|
331
|
+
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
except Exception as e:
|
|
335
|
+
print(f"Error getting device IP: {e}")
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def restart_server(self) -> tuple[bool, str]:
|
|
339
|
+
"""
|
|
340
|
+
Restart the HDC server.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Tuple of (success, message).
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
# Kill server
|
|
347
|
+
_run_hdc_command([self.hdc_path, "kill"], capture_output=True, timeout=5)
|
|
348
|
+
|
|
349
|
+
time.sleep(TIMING_CONFIG.connection.server_restart_delay)
|
|
350
|
+
|
|
351
|
+
# Start server (HDC auto-starts when running commands)
|
|
352
|
+
_run_hdc_command(
|
|
353
|
+
[self.hdc_path, "start", "-r"], capture_output=True, timeout=5
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return True, "HDC server restarted"
|
|
357
|
+
|
|
358
|
+
except Exception as e:
|
|
359
|
+
return False, f"Error restarting server: {e}"
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def quick_connect(address: str) -> tuple[bool, str]:
|
|
363
|
+
"""
|
|
364
|
+
Quick helper to connect to a remote device.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
address: Device address (e.g., "192.168.1.100" or "192.168.1.100:5555").
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Tuple of (success, message).
|
|
371
|
+
"""
|
|
372
|
+
conn = HDCConnection()
|
|
373
|
+
return conn.connect(address)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def list_devices() -> list[DeviceInfo]:
|
|
377
|
+
"""
|
|
378
|
+
Quick helper to list connected devices.
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
List of DeviceInfo objects.
|
|
382
|
+
"""
|
|
383
|
+
conn = HDCConnection()
|
|
384
|
+
return conn.list_devices()
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Device control utilities for HarmonyOS automation."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from phone_agent.config.apps_harmonyos import APP_ABILITIES, APP_PACKAGES
|
|
6
|
+
from phone_agent.config.timing import TIMING_CONFIG
|
|
7
|
+
from phone_agent.hdc.connection import _run_hdc_command
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_current_app(device_id: str | None = None) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Get the currently focused app name.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
device_id: Optional HDC device ID for multi-device setups.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The app name if recognized, otherwise "System Home".
|
|
19
|
+
"""
|
|
20
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
21
|
+
|
|
22
|
+
result = _run_hdc_command(
|
|
23
|
+
hdc_prefix + ["shell", "hidumper", "-s", "WindowManagerService", "-a", "-a"],
|
|
24
|
+
capture_output=True,
|
|
25
|
+
text=True,
|
|
26
|
+
encoding="utf-8",
|
|
27
|
+
)
|
|
28
|
+
output = result.stdout
|
|
29
|
+
if not output:
|
|
30
|
+
raise ValueError("No output from hidumper")
|
|
31
|
+
|
|
32
|
+
# Parse window focus info
|
|
33
|
+
for line in output.split("\n"):
|
|
34
|
+
if "focused" in line.lower() or "current" in line.lower():
|
|
35
|
+
for app_name, package in APP_PACKAGES.items():
|
|
36
|
+
if package in line:
|
|
37
|
+
return app_name
|
|
38
|
+
|
|
39
|
+
return "System Home"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def tap(
|
|
43
|
+
x: int, y: int, device_id: str | None = None, delay: float | None = None
|
|
44
|
+
) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Tap at the specified coordinates.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
x: X coordinate.
|
|
50
|
+
y: Y coordinate.
|
|
51
|
+
device_id: Optional HDC device ID.
|
|
52
|
+
delay: Delay in seconds after tap. If None, uses configured default.
|
|
53
|
+
"""
|
|
54
|
+
if delay is None:
|
|
55
|
+
delay = TIMING_CONFIG.device.default_tap_delay
|
|
56
|
+
|
|
57
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
58
|
+
|
|
59
|
+
# HarmonyOS uses uitest uiInput click
|
|
60
|
+
_run_hdc_command(
|
|
61
|
+
hdc_prefix + ["shell", "uitest", "uiInput", "click", str(x), str(y)],
|
|
62
|
+
capture_output=True,
|
|
63
|
+
)
|
|
64
|
+
time.sleep(delay)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def double_tap(
|
|
68
|
+
x: int, y: int, device_id: str | None = None, delay: float | None = None
|
|
69
|
+
) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Double tap at the specified coordinates.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
x: X coordinate.
|
|
75
|
+
y: Y coordinate.
|
|
76
|
+
device_id: Optional HDC device ID.
|
|
77
|
+
delay: Delay in seconds after double tap. If None, uses configured default.
|
|
78
|
+
"""
|
|
79
|
+
if delay is None:
|
|
80
|
+
delay = TIMING_CONFIG.device.default_double_tap_delay
|
|
81
|
+
|
|
82
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
83
|
+
|
|
84
|
+
# HarmonyOS uses uitest uiInput doubleClick
|
|
85
|
+
_run_hdc_command(
|
|
86
|
+
hdc_prefix + ["shell", "uitest", "uiInput", "doubleClick", str(x), str(y)],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
)
|
|
89
|
+
time.sleep(delay)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def long_press(
|
|
93
|
+
x: int,
|
|
94
|
+
y: int,
|
|
95
|
+
duration_ms: int = 3000,
|
|
96
|
+
device_id: str | None = None,
|
|
97
|
+
delay: float | None = None,
|
|
98
|
+
) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Long press at the specified coordinates.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
x: X coordinate.
|
|
104
|
+
y: Y coordinate.
|
|
105
|
+
duration_ms: Duration of press in milliseconds (note: HarmonyOS longClick may not support duration).
|
|
106
|
+
device_id: Optional HDC device ID.
|
|
107
|
+
delay: Delay in seconds after long press. If None, uses configured default.
|
|
108
|
+
"""
|
|
109
|
+
if delay is None:
|
|
110
|
+
delay = TIMING_CONFIG.device.default_long_press_delay
|
|
111
|
+
|
|
112
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
113
|
+
|
|
114
|
+
# HarmonyOS uses uitest uiInput longClick
|
|
115
|
+
# Note: longClick may have a fixed duration, duration_ms parameter might not be supported
|
|
116
|
+
_run_hdc_command(
|
|
117
|
+
hdc_prefix + ["shell", "uitest", "uiInput", "longClick", str(x), str(y)],
|
|
118
|
+
capture_output=True,
|
|
119
|
+
)
|
|
120
|
+
time.sleep(delay)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def swipe(
|
|
124
|
+
start_x: int,
|
|
125
|
+
start_y: int,
|
|
126
|
+
end_x: int,
|
|
127
|
+
end_y: int,
|
|
128
|
+
duration_ms: int | None = None,
|
|
129
|
+
device_id: str | None = None,
|
|
130
|
+
delay: float | None = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""
|
|
133
|
+
Swipe from start to end coordinates.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
start_x: Starting X coordinate.
|
|
137
|
+
start_y: Starting Y coordinate.
|
|
138
|
+
end_x: Ending X coordinate.
|
|
139
|
+
end_y: Ending Y coordinate.
|
|
140
|
+
duration_ms: Duration of swipe in milliseconds (auto-calculated if None).
|
|
141
|
+
device_id: Optional HDC device ID.
|
|
142
|
+
delay: Delay in seconds after swipe. If None, uses configured default.
|
|
143
|
+
"""
|
|
144
|
+
if delay is None:
|
|
145
|
+
delay = TIMING_CONFIG.device.default_swipe_delay
|
|
146
|
+
|
|
147
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
148
|
+
|
|
149
|
+
if duration_ms is None:
|
|
150
|
+
# Calculate duration based on distance
|
|
151
|
+
dist_sq = (start_x - end_x) ** 2 + (start_y - end_y) ** 2
|
|
152
|
+
duration_ms = int(dist_sq / 1000)
|
|
153
|
+
duration_ms = max(500, min(duration_ms, 1000)) # Clamp between 500-1000ms
|
|
154
|
+
|
|
155
|
+
# HarmonyOS uses uitest uiInput swipe
|
|
156
|
+
# Format: swipe startX startY endX endY duration
|
|
157
|
+
_run_hdc_command(
|
|
158
|
+
hdc_prefix
|
|
159
|
+
+ [
|
|
160
|
+
"shell",
|
|
161
|
+
"uitest",
|
|
162
|
+
"uiInput",
|
|
163
|
+
"swipe",
|
|
164
|
+
str(start_x),
|
|
165
|
+
str(start_y),
|
|
166
|
+
str(end_x),
|
|
167
|
+
str(end_y),
|
|
168
|
+
str(duration_ms),
|
|
169
|
+
],
|
|
170
|
+
capture_output=True,
|
|
171
|
+
)
|
|
172
|
+
time.sleep(delay)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def back(device_id: str | None = None, delay: float | None = None) -> None:
|
|
176
|
+
"""
|
|
177
|
+
Press the back button.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
device_id: Optional HDC device ID.
|
|
181
|
+
delay: Delay in seconds after pressing back. If None, uses configured default.
|
|
182
|
+
"""
|
|
183
|
+
if delay is None:
|
|
184
|
+
delay = TIMING_CONFIG.device.default_back_delay
|
|
185
|
+
|
|
186
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
187
|
+
|
|
188
|
+
# HarmonyOS uses uitest uiInput keyEvent Back
|
|
189
|
+
_run_hdc_command(
|
|
190
|
+
hdc_prefix + ["shell", "uitest", "uiInput", "keyEvent", "Back"],
|
|
191
|
+
capture_output=True,
|
|
192
|
+
)
|
|
193
|
+
time.sleep(delay)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def home(device_id: str | None = None, delay: float | None = None) -> None:
|
|
197
|
+
"""
|
|
198
|
+
Press the home button.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
device_id: Optional HDC device ID.
|
|
202
|
+
delay: Delay in seconds after pressing home. If None, uses configured default.
|
|
203
|
+
"""
|
|
204
|
+
if delay is None:
|
|
205
|
+
delay = TIMING_CONFIG.device.default_home_delay
|
|
206
|
+
|
|
207
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
208
|
+
|
|
209
|
+
# HarmonyOS uses uitest uiInput keyEvent Home
|
|
210
|
+
_run_hdc_command(
|
|
211
|
+
hdc_prefix + ["shell", "uitest", "uiInput", "keyEvent", "Home"],
|
|
212
|
+
capture_output=True,
|
|
213
|
+
)
|
|
214
|
+
time.sleep(delay)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def launch_app(
|
|
218
|
+
app_name: str, device_id: str | None = None, delay: float | None = None
|
|
219
|
+
) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Launch an app by name.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
app_name: The app name (must be in APP_PACKAGES).
|
|
225
|
+
device_id: Optional HDC device ID.
|
|
226
|
+
delay: Delay in seconds after launching. If None, uses configured default.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
True if app was launched, False if app not found.
|
|
230
|
+
"""
|
|
231
|
+
if delay is None:
|
|
232
|
+
delay = TIMING_CONFIG.device.default_launch_delay
|
|
233
|
+
|
|
234
|
+
if app_name not in APP_PACKAGES:
|
|
235
|
+
print(f"[HDC] App '{app_name}' not found in HarmonyOS app list")
|
|
236
|
+
print(f"[HDC] Available apps: {', '.join(sorted(APP_PACKAGES.keys())[:10])}...")
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
hdc_prefix = _get_hdc_prefix(device_id)
|
|
240
|
+
bundle = APP_PACKAGES[app_name]
|
|
241
|
+
|
|
242
|
+
# Get the ability name for this bundle
|
|
243
|
+
# Default to "EntryAbility" if not specified in APP_ABILITIES
|
|
244
|
+
ability = APP_ABILITIES.get(bundle, "EntryAbility")
|
|
245
|
+
|
|
246
|
+
# HarmonyOS uses 'aa start' command to launch apps
|
|
247
|
+
# Format: aa start -b {bundle} -a {ability}
|
|
248
|
+
_run_hdc_command(
|
|
249
|
+
hdc_prefix
|
|
250
|
+
+ [
|
|
251
|
+
"shell",
|
|
252
|
+
"aa",
|
|
253
|
+
"start",
|
|
254
|
+
"-b",
|
|
255
|
+
bundle,
|
|
256
|
+
"-a",
|
|
257
|
+
ability,
|
|
258
|
+
],
|
|
259
|
+
capture_output=True,
|
|
260
|
+
)
|
|
261
|
+
time.sleep(delay)
|
|
262
|
+
return True
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _get_hdc_prefix(device_id: str | None) -> list:
|
|
266
|
+
"""Get HDC command prefix with optional device specifier."""
|
|
267
|
+
if device_id:
|
|
268
|
+
return ["hdc", "-t", device_id]
|
|
269
|
+
return ["hdc"]
|