diff --git a/templates/admin/wall.html b/templates/admin/wall.html index 34ec3b7..e057783 100644 --- a/templates/admin/wall.html +++ b/templates/admin/wall.html @@ -391,14 +391,18 @@ function updateCtrlHead(){ document.getElementById('ctrl-sub').textContent=_ctrlSerial+(d.present?'':'(离线)'); } // 坐标映射:显示尺寸 -> 设备原生分辨率 +// 图片 object-fit:contain 时实际绘制区域小于盒子(居中留黑边), +// 必须按绘制区域换算,否则黑边/偏移导致点击错位 function ctrlCoords(ev){ const img=document.getElementById('ctrl-img'); - if(!img.naturalWidth)return[null,null]; + if(!img.naturalWidth||!img.naturalHeight)return[null,null]; const r=img.getBoundingClientRect(); - return[ - Math.round((ev.clientX-r.left)/r.width*img.naturalWidth), - Math.round((ev.clientY-r.top)/r.height*img.naturalHeight), - ]; + const scale=Math.min(r.width/img.naturalWidth,r.height/img.naturalHeight); + const drawW=img.naturalWidth*scale, drawH=img.naturalHeight*scale; + const ox=(r.width-drawW)/2, oy=(r.height-drawH)/2; // contain 偏移 + const px=ev.clientX-r.left, py=ev.clientY-r.top; + if(pxox+drawW||pyoy+drawH)return[null,null]; // 黑边点击忽略 + return[Math.round((px-ox)/scale),Math.round((py-oy)/scale)]; } function ctrlTap(x,y){ if(x==null||!x)return; diff --git a/web/monitor.py b/web/monitor.py index 653ec78..52f33c8 100644 --- a/web/monitor.py +++ b/web/monitor.py @@ -416,10 +416,34 @@ def api_screen_stream(): mimetype="multipart/x-mixed-replace; boundary=frame") +_screen_u2_cache = {} # serial -> (ts, device):大屏高频触控复用连接(60s TTL) + + def _screen_get_device(serial): - """远程看屏用的 u2 连接(连接失败抛异常由调用方转 503)。""" + """远程看屏用的 u2 连接(60s 缓存复用,避免每次 tap/swipe 都重建连接)。 + + 大屏高频操作(点击/滑动/按键)每次 u2.connect 有 1-3s 握手开销(atx-agent + 探测),体验卡顿。缓存 60s 内复用;连接失败(设备离线/重启)时清缓存重建, + 由调用方把异常转 503。 + """ + import time import uiautomator2 as u2 - return u2.connect(serial) + now = time.time() + cached = _screen_u2_cache.get(serial) + if cached and now - cached[0] < 60: + return cached[1] + d = u2.connect(serial) + _screen_u2_cache[serial] = (now, d) + if len(_screen_u2_cache) > 30: # 惰性清理过期项 + for k in list(_screen_u2_cache): + if now - _screen_u2_cache[k][0] >= 60: + del _screen_u2_cache[k] + return d + + +def _screen_invalidate(serial): + """u2 调用失败时清掉缓存(设备离线/atx-agent 重启),下次请求重新连接。""" + _screen_u2_cache.pop(serial, None) @bp.route("/api/screen/thumb") @perm_required(PERM_DEVICES) @@ -464,6 +488,7 @@ def api_screen_tap(): _screen_get_device(serial).click(int(x), int(y)) return jsonify({"ok": True}) except Exception as e: + _screen_invalidate(serial) return jsonify({"ok": False, "error": f"点击失败: {e}"}), 503 @bp.route("/api/screen/swipe", methods=["POST"]) @@ -480,6 +505,7 @@ def api_screen_swipe(): duration=float(data.get("duration", 0.2))) return jsonify({"ok": True}) except Exception as e: + _screen_invalidate(serial) return jsonify({"ok": False, "error": f"滑动失败: {e}"}), 503 @@ -498,6 +524,7 @@ def api_screen_key(): _screen_get_device(serial).press(key) return jsonify({"ok": True}) except Exception as e: + _screen_invalidate(serial) return jsonify({"ok": False, "error": f"按键失败: {e}"}), 503 @bp.route("/api/screen/text", methods=["POST"]) @@ -528,8 +555,32 @@ def _current_focus_pkg(serial): except Exception: return "" +_screen_u2_cache = {} # serial -> (ts, device):大屏高频触控复用连接(60s TTL) + + def _screen_get_device(serial): - """远程看屏用的 u2 连接(连接失败抛异常由调用方转 503)。""" + """远程看屏用的 u2 连接(60s 缓存复用,避免每次 tap/swipe 都重建连接)。 + + 大屏高频操作(点击/滑动/按键)每次 u2.connect 有 1-3s 握手开销(atx-agent + 探测),体验卡顿。缓存 60s 内复用;连接失败(设备离线/重启)时清缓存重建, + 由调用方把异常转 503。 + """ + import time import uiautomator2 as u2 - return u2.connect(serial) + now = time.time() + cached = _screen_u2_cache.get(serial) + if cached and now - cached[0] < 60: + return cached[1] + d = u2.connect(serial) + _screen_u2_cache[serial] = (now, d) + if len(_screen_u2_cache) > 30: # 惰性清理过期项 + for k in list(_screen_u2_cache): + if now - _screen_u2_cache[k][0] >= 60: + del _screen_u2_cache[k] + return d + + +def _screen_invalidate(serial): + """u2 调用失败时清掉缓存(设备离线/atx-agent 重启),下次请求重新连接。""" + _screen_u2_cache.pop(serial, None)