2.3.0:手表界面精简 + 播放器 + 鼠标重做

手表端
- 功能页从 10 个杂项精简到 7 个(屏幕/鼠标/媒体/文件/电脑/语音/电源)
- 删掉:游戏封锁、待办、掷骰子、抽奖、壁纸、显示器开关、定时关机、
  剪贴板历史、性能/磁盘/电量、文件夹浏览、教室限制等
- 改成显式列按钮,不再把电脑端所有功能组无脑铺到表盘
- 远程鼠标控制重做:滑动区 + 左键/滚轮/右键三按钮
  滚轮点按=中键、上下滑=滚动
- 新增视频/音乐播放器(手表本地播放):进度拖动、左右滑快进退10秒、
  点画面播放暂停、上一/下一、播完自动下一首

电脑端
- 新增 kill_all(结束所有进程),白名单保护系统关键进程和自身
- 新增 media_list / media_pull(播放器数据源)
  安全边界:只允许媒体目录,用 relative_to 判断,防同前缀兄弟目录绕过

中转站
- known_devices 从 ID 集合改成「设备ID -> 配对码」映射
- 修复:管理员改码后关机超过 6 小时,改码会被超时清理冲掉
- 老格式 state 文件平滑升级
This commit is contained in:
xmz7891
2026-10-18 11:39:14 +08:00
parent 1d368979aa
commit 2061f9525f
50 changed files with 13812 additions and 584 deletions
+94 -27
View File
@@ -60,7 +60,7 @@ from collections import deque
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
VERSION = "2.2.0"
VERSION = "2.3.0"
# ---------------- 参数(都能用环境变量覆盖)----------------
@@ -603,10 +603,23 @@ class Relay:
self.retired: dict = {}
# 是否允许新设备接入。False 时只有已登记过的 deviceId 能上线。
self.allow_new = True
# 设备历史名单(只有 deviceId,不含配对码)。
# 设备历史:**deviceId -> 配对码**。
#
# 为什么是字典而不是集合(2.2.1 改):
# 集合只能记住"这设备来过",记不住"它该用哪个码"。
# 于是这条链会断:
# ① 管理员在后台把码 111111 改成 222222
# ② 那台电脑关机(或断网)超过 DEVICE_TTL(6 小时)
# ③ snapshot() 把设备删了,孤儿的码 222222 也被清掉
# ④ 电脑开机,报自己本地的旧码 111111
# ⑤ 中转已经不记得改过码 -> 收下 111111
# ⑥ **管理员的改码白做了**,手表手里是 222222,连不上
# 把码记进历史后,步骤 ⑤ 会变成"你应该是 222222",
# 并随 register() 的返回值告诉客户端,客户端写回本地设置。
#
# **超时清理绝不动它** —— 它是"这台设备曾经被允许过"的唯一凭证。
# 详见 register() 里 known_device 的注释。
self.known_devices: set = set()
self.known_devices: dict = {}
self.started = time.time()
self.total_requests = 0
self.state_file = DATA_DIR / "relay_state.json"
@@ -621,11 +634,25 @@ class Relay:
return
# 是否允许新设备接入(重启不丢)
self.allow_new = bool(raw.get("allowNew", True))
# 恢复设备历史名单(重启不丢)
for did in raw.get("knownDevices", []):
did = str(did or "").strip()
if did:
self.known_devices.add(did)
# 恢复设备历史(重启不丢)。
#
# ⚠ 兼容老格式:2.2.1 之前存的是**纯 ID 列表**
# (`"knownDevices": ["abc", "def"]`),
# 2.2.1 起是**字典**(`"knownDevices": {"abc": "123456"}`)。
# 老格式加载时码记成空串 —— 表示"认识这台设备,但不知道它该用哪个码",
# 下次它上报什么就用什么(跟改动之前的行为一致)。
raw_known = raw.get("knownDevices") or {}
if isinstance(raw_known, dict):
for did, code in raw_known.items():
did = str(did or "").strip()
if did:
self.known_devices[did] = str(code or "").strip()
elif isinstance(raw_known, list):
# 老格式:只有 ID
for did in raw_known:
did = str(did or "").strip()
if did:
self.known_devices[did] = ""
for item in raw.get("codes", []):
device_id = str(item.get("deviceId", ""))
code = str(item.get("code", ""))
@@ -639,8 +666,9 @@ class Relay:
payload = {
"allowNew": bool(self.allow_new),
# 设备历史:超时清理不会删它,所以关掉接入开关后
# 老设备第二天开机仍然能上线
"knownDevices": sorted(self.known_devices),
# 老设备第二天开机仍然能上线。
# 2.2.1 起顺便记住"它该用哪个码",避免改码被超时清理冲掉。
"knownDevices": dict(self.known_devices),
"codes": [
{"deviceId": did, "code": code}
for code, did in sorted(self.codes.items())
@@ -731,16 +759,45 @@ class Relay:
"codeTaken": True,
"error": "该配对码已被使用,请换一个配对码"}
# 这台设备以前配过码吗?
old_code = None
for existing_code, existing_device in self.codes.items():
if existing_device == device_id:
old_code = existing_code
break
# 这台设备该用哪个配对码?**三个来源,按可信度排**:
#
# ① known_devices[deviceId] —— 设备历史里记着的(最权威)
# 管理员在后台改过码 -> set_device_code 会写进这里,
# 所以哪怕设备超时被清理、关机过夜,改码也不会丢。
# ② self.codes 里的现存绑定 —— 设备在线期间的有效映射
# ③ 本次上报的 code —— 前两者都没有时就用它
#
# ⚠ 不能只看 ②:snapshot() 会把「设备不存在」的码当孤儿清掉,
# 关机 >6 小时就必然触发 —— 那时 ② 是空的,改码就丢了。
reported_code = str(body.get("code") or "").strip()
authoritative = (self.known_devices.get(device_id) or "").strip()
if not authoritative:
for existing_code, existing_device in self.codes.items():
if existing_device == device_id:
authoritative = existing_code
break
if not authoritative:
authoritative = reported_code
if old_code and old_code != code:
log(f"设备 {name} 报了新码 {code},按单设备单码制改回 {old_code}")
code = old_code
# 权威码要是被**别的**设备占着,就不能硬用(否则两台设备抢一个码)。
# 这种情况只在"管理员改码后、旧码又被别的设备抢注"时出现,
# 属于异常,以本次上报为准,并留一条日志方便排查。
owner = self.codes.get(authoritative)
if owner and owner != device_id:
log(f"设备 {name} 的历史码 {authoritative} 已被 {owner} 占用,"
f"改用本次上报的 {reported_code}")
authoritative = reported_code
# 重新检查上报的码有没有冲突(上面 known 已经查过,
# 但 authoritative 换成 reported_code 后要再确认一次)
owner2 = self.codes.get(authoritative)
if owner2 and owner2 != device_id:
return {"ok": False, "codeTaken": True,
"error": "该配对码已被使用,请换一个配对码"}
code = authoritative
if code != reported_code:
log(f"设备 {name} 报了码 {reported_code},"
f"按单设备单码制用回 {code}")
self.codes[code] = device_id
device = self.devices.get(device_id)
@@ -753,9 +810,10 @@ class Relay:
device.version = version
device.token = secrets.token_hex(24)
device.last_seen = time.time()
# 记进设备历史名单(这是"曾经被允许过"的凭证,只增不减;
# 只有管理员显式删除设备时才会被移除)
self.known_devices.add(device_id)
# 记进设备历史:deviceId -> 权威码。
# 这是"曾经被允许过"的凭证,也顺带记住它该用哪个码。
# **只有管理员显式删除设备/抹码时才会被移除或清空。**
self.known_devices[device_id] = code
# 每次登记都刷新局域网信息(IP 可能变,端口也可能改)
device.lan_ips = lan_ips
device.port = listen_port
@@ -763,8 +821,8 @@ class Relay:
log(f"被控端上线:{name} 码 {code} ({device_id[:12]}…)")
note = ""
if old_code and old_code != str(body.get("code") or "").strip():
note = (f"这台设备之前设过配对码 {old_code},"
if code != reported_code:
note = (f"这台设备之前设过配对码 {code},"
f"已按「单设备单配对码」沿用它")
return {"ok": True, "token": device.token, "code": code,
"note": note, "pollSeconds": POLL_SECONDS}
@@ -814,6 +872,10 @@ class Relay:
self.codes.pop(code, None)
self.codes[new_code] = device_id
device.code = new_code
# ⚠ 设备历史里记的码也要同步更新 ——
# 否则设备关机 >6 小时被清理后重新登记时,
# register() 会拿历史里的**旧码**把它改回去,改码就白做了。
self.known_devices[device_id] = new_code
# 会话绑在码上,旧会话作废,让手表重新配对。
#
@@ -1144,7 +1206,7 @@ class Relay:
# "删除设备"是**真正忘记这台设备** —— 连历史名单一起删。
# 不删的话,管理员点了删除、开关又关着,
# 那台设备下次来仍然被当成老设备放行,开关就形同虚设。
self.known_devices.discard(target.device_id)
self.known_devices.pop(target.device_id, None)
# 把它的码绑定一起删掉
gone_codes = [c for c, owner in list(self.codes.items())
if owner == target.device_id]
@@ -1169,8 +1231,10 @@ class Relay:
def forget(self, code: str) -> bool:
"""抹掉一个配对码绑定(让那台设备下次可以设新码)。
注意:**只抹码,不抹设备历史**。
抹码的语义是"这个码我不占了",不是"我不认识这台设备了"。
注意:**只抹码,不抹设备历史** ——
设备仍然是"认识的"(关了接入开关也能上线),
但**码要忘掉**,否则 register() 会把历史里的旧码又塞回去,
"抹码"就白做了。
"""
with self.lock:
device_id = self.codes.pop(code, None)
@@ -1179,6 +1243,9 @@ class Relay:
device = self.devices.get(device_id)
if device is not None:
self.devices.pop(device_id, None)
# 把历史里记的码清空(但保留设备 ID —— 设备还是认识的)
if device_id in self.known_devices:
self.known_devices[device_id] = ""
self._save()
return True