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.
- package/core/vnc_manager.py +76 -72
- package/package.json +1 -1
- package/web/api_server.py +60 -0
package/core/vnc_manager.py
CHANGED
|
@@ -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.
|
|
85
|
+
# [v1.47.30] 初始分辨率 — 连接后由客户端视口自适应调整
|
|
86
|
+
# 也可通过 MYAGENT_SCREEN 环境变量手动指定初始分辨率(格式 "1097x550")
|
|
150
87
|
if not resolution:
|
|
151
|
-
|
|
152
|
-
if
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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.
|
|
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
|
}};
|