autoglm-gui 0.4.8__tar.gz → 0.4.9__tar.gz

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 (47) hide show
  1. autoglm_gui-0.4.9/AutoGLM_GUI/__init__.py +52 -0
  2. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/api/media.py +3 -1
  3. autoglm_gui-0.4.9/AutoGLM_GUI/platform_utils.py +37 -0
  4. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/scrcpy_stream.py +24 -80
  5. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/PKG-INFO +4 -1
  6. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/README.md +3 -0
  7. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/pyproject.toml +1 -1
  8. autoglm_gui-0.4.8/AutoGLM_GUI/__init__.py +0 -9
  9. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/.gitignore +0 -0
  10. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/__main__.py +0 -0
  11. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/adb_plus/__init__.py +0 -0
  12. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/adb_plus/screenshot.py +0 -0
  13. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/adb_plus/touch.py +0 -0
  14. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/api/__init__.py +0 -0
  15. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/api/agents.py +0 -0
  16. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/api/control.py +0 -0
  17. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/api/devices.py +0 -0
  18. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/config.py +0 -0
  19. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/schemas.py +0 -0
  20. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/server.py +0 -0
  21. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/state.py +0 -0
  22. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/assets/about-BI6OV6gm.js +0 -0
  23. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/assets/chat-C_2Cot0q.js +0 -0
  24. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/assets/index-DCrxTz-A.css +0 -0
  25. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/assets/index-Dn3vR6uV.js +0 -0
  26. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/assets/index-Do7ha9Kf.js +0 -0
  27. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/static/index.html +0 -0
  28. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/AutoGLM_GUI/version.py +0 -0
  29. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/LICENSE +0 -0
  30. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/__init__.py +0 -0
  31. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/actions/__init__.py +0 -0
  32. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/actions/handler.py +0 -0
  33. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/adb/__init__.py +0 -0
  34. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/adb/connection.py +0 -0
  35. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/adb/device.py +0 -0
  36. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/adb/input.py +0 -0
  37. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/adb/screenshot.py +0 -0
  38. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/agent.py +0 -0
  39. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/__init__.py +0 -0
  40. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/apps.py +0 -0
  41. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/i18n.py +0 -0
  42. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/prompts.py +0 -0
  43. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/prompts_en.py +0 -0
  44. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/config/prompts_zh.py +0 -0
  45. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/model/__init__.py +0 -0
  46. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/phone_agent/model/client.py +0 -0
  47. {autoglm_gui-0.4.8 → autoglm_gui-0.4.9}/scrcpy-server-v3.3.3 +0 -0
@@ -0,0 +1,52 @@
1
+ """AutoGLM-GUI package metadata."""
2
+
3
+ import subprocess
4
+ import sys
5
+ from functools import wraps
6
+ from importlib import metadata
7
+
8
+ # ============================================================================
9
+ # Fix Windows encoding issue: Force UTF-8 for all subprocess calls
10
+ # ============================================================================
11
+ # On Windows, subprocess defaults to GBK encoding which fails when ADB/scrcpy
12
+ # output UTF-8 characters. This monkey patch ensures all subprocess calls
13
+ # use UTF-8 encoding by default.
14
+
15
+ _original_run = subprocess.run
16
+ _original_popen = subprocess.Popen
17
+
18
+
19
+ @wraps(_original_run)
20
+ def _patched_run(*args, **kwargs):
21
+ """Patched subprocess.run that defaults to UTF-8 encoding on Windows."""
22
+ if sys.platform == "win32":
23
+ # Add encoding='utf-8' if text=True is set but encoding is not specified
24
+ if kwargs.get("text") or kwargs.get("universal_newlines"):
25
+ if "encoding" not in kwargs:
26
+ kwargs["encoding"] = "utf-8"
27
+ return _original_run(*args, **kwargs)
28
+
29
+
30
+ class _PatchedPopen(_original_popen):
31
+ """Patched subprocess.Popen that defaults to UTF-8 encoding on Windows."""
32
+
33
+ def __init__(self, *args, **kwargs):
34
+ if sys.platform == "win32":
35
+ # Add encoding='utf-8' if text=True is set but encoding is not specified
36
+ if kwargs.get("text") or kwargs.get("universal_newlines"):
37
+ if "encoding" not in kwargs:
38
+ kwargs["encoding"] = "utf-8"
39
+ super().__init__(*args, **kwargs)
40
+
41
+
42
+ # Apply the patches globally
43
+ subprocess.run = _patched_run
44
+ subprocess.Popen = _PatchedPopen
45
+
46
+ # ============================================================================
47
+
48
+ # Expose package version at runtime; fall back to "unknown" during editable/dev runs
49
+ try:
50
+ __version__ = metadata.version("autoglm-gui")
51
+ except metadata.PackageNotFoundError:
52
+ __version__ = "unknown"
@@ -91,7 +91,9 @@ async def video_stream_ws(
91
91
  if DEBUG_SAVE_STREAM:
92
92
  debug_dir = Path("debug_streams")
93
93
  debug_dir.mkdir(exist_ok=True)
94
- debug_file_path = debug_dir / f"{device_id}_{int(__import__('time').time())}.h264"
94
+ debug_file_path = (
95
+ debug_dir / f"{device_id}_{int(__import__('time').time())}.h264"
96
+ )
95
97
  debug_file = open(debug_file_path, "wb")
96
98
  print(f"[video/stream] DEBUG: Saving stream to {debug_file_path}")
97
99
 
@@ -0,0 +1,37 @@
1
+ """Platform-aware subprocess helpers to avoid duplicated Windows branches."""
2
+
3
+ import asyncio
4
+ import platform
5
+ import subprocess
6
+ from typing import Any, Sequence
7
+
8
+
9
+ def is_windows() -> bool:
10
+ """Return True if running on Windows."""
11
+ return platform.system() == "Windows"
12
+
13
+
14
+ async def run_cmd_silently(cmd: Sequence[str]) -> subprocess.CompletedProcess:
15
+ """Run a command, suppressing output; safe for async contexts on all platforms."""
16
+ if is_windows():
17
+ # Avoid blocking the event loop with a blocking subprocess call on Windows.
18
+ return await asyncio.to_thread(
19
+ subprocess.run, cmd, capture_output=True, check=False
20
+ )
21
+
22
+ process = await asyncio.create_subprocess_exec(
23
+ *cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
24
+ )
25
+ await process.wait()
26
+ return subprocess.CompletedProcess(cmd, process.returncode, None, None)
27
+
28
+
29
+ async def spawn_process(cmd: Sequence[str], *, capture_output: bool = False) -> Any:
30
+ """Start a long-running process with optional stdio capture."""
31
+ stdout = subprocess.PIPE if capture_output else None
32
+ stderr = subprocess.PIPE if capture_output else None
33
+
34
+ if is_windows():
35
+ return subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
36
+
37
+ return await asyncio.create_subprocess_exec(*cmd, stdout=stdout, stderr=stderr)
@@ -2,10 +2,12 @@
2
2
 
3
3
  import asyncio
4
4
  import os
5
- import platform
6
5
  import socket
7
6
  import subprocess
8
7
  from pathlib import Path
8
+ from typing import Any
9
+
10
+ from AutoGLM_GUI.platform_utils import is_windows, run_cmd_silently, spawn_process
9
11
 
10
12
 
11
13
  class ScrcpyStreamer:
@@ -34,7 +36,7 @@ class ScrcpyStreamer:
34
36
  self.port = port
35
37
  self.idr_interval_s = idr_interval_s
36
38
 
37
- self.scrcpy_process: subprocess.Popen | None = None
39
+ self.scrcpy_process: Any | None = None
38
40
  self.tcp_socket: socket.socket | None = None
39
41
  self.forward_cleanup_needed = False
40
42
 
@@ -126,50 +128,20 @@ class ScrcpyStreamer:
126
128
  if self.device_id:
127
129
  cmd_base.extend(["-s", self.device_id])
128
130
 
129
- # On Windows, use subprocess.run instead of asyncio.create_subprocess_exec
130
- # to avoid NotImplementedError in some Windows environments
131
- if platform.system() == "Windows":
132
- # Method 1: Try pkill
133
- cmd = cmd_base + ["shell", "pkill", "-9", "-f", "app_process.*scrcpy"]
134
- subprocess.run(cmd, capture_output=True, check=False)
131
+ # Method 1: Try pkill
132
+ cmd = cmd_base + ["shell", "pkill", "-9", "-f", "app_process.*scrcpy"]
133
+ await run_cmd_silently(cmd)
135
134
 
136
- # Method 2: Find and kill by PID (more reliable)
137
- cmd = cmd_base + [
138
- "shell",
139
- "ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
140
- ]
141
- subprocess.run(cmd, capture_output=True, check=False)
142
-
143
- # Method 3: Remove port forward if exists
144
- cmd_remove_forward = cmd_base + ["forward", "--remove", f"tcp:{self.port}"]
145
- subprocess.run(cmd_remove_forward, capture_output=True, check=False)
146
- else:
147
- # Original asyncio-based implementation for Unix systems
148
- # Method 1: Try pkill
149
- cmd = cmd_base + ["shell", "pkill", "-9", "-f", "app_process.*scrcpy"]
150
- process = await asyncio.create_subprocess_exec(
151
- *cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
152
- )
153
- await process.wait()
135
+ # Method 2: Find and kill by PID (more reliable)
136
+ cmd = cmd_base + [
137
+ "shell",
138
+ "ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
139
+ ]
140
+ await run_cmd_silently(cmd)
154
141
 
155
- # Method 2: Find and kill by PID (more reliable)
156
- cmd = cmd_base + [
157
- "shell",
158
- "ps -ef | grep 'app_process.*scrcpy' | grep -v grep | awk '{print $2}' | xargs kill -9",
159
- ]
160
- process = await asyncio.create_subprocess_exec(
161
- *cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
162
- )
163
- await process.wait()
164
-
165
- # Method 3: Remove port forward if exists
166
- cmd_remove_forward = cmd_base + ["forward", "--remove", f"tcp:{self.port}"]
167
- process = await asyncio.create_subprocess_exec(
168
- *cmd_remove_forward,
169
- stdout=subprocess.DEVNULL,
170
- stderr=subprocess.DEVNULL,
171
- )
172
- await process.wait()
142
+ # Method 3: Remove port forward if exists
143
+ cmd_remove_forward = cmd_base + ["forward", "--remove", f"tcp:{self.port}"]
144
+ await run_cmd_silently(cmd_remove_forward)
173
145
 
174
146
  # Wait longer for resources to be released
175
147
  print("[ScrcpyStreamer] Waiting for cleanup to complete...")
@@ -182,13 +154,7 @@ class ScrcpyStreamer:
182
154
  cmd.extend(["-s", self.device_id])
183
155
  cmd.extend(["push", self.scrcpy_server_path, "/data/local/tmp/scrcpy-server"])
184
156
 
185
- if platform.system() == "Windows":
186
- subprocess.run(cmd, capture_output=True, check=False)
187
- else:
188
- process = await asyncio.create_subprocess_exec(
189
- *cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
190
- )
191
- await process.wait()
157
+ await run_cmd_silently(cmd)
192
158
 
193
159
  async def _setup_port_forward(self) -> None:
194
160
  """Setup ADB port forwarding."""
@@ -197,13 +163,7 @@ class ScrcpyStreamer:
197
163
  cmd.extend(["-s", self.device_id])
198
164
  cmd.extend(["forward", f"tcp:{self.port}", "localabstract:scrcpy"])
199
165
 
200
- if platform.system() == "Windows":
201
- subprocess.run(cmd, capture_output=True, check=False)
202
- else:
203
- process = await asyncio.create_subprocess_exec(
204
- *cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
205
- )
206
- await process.wait()
166
+ await run_cmd_silently(cmd)
207
167
  self.forward_cleanup_needed = True
208
168
 
209
169
  async def _start_server(self) -> None:
@@ -238,22 +198,14 @@ class ScrcpyStreamer:
238
198
  cmd.extend(server_args)
239
199
 
240
200
  # Capture stderr to see error messages
241
- if platform.system() == "Windows":
242
- # On Windows, use subprocess.Popen for async-like behavior
243
- self.scrcpy_process = subprocess.Popen(
244
- cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
245
- )
246
- else:
247
- self.scrcpy_process = await asyncio.create_subprocess_exec(
248
- *cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
249
- )
201
+ self.scrcpy_process = await spawn_process(cmd, capture_output=True)
250
202
 
251
203
  # Wait for server to start
252
204
  await asyncio.sleep(2)
253
205
 
254
206
  # Check if process is still running
255
207
  error_msg = None
256
- if platform.system() == "Windows":
208
+ if is_windows():
257
209
  # For Windows Popen, check returncode directly
258
210
  if self.scrcpy_process.poll() is not None:
259
211
  # Process has exited
@@ -314,9 +266,7 @@ class ScrcpyStreamer:
314
266
 
315
267
  raise ConnectionError("Failed to connect to scrcpy server")
316
268
 
317
- def _find_nal_units(
318
- self, data: bytes
319
- ) -> list[tuple[int, int, int, bool]]:
269
+ def _find_nal_units(self, data: bytes) -> list[tuple[int, int, int, bool]]:
320
270
  """Find NAL units in H.264 data.
321
271
 
322
272
  Returns:
@@ -392,9 +342,7 @@ class ScrcpyStreamer:
392
342
  f"[ScrcpyStreamer] ✓ Cached SPS ({size} bytes, complete={is_complete}): {hex_preview}..."
393
343
  )
394
344
  elif size < 10:
395
- print(
396
- f"[ScrcpyStreamer] ✗ Skipped short SPS ({size} bytes)"
397
- )
345
+ print(f"[ScrcpyStreamer] ✗ Skipped short SPS ({size} bytes)")
398
346
 
399
347
  elif nal_type == 8: # PPS
400
348
  # Only cache PPS if not yet locked
@@ -409,9 +357,7 @@ class ScrcpyStreamer:
409
357
  f"[ScrcpyStreamer] ✓ Cached PPS ({size} bytes, complete={is_complete}): {hex_preview}..."
410
358
  )
411
359
  elif size < 6:
412
- print(
413
- f"[ScrcpyStreamer] ✗ Skipped short PPS ({size} bytes)"
414
- )
360
+ print(f"[ScrcpyStreamer] ✗ Skipped short PPS ({size} bytes)")
415
361
 
416
362
  elif nal_type == 5: # IDR frame
417
363
  # Cache IDR if it's large enough (size check is sufficient)
@@ -422,9 +368,7 @@ class ScrcpyStreamer:
422
368
  is_first = self.cached_idr is None
423
369
  self.cached_idr = nal_data
424
370
  if is_first:
425
- print(
426
- f"[ScrcpyStreamer] ✓ Cached IDR frame ({size} bytes)"
427
- )
371
+ print(f"[ScrcpyStreamer] ✓ Cached IDR frame ({size} bytes)")
428
372
  # Don't log every IDR update (too verbose)
429
373
  elif size < 1024:
430
374
  print(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autoglm-gui
3
- Version: 0.4.8
3
+ Version: 0.4.9
4
4
  Summary: Web GUI for AutoGLM Phone Agent - AI-powered Android automation
5
5
  Project-URL: Homepage, https://github.com/suyiiyii/AutoGLM-GUI
6
6
  Project-URL: Repository, https://github.com/suyiiyii/AutoGLM-GUI
@@ -51,6 +51,9 @@ AutoGLM 手机助手的现代化 Web 图形界面 - 让 AI 自动化操作 Andro
51
51
  ### 任务执行完成
52
52
  ![任务结束](https://github.com/user-attachments/assets/b32f2e46-5340-42f5-a0db-0033729e1605)
53
53
 
54
+ ### 多设备控制
55
+ ![多设备控制](https://github.com/user-attachments/assets/f826736f-c41f-4d64-bf54-3ca65c69068d)
56
+
54
57
  ## 🚀 快速开始
55
58
 
56
59
  ## 🎯 模型服务配置
@@ -23,6 +23,9 @@ AutoGLM 手机助手的现代化 Web 图形界面 - 让 AI 自动化操作 Andro
23
23
  ### 任务执行完成
24
24
  ![任务结束](https://github.com/user-attachments/assets/b32f2e46-5340-42f5-a0db-0033729e1605)
25
25
 
26
+ ### 多设备控制
27
+ ![多设备控制](https://github.com/user-attachments/assets/f826736f-c41f-4d64-bf54-3ca65c69068d)
28
+
26
29
  ## 🚀 快速开始
27
30
 
28
31
  ## 🎯 模型服务配置
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "autoglm-gui"
3
- version = "0.4.8"
3
+ version = "0.4.9"
4
4
  description = "Web GUI for AutoGLM Phone Agent - AI-powered Android automation"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,9 +0,0 @@
1
- """AutoGLM-GUI package metadata."""
2
-
3
- from importlib import metadata
4
-
5
- # Expose package version at runtime; fall back to "unknown" during editable/dev runs
6
- try:
7
- __version__ = metadata.version("autoglm-gui")
8
- except metadata.PackageNotFoundError:
9
- __version__ = "unknown"
File without changes
File without changes