mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 18:20:43 +00:00
被控端 - 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%) - 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败) - 每分钟回头找局域网,自动切回快 400 倍的路径 - 画面心跳帧:静止时省流量,客户端仍知道流是活的 - 锁屏时给出可操作的提示 手表端 - 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException - 连接错误翻译成人话(证书/时间/解析/超时分别提示) - 经中转连上后用服务器报的地址刷新已配对记录 - 新增局域网连接页面、服务器延迟检测 服务器端 - 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502) - 网络调优脚本 net-tune.sh(开机自动应用) - 管理页:批量删除、网络体检、下载区 - 隧道改 HTTP/2 + 双连接 文档 - 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
314 lines
12 KiB
Python
314 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""任务 A:放行防火墙之前,配对码不加入服务器。
|
||
任务 B:性能提升。
|
||
|
||
────────────────────────────────────────
|
||
A. 配对码门禁
|
||
────────────────────────────────────────
|
||
用户要求:"在放行防火墙之前配对码不会加入服务器"
|
||
|
||
现在的顺序:
|
||
检查码(只读) -> 存码 -> 开自启 -> 放行防火墙 -> run_silent(注册)
|
||
虽然注册在防火墙之后,但**防火墙失败也照样注册** ——
|
||
服务器上就挂着一个谁也连不上的配对码。
|
||
|
||
改成:
|
||
防火墙没放行成功 -> 不注册,告诉用户
|
||
|
||
────────────────────────────────────────
|
||
B. 性能
|
||
────────────────────────────────────────
|
||
实测(本机 1920x1080):
|
||
抓屏 ImageGrab.grab() 33 ms <- 硬开销,决定了上限
|
||
缩放 + JPEG 编码 17 ms (BILINEAR, 960 宽)
|
||
合计 50 ms -> 约 20 帧/秒
|
||
|
||
问题:
|
||
1. screen_fps 默认只有 3 —— 白白浪费了 20 帧的能力
|
||
2. screen_quality 默认 55 —— 偏糊
|
||
3. **画面没变也照抓照编码照发** —— 手表看静态桌面时白烧 CPU 和流量
|
||
|
||
改动:
|
||
1. 默认帧率 3 -> 12
|
||
2. 默认质量 55 -> 72
|
||
3. 加"画面没变就跳过":用 32x18 缩略图做指纹(很便宜),
|
||
指纹一样就不编码、不发送,并把采样间隔逐级拉长(最多 4 倍),
|
||
画面一变立刻恢复满帧率。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
||
RUN = ROOT / "windows" / "run_agent.py"
|
||
CONFIG = ROOT / "windows" / "lanremote" / "config.py"
|
||
SCREEN = ROOT / "windows" / "lanremote" / "screen.py"
|
||
|
||
# ================= A. 配对码门禁 =================
|
||
|
||
A_OLD = ''' # 黑终端一闪:修防火墙
|
||
if result["firewall"]:
|
||
fix_firewall_with_flash(store)
|
||
'''
|
||
|
||
A_NEW = ''' # 黑终端一闪:修防火墙
|
||
firewall_ok = True
|
||
firewall_note = ""
|
||
if result["firewall"]:
|
||
firewall_ok, firewall_note = fix_firewall_with_flash(store)
|
||
|
||
# 放行防火墙之前,配对码**不加入服务器**。
|
||
#
|
||
# 为什么:注册(= 配对码挂到服务器上)如果发生在防火墙之前,
|
||
# 那么防火墙没放行成功时,服务器上会留着一个谁也连不上的码,
|
||
# 而且它会把码占住,用户重装都换不了。
|
||
# 所以这里卡一道:防火墙没成功就不注册。
|
||
if not firewall_ok:
|
||
_tell_firewall_problem(firewall_note)
|
||
return 3
|
||
'''
|
||
|
||
HELPER_ANCHOR = "def _tell_code_problem(message: str) -> None:"
|
||
|
||
HELPER_NEW = '''def _tell_firewall_problem(detail: str) -> None:
|
||
"""防火墙没放行成功,说明为什么这次没生效。"""
|
||
message = (
|
||
"防火墙没有放行成功,所以这次**没有**把配对码交给服务器。\\n\\n"
|
||
f"原因:{detail or '未知'}\\n\\n"
|
||
"该怎么办:\\n"
|
||
" 1. 右键点 ykxmz.exe →「以管理员身份运行」,重新走一遍设置\\n"
|
||
" 2. 或者手动放行:控制面板 → Windows Defender 防火墙 →\\n"
|
||
" 允许应用通过防火墙 → 找到 ykxmz → 勾上「专用网络」\\n\\n"
|
||
"放行之后再打开 ykxmz.exe 就行了。"
|
||
)
|
||
try:
|
||
import tkinter as tk
|
||
from tkinter import messagebox
|
||
root = tk.Tk()
|
||
root.withdraw()
|
||
root.attributes("-topmost", True)
|
||
messagebox.showerror("还没生效:防火墙没放行", message)
|
||
root.destroy()
|
||
return
|
||
except Exception: # noqa: BLE001
|
||
pass
|
||
print("=" * 56)
|
||
print("防火墙没放行,配对码未提交到服务器")
|
||
print(message)
|
||
print("=" * 56)
|
||
|
||
|
||
''' + HELPER_ANCHOR
|
||
|
||
# ================= B1. 默认参数 =================
|
||
|
||
CFG_OLD = ''' "screen_quality": 55, # 镜像 JPEG 质量
|
||
"screen_fps": 3, # 默认帧率'''
|
||
|
||
CFG_NEW = ''' # 镜像 JPEG 质量。
|
||
# 实测质量对编码耗时几乎没影响(都是 ~17ms),主要影响体积:
|
||
# 55 -> 30KB/帧 72 -> 45KB/帧 85 -> 70KB/帧
|
||
# 取 72:画质明显更清楚,局域网完全带得动。
|
||
"screen_quality": 72,
|
||
# 默认帧率。
|
||
# 实测抓屏 33ms + 编码 17ms = 50ms,也就是**硬件上限约 20 帧/秒**。
|
||
# 原来写 3 是白白浪费了 6 倍的能力。
|
||
# 取 12:够顺,又给抓屏留了余量,不至于把 CPU 占满。
|
||
# (画面静止时会自动降频,见 screen.py 的 _loop)
|
||
"screen_fps": 12,'''
|
||
|
||
# ================= B2. 抓屏去重 =================
|
||
|
||
GRAB_OLD = ''' width, height = image.size
|
||
if self.max_width and width > self.max_width:
|
||
ratio = self.max_width / float(width)
|
||
new_size = (self.max_width, max(1, int(height * ratio)))
|
||
image = image.resize(new_size, Image.BILINEAR)
|
||
width, height = image.size
|
||
|
||
buffer = io.BytesIO()
|
||
try:
|
||
image.save(buffer, format="JPEG", quality=self.quality,
|
||
optimize=False, subsampling=2)
|
||
except Exception as exc: # noqa: BLE001
|
||
raise ScreenError(f"JPEG 编码失败: {exc}") from exc
|
||
|
||
self._last_size = (width, height)
|
||
return buffer.getvalue(), width, height'''
|
||
|
||
GRAB_NEW = ''' width, height = image.size
|
||
if self.max_width and width > self.max_width:
|
||
ratio = self.max_width / float(width)
|
||
new_size = (self.max_width, max(1, int(height * ratio)))
|
||
image = image.resize(new_size, Image.BILINEAR)
|
||
width, height = image.size
|
||
|
||
# 画面没变就不编码。
|
||
# 用 32x18 的小缩略图做指纹:算一次只要 0.1ms 左右,
|
||
# 但能省掉 17ms 的 JPEG 编码和之后的传输。
|
||
# 手表看静态桌面是最常见的情况,这一条省得最多。
|
||
digest = None
|
||
if self.dedup:
|
||
try:
|
||
thumb = image.resize((32, 18), Image.BILINEAR)
|
||
digest = hash(thumb.tobytes())
|
||
except Exception: # noqa: BLE001
|
||
digest = None
|
||
if digest is not None and digest == self._last_digest:
|
||
self._last_size = (width, height)
|
||
return b"", width, height
|
||
self._last_digest = digest
|
||
|
||
buffer = io.BytesIO()
|
||
try:
|
||
image.save(buffer, format="JPEG", quality=self.quality,
|
||
optimize=False, subsampling=2)
|
||
except Exception as exc: # noqa: BLE001
|
||
raise ScreenError(f"JPEG 编码失败: {exc}") from exc
|
||
|
||
self._last_size = (width, height)
|
||
return buffer.getvalue(), width, height'''
|
||
|
||
INIT_OLD = ''' def __init__(self, max_width: int = 480, quality: int = 55, monitor: int = 1):
|
||
self.max_width = int(max_width)
|
||
self.quality = int(quality)
|
||
self.monitor = int(monitor)
|
||
self._lock = threading.RLock()
|
||
self._last_size = (0, 0)'''
|
||
|
||
INIT_NEW = ''' def __init__(self, max_width: int = 960, quality: int = 72, monitor: int = 1,
|
||
dedup: bool = True):
|
||
self.max_width = int(max_width)
|
||
self.quality = int(quality)
|
||
self.monitor = int(monitor)
|
||
# 画面没变就不重复编码(见 grab_jpeg)
|
||
self.dedup = bool(dedup)
|
||
self._lock = threading.RLock()
|
||
self._last_size = (0, 0)
|
||
self._last_digest = None'''
|
||
|
||
# ================= B3. 静态降频 =================
|
||
|
||
LOOP_OLD = ''' def _loop(self) -> None:
|
||
interval = 1.0 / self.fps
|
||
while not self._stop.is_set():
|
||
started = time.time()
|
||
try:
|
||
jpeg, width, height = self.capturer.grab_jpeg()
|
||
except ScreenError as exc:
|
||
self.last_error = str(exc)
|
||
time.sleep(0.5)
|
||
continue
|
||
self.frames_sent += 1
|
||
try:
|
||
if self.on_frame(jpeg, width, height) is False:
|
||
break # 没有观看者了,自动结束
|
||
except Exception as exc: # noqa: BLE001
|
||
self.last_error = str(exc)
|
||
break
|
||
elapsed = time.time() - started
|
||
wait = interval - elapsed
|
||
if wait > 0:
|
||
self._stop.wait(wait)'''
|
||
|
||
LOOP_NEW = ''' def _loop(self) -> None:
|
||
interval = 1.0 / self.fps
|
||
# 画面静止时把采样间隔逐级拉长(最多 4 倍),省 CPU。
|
||
# 抓屏本身就要 30ms 左右,静止时没必要一直满速抓。
|
||
idle = 1.0
|
||
while not self._stop.is_set():
|
||
started = time.time()
|
||
try:
|
||
jpeg, width, height = self.capturer.grab_jpeg()
|
||
except ScreenError as exc:
|
||
self.last_error = str(exc)
|
||
time.sleep(0.5)
|
||
continue
|
||
|
||
if not jpeg:
|
||
# 画面没变(capturer 做了去重):不发送,间隔拉长一点再抓。
|
||
idle = min(4.0, idle * 1.6)
|
||
self._stop.wait(interval * idle)
|
||
continue
|
||
|
||
idle = 1.0
|
||
self.frames_sent += 1
|
||
try:
|
||
if self.on_frame(jpeg, width, height) is False:
|
||
break # 没有观看者了,自动结束
|
||
except Exception as exc: # noqa: BLE001
|
||
self.last_error = str(exc)
|
||
break
|
||
elapsed = time.time() - started
|
||
wait = interval - elapsed
|
||
if wait > 0:
|
||
self._stop.wait(wait)'''
|
||
|
||
# 帧率上限 15 -> 30
|
||
CAP_OLD = " self.fps = max(0.5, min(15.0, float(fps)))\n return"
|
||
CAP_NEW = " self.fps = max(0.5, min(30.0, float(fps)))\n return"
|
||
CAP_OLD2 = " self.fps = max(0.5, min(15.0, float(fps)))\n self._stop.clear()"
|
||
CAP_NEW2 = " self.fps = max(0.5, min(30.0, float(fps)))\n self._stop.clear()"
|
||
|
||
|
||
def apply(path: Path, pairs, label: str) -> bool:
|
||
text = path.read_text(encoding="utf-8")
|
||
ok = True
|
||
for old, new in pairs:
|
||
if old == new:
|
||
continue
|
||
if old in text:
|
||
text = text.replace(old, new, 1)
|
||
else:
|
||
print(f" [失败] {label}: 锚点没找到 -> {old.strip()[:60]}")
|
||
ok = False
|
||
path.write_text(text, encoding="utf-8", newline="\n")
|
||
return ok
|
||
|
||
|
||
def main() -> int:
|
||
ok = True
|
||
|
||
# A
|
||
text = RUN.read_text(encoding="utf-8")
|
||
if "_tell_firewall_problem" in text:
|
||
print(" A: 已经改过")
|
||
else:
|
||
ok &= apply(RUN, [(A_OLD, A_NEW), (HELPER_ANCHOR, HELPER_NEW)], "run_agent")
|
||
print(" A: 防火墙门禁已加")
|
||
|
||
# B1
|
||
text = CONFIG.read_text(encoding="utf-8")
|
||
if '"screen_fps": 12' in text:
|
||
print(" B1: 已经改过")
|
||
else:
|
||
ok &= apply(CONFIG, [(CFG_OLD, CFG_NEW)], "config")
|
||
print(" B1: 默认帧率 3->12,质量 55->72")
|
||
|
||
# B2 / B3
|
||
text = SCREEN.read_text(encoding="utf-8")
|
||
if "self._last_digest" in text:
|
||
print(" B2/B3: 已经改过")
|
||
else:
|
||
ok &= apply(SCREEN, [(INIT_OLD, INIT_NEW), (GRAB_OLD, GRAB_NEW),
|
||
(LOOP_OLD, LOOP_NEW), (CAP_OLD, CAP_NEW),
|
||
(CAP_OLD2, CAP_NEW2)], "screen")
|
||
print(" B2: 加了画面去重(指纹 32x18)")
|
||
print(" B3: 静止时自动降频(最多 4 倍),帧率上限 15->30")
|
||
|
||
import ast
|
||
for label, path in (("run_agent", RUN), ("config", CONFIG),
|
||
("screen", SCREEN)):
|
||
try:
|
||
ast.parse(path.read_text(encoding="utf-8"))
|
||
print(f" {label} 语法 OK")
|
||
except SyntaxError as exc:
|
||
print(f" {label} 语法错误: 行 {exc.lineno}: {exc.msg}")
|
||
ok = False
|
||
return 0 if ok else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|