autoglm-gui 1.4.1__py3-none-any.whl → 1.5.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.
Files changed (135) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -4
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. phone_agent/actions/handler_ios.py → AutoGLM_GUI/actions/handler.py +30 -112
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. {phone_agent → AutoGLM_GUI}/adb/__init__.py +25 -23
  7. {phone_agent → AutoGLM_GUI}/adb/connection.py +5 -40
  8. {phone_agent → AutoGLM_GUI}/adb/device.py +12 -94
  9. {phone_agent → AutoGLM_GUI}/adb/input.py +6 -47
  10. AutoGLM_GUI/adb/screenshot.py +11 -0
  11. {phone_agent/config → AutoGLM_GUI/adb}/timing.py +1 -1
  12. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  13. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  14. AutoGLM_GUI/adb_plus/serial.py +38 -20
  15. AutoGLM_GUI/adb_plus/touch.py +4 -9
  16. AutoGLM_GUI/agents/__init__.py +43 -12
  17. AutoGLM_GUI/agents/events.py +19 -0
  18. AutoGLM_GUI/agents/factory.py +31 -38
  19. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  20. AutoGLM_GUI/agents/glm/agent.py +297 -0
  21. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  22. AutoGLM_GUI/agents/glm/parser.py +110 -0
  23. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_en.py +7 -9
  24. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_zh.py +18 -25
  25. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  26. AutoGLM_GUI/agents/mai/agent.py +408 -0
  27. AutoGLM_GUI/agents/mai/parser.py +254 -0
  28. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  29. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  30. AutoGLM_GUI/agents/protocols.py +12 -8
  31. AutoGLM_GUI/agents/stream_runner.py +193 -0
  32. AutoGLM_GUI/api/__init__.py +40 -21
  33. AutoGLM_GUI/api/agents.py +181 -239
  34. AutoGLM_GUI/api/control.py +9 -6
  35. AutoGLM_GUI/api/devices.py +102 -12
  36. AutoGLM_GUI/api/history.py +104 -0
  37. AutoGLM_GUI/api/layered_agent.py +67 -15
  38. AutoGLM_GUI/api/media.py +64 -1
  39. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  40. AutoGLM_GUI/config.py +81 -0
  41. AutoGLM_GUI/config_manager.py +68 -51
  42. AutoGLM_GUI/device_manager.py +248 -29
  43. AutoGLM_GUI/device_protocol.py +1 -1
  44. AutoGLM_GUI/devices/adb_device.py +5 -10
  45. AutoGLM_GUI/devices/mock_device.py +4 -2
  46. AutoGLM_GUI/devices/remote_device.py +8 -3
  47. AutoGLM_GUI/history_manager.py +164 -0
  48. AutoGLM_GUI/model/__init__.py +5 -0
  49. AutoGLM_GUI/model/message_builder.py +69 -0
  50. AutoGLM_GUI/model/types.py +24 -0
  51. AutoGLM_GUI/models/__init__.py +10 -0
  52. AutoGLM_GUI/models/history.py +140 -0
  53. AutoGLM_GUI/models/scheduled_task.py +71 -0
  54. AutoGLM_GUI/parsers/__init__.py +22 -0
  55. AutoGLM_GUI/parsers/base.py +50 -0
  56. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  57. AutoGLM_GUI/phone_agent_manager.py +62 -396
  58. AutoGLM_GUI/platform_utils.py +26 -0
  59. AutoGLM_GUI/prompt_config.py +15 -0
  60. AutoGLM_GUI/prompts/__init__.py +32 -0
  61. AutoGLM_GUI/scheduler_manager.py +350 -0
  62. AutoGLM_GUI/schemas.py +246 -72
  63. AutoGLM_GUI/scrcpy_stream.py +142 -24
  64. AutoGLM_GUI/socketio_server.py +100 -27
  65. AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-CfwX1Cmc.js} +1 -1
  66. AutoGLM_GUI/static/assets/alert-dialog-CtGlN2IJ.js +1 -0
  67. AutoGLM_GUI/static/assets/chat-BYa-foUI.js +129 -0
  68. AutoGLM_GUI/static/assets/circle-alert-t08bEMPO.js +1 -0
  69. AutoGLM_GUI/static/assets/dialog-FNwZJFwk.js +45 -0
  70. AutoGLM_GUI/static/assets/eye-D0UPWCWC.js +1 -0
  71. AutoGLM_GUI/static/assets/history-CRo95B7i.js +1 -0
  72. AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-BaLMSqd3.js} +1 -1
  73. AutoGLM_GUI/static/assets/index-CTHbFvKl.js +11 -0
  74. AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
  75. AutoGLM_GUI/static/assets/label-DJFevVmr.js +1 -0
  76. AutoGLM_GUI/static/assets/logs-RW09DyYY.js +1 -0
  77. AutoGLM_GUI/static/assets/popover--JTJrE5v.js +1 -0
  78. AutoGLM_GUI/static/assets/scheduled-tasks-DTRKsQXF.js +1 -0
  79. AutoGLM_GUI/static/assets/square-pen-CPK_K680.js +1 -0
  80. AutoGLM_GUI/static/assets/textarea-PRmVnWq5.js +1 -0
  81. AutoGLM_GUI/static/assets/workflows-CdcsAoaT.js +1 -0
  82. AutoGLM_GUI/static/index.html +2 -2
  83. AutoGLM_GUI/types.py +17 -0
  84. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/METADATA +179 -130
  85. autoglm_gui-1.5.1.dist-info/RECORD +118 -0
  86. AutoGLM_GUI/agents/mai_adapter.py +0 -627
  87. AutoGLM_GUI/api/dual_model.py +0 -317
  88. AutoGLM_GUI/device_adapter.py +0 -263
  89. AutoGLM_GUI/dual_model/__init__.py +0 -53
  90. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  91. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  92. AutoGLM_GUI/dual_model/protocols.py +0 -354
  93. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  94. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  95. AutoGLM_GUI/phone_agent_patches.py +0 -147
  96. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
  97. AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
  98. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
  99. AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
  100. AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
  101. autoglm_gui-1.4.1.dist-info/RECORD +0 -117
  102. mai_agent/base.py +0 -137
  103. mai_agent/mai_grounding_agent.py +0 -263
  104. mai_agent/mai_naivigation_agent.py +0 -526
  105. mai_agent/prompt.py +0 -148
  106. mai_agent/unified_memory.py +0 -67
  107. mai_agent/utils.py +0 -73
  108. phone_agent/__init__.py +0 -12
  109. phone_agent/actions/__init__.py +0 -5
  110. phone_agent/actions/handler.py +0 -400
  111. phone_agent/adb/screenshot.py +0 -108
  112. phone_agent/agent.py +0 -253
  113. phone_agent/agent_ios.py +0 -277
  114. phone_agent/config/__init__.py +0 -53
  115. phone_agent/config/apps_harmonyos.py +0 -256
  116. phone_agent/config/apps_ios.py +0 -339
  117. phone_agent/config/prompts.py +0 -80
  118. phone_agent/device_factory.py +0 -166
  119. phone_agent/hdc/__init__.py +0 -53
  120. phone_agent/hdc/connection.py +0 -384
  121. phone_agent/hdc/device.py +0 -269
  122. phone_agent/hdc/input.py +0 -145
  123. phone_agent/hdc/screenshot.py +0 -127
  124. phone_agent/model/__init__.py +0 -5
  125. phone_agent/model/client.py +0 -290
  126. phone_agent/xctest/__init__.py +0 -47
  127. phone_agent/xctest/connection.py +0 -379
  128. phone_agent/xctest/device.py +0 -472
  129. phone_agent/xctest/input.py +0 -311
  130. phone_agent/xctest/screenshot.py +0 -226
  131. {phone_agent/config → AutoGLM_GUI/adb}/apps.py +0 -0
  132. {phone_agent/config → AutoGLM_GUI}/i18n.py +0 -0
  133. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/WHEEL +0 -0
  134. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/entry_points.txt +0 -0
  135. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/licenses/LICENSE +0 -0
phone_agent/hdc/device.py DELETED
@@ -1,269 +0,0 @@
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"]
phone_agent/hdc/input.py DELETED
@@ -1,145 +0,0 @@
1
- """Input utilities for HarmonyOS device text input."""
2
-
3
- from phone_agent.hdc.connection import _run_hdc_command
4
-
5
-
6
- def type_text(text: str, device_id: str | None = None) -> None:
7
- """
8
- Type text into the currently focused input field.
9
-
10
- Args:
11
- text: The text to type. Supports multi-line text with newline characters.
12
- device_id: Optional HDC device ID for multi-device setups.
13
-
14
- Note:
15
- HarmonyOS uses: hdc shell uitest uiInput text "文本内容"
16
- This command works without coordinates when input field is focused.
17
- For multi-line text, the function splits by newlines and sends ENTER keyEvents.
18
- ENTER key code in HarmonyOS: 2054
19
- Recommendation: Click on the input field first to focus it, then use this function.
20
- """
21
- hdc_prefix = _get_hdc_prefix(device_id)
22
-
23
- # Handle multi-line text by splitting on newlines
24
- if "\n" in text:
25
- lines = text.split("\n")
26
- for i, line in enumerate(lines):
27
- if line: # Only process non-empty lines
28
- # Escape special characters for shell
29
- escaped_line = line.replace('"', '\\"').replace("$", "\\$")
30
-
31
- _run_hdc_command(
32
- hdc_prefix + ["shell", "uitest", "uiInput", "text", escaped_line],
33
- capture_output=True,
34
- text=True,
35
- )
36
-
37
- # Send ENTER key event after each line except the last one
38
- if i < len(lines) - 1:
39
- try:
40
- _run_hdc_command(
41
- hdc_prefix + ["shell", "uitest", "uiInput", "keyEvent", "2054"],
42
- capture_output=True,
43
- text=True,
44
- )
45
- except Exception as e:
46
- print(f"[HDC] ENTER keyEvent failed: {e}")
47
- else:
48
- # Single line text - original logic
49
- # Escape special characters for shell (keep quotes for proper text handling)
50
- # The text will be wrapped in quotes in the command
51
- escaped_text = text.replace('"', '\\"').replace("$", "\\$")
52
-
53
- # HarmonyOS uitest uiInput text command
54
- # Format: hdc shell uitest uiInput text "文本内容"
55
- _run_hdc_command(
56
- hdc_prefix + ["shell", "uitest", "uiInput", "text", escaped_text],
57
- capture_output=True,
58
- text=True,
59
- )
60
-
61
-
62
- def clear_text(device_id: str | None = None) -> None:
63
- """
64
- Clear text in the currently focused input field.
65
-
66
- Args:
67
- device_id: Optional HDC device ID for multi-device setups.
68
-
69
- Note:
70
- This method uses repeated delete key events to clear text.
71
- For HarmonyOS, you might also use select all + delete for better efficiency.
72
- """
73
- hdc_prefix = _get_hdc_prefix(device_id)
74
- # Ctrl+A to select all (key code 2072 for Ctrl, 2017 for A)
75
- # Then delete
76
- _run_hdc_command(
77
- hdc_prefix + ["shell", "uitest", "uiInput", "keyEvent", "2072", "2017"],
78
- capture_output=True,
79
- text=True,
80
- )
81
- _run_hdc_command(
82
- hdc_prefix + ["shell", "uitest", "uiInput", "keyEvent", "2055"], # Delete key
83
- capture_output=True,
84
- text=True,
85
- )
86
-
87
-
88
- def detect_and_set_adb_keyboard(device_id: str | None = None) -> str:
89
- """
90
- Detect current keyboard and switch to ADB Keyboard if available.
91
-
92
- Args:
93
- device_id: Optional HDC device ID for multi-device setups.
94
-
95
- Returns:
96
- The original keyboard IME identifier for later restoration.
97
-
98
- Note:
99
- This is a placeholder. HarmonyOS may not support ADB Keyboard.
100
- If there's a similar tool for HarmonyOS, integrate it here.
101
- """
102
- hdc_prefix = _get_hdc_prefix(device_id)
103
-
104
- # Get current IME (if HarmonyOS supports this)
105
- try:
106
- result = _run_hdc_command(
107
- hdc_prefix + ["shell", "settings", "get", "secure", "default_input_method"],
108
- capture_output=True,
109
- text=True,
110
- )
111
- current_ime = (result.stdout + result.stderr).strip()
112
-
113
- # If ADB Keyboard equivalent exists for HarmonyOS, switch to it
114
- # For now, we'll just return the current IME
115
- return current_ime
116
- except Exception:
117
- return ""
118
-
119
-
120
- def restore_keyboard(ime: str, device_id: str | None = None) -> None:
121
- """
122
- Restore the original keyboard IME.
123
-
124
- Args:
125
- ime: The IME identifier to restore.
126
- device_id: Optional HDC device ID for multi-device setups.
127
- """
128
- if not ime:
129
- return
130
-
131
- hdc_prefix = _get_hdc_prefix(device_id)
132
-
133
- try:
134
- _run_hdc_command(
135
- hdc_prefix + ["shell", "ime", "set", ime], capture_output=True, text=True
136
- )
137
- except Exception:
138
- pass
139
-
140
-
141
- def _get_hdc_prefix(device_id: str | None) -> list:
142
- """Get HDC command prefix with optional device specifier."""
143
- if device_id:
144
- return ["hdc", "-t", device_id]
145
- return ["hdc"]
@@ -1,127 +0,0 @@
1
- """Screenshot utilities for capturing HarmonyOS device screen."""
2
-
3
- import base64
4
- import os
5
- import tempfile
6
- import uuid
7
- from dataclasses import dataclass
8
- from io import BytesIO
9
-
10
- from PIL import Image
11
- from phone_agent.hdc.connection import _run_hdc_command
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(device_id: str | None = None, timeout: int = 10) -> Screenshot:
25
- """
26
- Capture a screenshot from the connected HarmonyOS device.
27
-
28
- Args:
29
- device_id: Optional HDC device ID for multi-device setups.
30
- timeout: Timeout in seconds for screenshot operations.
31
-
32
- Returns:
33
- Screenshot object containing base64 data and dimensions.
34
-
35
- Note:
36
- If the screenshot fails (e.g., on sensitive screens like payment pages),
37
- a black fallback image is returned with is_sensitive=True.
38
- """
39
- temp_path = os.path.join(tempfile.gettempdir(), f"screenshot_{uuid.uuid4()}.png")
40
- hdc_prefix = _get_hdc_prefix(device_id)
41
-
42
- try:
43
- # Execute screenshot command
44
- # HarmonyOS HDC only supports JPEG format
45
- remote_path = "/data/local/tmp/tmp_screenshot.jpeg"
46
-
47
- # Try method 1: hdc shell screenshot (newer HarmonyOS versions)
48
- result = _run_hdc_command(
49
- hdc_prefix + ["shell", "screenshot", remote_path],
50
- capture_output=True,
51
- text=True,
52
- timeout=timeout,
53
- )
54
-
55
- # Check for screenshot failure (sensitive screen)
56
- output = result.stdout + result.stderr
57
- if (
58
- "fail" in output.lower()
59
- or "error" in output.lower()
60
- or "not found" in output.lower()
61
- ):
62
- # Try method 2: snapshot_display (older versions or different devices)
63
- result = _run_hdc_command(
64
- hdc_prefix + ["shell", "snapshot_display", "-f", remote_path],
65
- capture_output=True,
66
- text=True,
67
- timeout=timeout,
68
- )
69
- output = result.stdout + result.stderr
70
- if "fail" in output.lower() or "error" in output.lower():
71
- return _create_fallback_screenshot(is_sensitive=True)
72
-
73
- # Pull screenshot to local temp path
74
- # Note: remote file is JPEG, but PIL can open it regardless of local extension
75
- _run_hdc_command(
76
- hdc_prefix + ["file", "recv", remote_path, temp_path],
77
- capture_output=True,
78
- text=True,
79
- timeout=5,
80
- )
81
-
82
- if not os.path.exists(temp_path):
83
- return _create_fallback_screenshot(is_sensitive=False)
84
-
85
- # Read JPEG image and convert to PNG for model inference
86
- # PIL automatically detects the image format from file content
87
- img = Image.open(temp_path)
88
- width, height = img.size
89
-
90
- buffered = BytesIO()
91
- img.save(buffered, format="PNG")
92
- base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
93
-
94
- # Cleanup
95
- os.remove(temp_path)
96
-
97
- return Screenshot(
98
- base64_data=base64_data, width=width, height=height, is_sensitive=False
99
- )
100
-
101
- except Exception as e:
102
- print(f"Screenshot error: {e}")
103
- return _create_fallback_screenshot(is_sensitive=False)
104
-
105
-
106
- def _get_hdc_prefix(device_id: str | None) -> list:
107
- """Get HDC command prefix with optional device specifier."""
108
- if device_id:
109
- return ["hdc", "-t", device_id]
110
- return ["hdc"]
111
-
112
-
113
- def _create_fallback_screenshot(is_sensitive: bool) -> Screenshot:
114
- """Create a black fallback image when screenshot fails."""
115
- default_width, default_height = 1080, 2400
116
-
117
- black_img = Image.new("RGB", (default_width, default_height), color="black")
118
- buffered = BytesIO()
119
- black_img.save(buffered, format="PNG")
120
- base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8")
121
-
122
- return Screenshot(
123
- base64_data=base64_data,
124
- width=default_width,
125
- height=default_height,
126
- is_sensitive=is_sensitive,
127
- )
@@ -1,5 +0,0 @@
1
- """Model client module for AI inference."""
2
-
3
- from phone_agent.model.client import ModelClient, ModelConfig
4
-
5
- __all__ = ["ModelClient", "ModelConfig"]