myagent-ai 1.47.29 → 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 +93 -3
- package/package.json +1 -1
- package/web/api_server.py +60 -0
package/core/vnc_manager.py
CHANGED
|
@@ -55,7 +55,9 @@ logger = get_logger("myagent.vnc")
|
|
|
55
55
|
# ── 默认配置 ──────────────────────────────────────────────
|
|
56
56
|
|
|
57
57
|
DEFAULT_DISPLAY = ":99"
|
|
58
|
-
DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机)
|
|
58
|
+
DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机),连接后由客户端视口自适应
|
|
59
|
+
|
|
60
|
+
|
|
59
61
|
DEFAULT_X11VNC_PORT = 5900
|
|
60
62
|
DEFAULT_NOVNC_PORT = 6080 # WebSocket 代理端口(供 noVNC 客户端连接)
|
|
61
63
|
|
|
@@ -75,11 +77,24 @@ class VNCManager:
|
|
|
75
77
|
def __init__(
|
|
76
78
|
self,
|
|
77
79
|
display: str = DEFAULT_DISPLAY,
|
|
78
|
-
resolution: str =
|
|
80
|
+
resolution: str = "",
|
|
79
81
|
x11vnc_port: int = DEFAULT_X11VNC_PORT,
|
|
80
82
|
novnc_port: int = DEFAULT_NOVNC_PORT,
|
|
81
83
|
):
|
|
82
84
|
self.display = display
|
|
85
|
+
# [v1.47.30] 初始分辨率 — 连接后由客户端视口自适应调整
|
|
86
|
+
# 也可通过 MYAGENT_SCREEN 环境变量手动指定初始分辨率(格式 "1097x550")
|
|
87
|
+
if not resolution:
|
|
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
|
|
96
|
+
else:
|
|
97
|
+
resolution = DEFAULT_XVFB_RESOLUTION
|
|
83
98
|
self.resolution = resolution
|
|
84
99
|
self.x11vnc_port = x11vnc_port
|
|
85
100
|
self.novnc_port = novnc_port
|
|
@@ -639,6 +654,71 @@ class VNCManager:
|
|
|
639
654
|
except Exception:
|
|
640
655
|
pass
|
|
641
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
|
+
|
|
642
722
|
def ensure_dependencies(self) -> tuple:
|
|
643
723
|
"""检测并安装 VNC 所需依赖。
|
|
644
724
|
|
|
@@ -5015,9 +5095,19 @@ exec "$BROWSER" "$@"
|
|
|
5015
5095
|
"-nowcr", # 禁用 cursor shape updates
|
|
5016
5096
|
"-nocursorshape",
|
|
5017
5097
|
"-deferupdate", "5", # 延迟更新(降低带宽)
|
|
5018
|
-
"-scale", "2/3", # 缩小 2/3(降低带宽)
|
|
5019
5098
|
]
|
|
5020
5099
|
|
|
5100
|
+
# [v1.47.29] VNC 缩放策略:分辨率匹配屏幕时不缩放,大分辨率缩小 2/3
|
|
5101
|
+
try:
|
|
5102
|
+
res_parts = self.resolution.split("x")
|
|
5103
|
+
res_w, res_h = int(res_parts[0]), int(res_parts[1])
|
|
5104
|
+
if res_w * res_h > 1600 * 900:
|
|
5105
|
+
# 大分辨率(如 1080x1920)→ 缩小 2/3 降低带宽
|
|
5106
|
+
cmd.extend(["-scale", "2/3"])
|
|
5107
|
+
# else: 小/匹配分辨率 → 不缩放,保持清晰
|
|
5108
|
+
except (ValueError, IndexError):
|
|
5109
|
+
cmd.extend(["-scale", "2/3"])
|
|
5110
|
+
|
|
5021
5111
|
# [v1.18.5] 不使用 -threads 模式:与 -scale 组合在 0.9.17 中
|
|
5022
5112
|
# 会导致父进程 fork 后立即退出,端口延迟监听
|
|
5023
5113
|
|
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
|
}};
|