myagent-ai 1.47.30 → 1.47.31

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.
@@ -55,71 +55,7 @@ logger = get_logger("myagent.vnc")
55
55
  # ── 默认配置 ──────────────────────────────────────────────
56
56
 
57
57
  DEFAULT_DISPLAY = ":99"
58
- DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机),旋转切换到横屏
59
-
60
-
61
- def _detect_android_screen_size() -> Optional[tuple]:
62
- """[v1.47.29] 检测 Android 设备屏幕物理尺寸(宽x高,像素)。
63
-
64
- 优先级:
65
- 1. 环境变量 MYAGENT_SCREEN (用户手动指定,格式 "1097x550")
66
- 2. getprop ro.hardware.screen_width/height (部分 ROM)
67
- 3. /sys/class/graphics/fb0/virtual_size (内核帧缓冲区)
68
- 4. getprop qemu.sf.lcd_density + ro.sf.lcd_density 推算
69
-
70
- Returns:
71
- (width, height) 元组,检测失败返回 None
72
- """
73
- # 1. 环境变量
74
- env_screen = os.environ.get("MYAGENT_SCREEN", "").strip()
75
- if env_screen and "x" in env_screen:
76
- try:
77
- parts = env_screen.lower().split("x")
78
- w, h = int(parts[0]), int(parts[1])
79
- if w > 0 and h > 0:
80
- logger.info(f"屏幕尺寸来自环境变量 MYAGENT_SCREEN: {w}x{h}")
81
- return (w, h)
82
- except (ValueError, IndexError):
83
- pass
84
-
85
- # 2. getprop 属性(部分 ROM 有 ro.hardware.screen_width/height)
86
- try:
87
- result_w = subprocess.run(
88
- ["getprop", "ro.hardware.screen_width"],
89
- capture_output=True, text=True, timeout=3,
90
- )
91
- result_h = subprocess.run(
92
- ["getprop", "ro.hardware.screen_height"],
93
- capture_output=True, text=True, timeout=3,
94
- )
95
- if result_w.returncode == 0 and result_h.returncode == 0:
96
- sw = result_w.stdout.strip()
97
- sh = result_h.stdout.strip()
98
- if sw and sh and sw.isdigit() and sh.isdigit():
99
- w, h = int(sw), int(sh)
100
- if w > 0 and h > 0:
101
- logger.info(f"屏幕尺寸来自 getprop: {w}x{h}")
102
- return (w, h)
103
- except Exception:
104
- pass
105
-
106
- # 3. 内核帧缓冲区尺寸
107
- fb_path = "/sys/class/graphics/fb0/virtual_size"
108
- try:
109
- if os.path.exists(fb_path):
110
- with open(fb_path, "r") as f:
111
- content = f.read().strip()
112
- # 格式通常是 "1097,550"
113
- if "," in content:
114
- parts = content.split(",")
115
- w, h = int(parts[0]), int(parts[1])
116
- if w > 0 and h > 0:
117
- logger.info(f"屏幕尺寸来自帧缓冲区: {w}x{h}")
118
- return (w, h)
119
- except Exception:
120
- pass
121
-
122
- return None
58
+ DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机),连接后由客户端视口自适应
123
59
 
124
60
 
125
61
  DEFAULT_X11VNC_PORT = 5900
@@ -146,16 +82,19 @@ class VNCManager:
146
82
  novnc_port: int = DEFAULT_NOVNC_PORT,
147
83
  ):
148
84
  self.display = display
149
- # [v1.47.29] 自动检测 Android 屏幕尺寸设置 VNC 分辨率
85
+ # [v1.47.30] 初始分辨率 连接后由客户端视口自适应调整
86
+ # 也可通过 MYAGENT_SCREEN 环境变量手动指定初始分辨率(格式 "1097x550")
150
87
  if not resolution:
151
- screen_size = _detect_android_screen_size()
152
- if screen_size:
153
- w, h = screen_size
154
- resolution = f"{w}x{h}x24"
155
- logger.info(f"VNC 分辨率已适配设备屏幕: {w}x{h}")
88
+ env_screen = os.environ.get("MYAGENT_SCREEN", "").strip()
89
+ if env_screen and "x" in env_screen:
90
+ try:
91
+ parts = env_screen.lower().split("x")
92
+ resolution = f"{int(parts[0])}x{int(parts[1])}x24"
93
+ logger.info(f"VNC 初始分辨率来自环境变量: {resolution}")
94
+ except (ValueError, IndexError):
95
+ resolution = DEFAULT_XVFB_RESOLUTION
156
96
  else:
157
97
  resolution = DEFAULT_XVFB_RESOLUTION
158
- logger.info(f"未检测到设备屏幕尺寸,使用默认分辨率: {resolution}")
159
98
  self.resolution = resolution
160
99
  self.x11vnc_port = x11vnc_port
161
100
  self.novnc_port = novnc_port
@@ -715,6 +654,71 @@ class VNCManager:
715
654
  except Exception:
716
655
  pass
717
656
 
657
+ def resize_display(self, width: int, height: int) -> Dict[str, Any]:
658
+ """[v1.47.30] 自适应调整 VNC 分辨率 — 匹配客户端视口大小。
659
+
660
+ 当 noVNC 客户端视口尺寸变化时(手机旋转、窗口大小改变),
661
+ 通过 xrandr 动态调整 Xvfb 帧缓冲区大小,实现像素级清晰显示。
662
+
663
+ Args:
664
+ width: 目标宽度(像素)
665
+ height: 目标高度(像素)
666
+
667
+ Returns:
668
+ {"success": bool, "resolution": str, "error": str}
669
+ """
670
+ if not self.is_running:
671
+ return {"success": False, "error": "VNC未运行"}
672
+
673
+ # 参数校验:合理范围
674
+ if width < 320 or height < 240 or width > 3840 or height > 3840:
675
+ return {"success": False, "error": f"分辨率超出合理范围: {width}x{height}"}
676
+
677
+ # 解析当前分辨率
678
+ try:
679
+ parts = self.resolution.split("x")
680
+ cur_w, cur_h = int(parts[0]), int(parts[1])
681
+ depth = parts[2] if len(parts) > 2 else "24"
682
+ except (ValueError, IndexError):
683
+ return {"success": False, "error": f"无法解析当前分辨率: {self.resolution}"}
684
+
685
+ # 如果分辨率没变,无需操作
686
+ if cur_w == width and cur_h == height:
687
+ return {"success": True, "resolution": self.resolution}
688
+
689
+ new_resolution = f"{width}x{height}x{depth}"
690
+ env = {**os.environ, "DISPLAY": self.display}
691
+
692
+ # 尝试 xrandr --fb 在线调整
693
+ xrandr_ok = False
694
+ if shutil.which("xrandr"):
695
+ try:
696
+ result = subprocess.run(
697
+ ["xrandr", "--fb", f"{width}x{height}"],
698
+ capture_output=True, text=True, timeout=5,
699
+ env=env, start_new_session=True,
700
+ )
701
+ if result.returncode == 0:
702
+ xrandr_ok = True
703
+ logger.info(f"VNC 自适应分辨率: {cur_w}x{cur_h} → {width}x{height}")
704
+ else:
705
+ logger.debug(f"xrandr --fb 失败: {(result.stderr or '')[:100]}")
706
+ except Exception as e:
707
+ logger.debug(f"xrandr --fb 异常: {e}")
708
+
709
+ if xrandr_ok:
710
+ self.resolution = new_resolution
711
+ # 更新旋转状态
712
+ if width > height:
713
+ self._current_rotation = "landscape"
714
+ else:
715
+ self._current_rotation = "portrait"
716
+ # 通知窗口管理器
717
+ self._notify_wm_resize(width, height, env)
718
+ return {"success": True, "resolution": new_resolution}
719
+
720
+ return {"success": False, "error": "xrandr 不支持,无法动态调整分辨率"}
721
+
718
722
  def ensure_dependencies(self) -> tuple:
719
723
  """检测并安装 VNC 所需依赖。
720
724
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.30",
3
+ "version": "1.47.31",
4
4
  "description": "\u672c\u5730\u684c\u9762\u7aef\u6267\u884c\u578bAI\u52a9\u624b - Open Interpreter \u98ce\u683c | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -628,6 +628,7 @@ class ApiServer:
628
628
  r.add_post("/api/vnc/restart", self.handle_vnc_restart)
629
629
  r.add_get("/api/vnc/screenshot", self.handle_vnc_screenshot)
630
630
  r.add_post("/api/vnc/rotate", self.handle_vnc_rotate) # [v1.47.1] 屏幕旋转
631
+ r.add_post("/api/vnc/resize", self.handle_vnc_resize) # [v1.47.30] 自适应分辨率
631
632
  r.add_get("/vnc/{path:.*}", self.handle_novnc_proxy) # noVNC 客户端页面代理
632
633
  # [v1.20.8] VNC WebSocket 代理 — 通过主服务器转发 VNC WebSocket,解决 HTTPS/WSS 问题
633
634
  r.add_get("/api/vnc/ws", self.handle_vnc_websocket)
@@ -958,6 +959,20 @@ class ApiServer:
958
959
  result = mgr.rotate_display(direction)
959
960
  return web.json_response(result)
960
961
 
962
+ async def handle_vnc_resize(self, request):
963
+ """POST /api/vnc/resize - [v1.47.30] 自适应调整 VNC 分辨率匹配客户端视口"""
964
+ mgr = self._get_vnc_manager()
965
+ try:
966
+ data = await request.json()
967
+ except Exception:
968
+ data = {}
969
+ width = data.get("width", 0)
970
+ height = data.get("height", 0)
971
+ if not width or not height:
972
+ return web.json_response({"success": False, "error": "需要 width 和 height 参数"})
973
+ result = mgr.resize_display(int(width), int(height))
974
+ return web.json_response(result)
975
+
961
976
  async def handle_novnc_proxy(self, request):
962
977
  """GET /vnc/{path} - 代理 noVNC 客户端页面
963
978
 
@@ -1671,6 +1686,51 @@ function initVNC(RFBClass) {{
1671
1686
  window.rfb = rfb;
1672
1687
  window._vncConnect = connect;
1673
1688
  window._vncUseProxy = useProxy;
1689
+
1690
+ // [v1.47.30] 自适应分辨率 — 检测视口大小,通知后端调整 Xvfb 分辨率
1691
+ // 实现:客户端知道自己的视口尺寸,通过 /api/vnc/resize 告诉后端
1692
+ // 后端用 xrandr --fb 动态调整 Xvfb 帧缓冲区,实现像素级清晰
1693
+ var _lastResizeW = 0, _lastResizeH = 0, _resizeDebounce = null;
1694
+
1695
+ function reportViewportSize() {{
1696
+ // 计算实际可用区域(去掉工具栏和虚拟按键栏的高度)
1697
+ var screenDiv = document.getElementById('screen');
1698
+ if (!screenDiv) return;
1699
+ var w = screenDiv.clientWidth;
1700
+ var h = screenDiv.clientHeight;
1701
+ if (w < 320 || h < 240) return;
1702
+ // 避免微小变化频繁触发(差值 < 10px 忽略)
1703
+ if (_lastResizeW > 0 && Math.abs(w - _lastResizeW) < 10 && Math.abs(h - _lastResizeH) < 10) return;
1704
+ _lastResizeW = w;
1705
+ _lastResizeH = h;
1706
+ fetch('/api/vnc/resize', {{
1707
+ method: 'POST',
1708
+ headers: {{ 'Content-Type': 'application/json' }},
1709
+ body: JSON.stringify({{ width: w, height: h }})
1710
+ }}).then(function(r) {{ return r.json(); }})
1711
+ .then(function(data) {{
1712
+ if (data.success) {{
1713
+ var statusEl = document.getElementById('statusText');
1714
+ if (statusEl) statusEl.textContent = data.resolution || '';
1715
+ }}
1716
+ }}).catch(function() {{}});
1717
+ }}
1718
+
1719
+ // 监听窗口大小变化(手机旋转 = resize 事件)
1720
+ window.addEventListener('resize', function() {{
1721
+ if (_resizeDebounce) clearTimeout(_resizeDebounce);
1722
+ _resizeDebounce = setTimeout(reportViewportSize, 500);
1723
+ }});
1724
+
1725
+ // VNC 连接成功后立即上报一次视口大小
1726
+ // 监听 connect 事件(通过轮询 isConnected)
1727
+ var _resizeCheckInterval = setInterval(function() {{
1728
+ if (isConnected) {{
1729
+ reportViewportSize();
1730
+ clearInterval(_resizeCheckInterval);
1731
+ }}
1732
+ }}, 1000);
1733
+
1674
1734
  window.sendCtrlAltDel = function() {{
1675
1735
  if (rfb) rfb.sendCtrlAltDel();
1676
1736
  }};