# -*- coding: utf-8 -*- r'''把「远程鼠标控制」和「一键显示桌面」接进协议 + 服务端。 == 为什么这两个一起做 == 它们共用同一个底座(lanremote/input.py 的 SendInput 封装)。 分开做的话底座要写两遍。 {"cmd":"mouse","action":"move","x":800,"y":450} {"cmd":"mouse","action":"click","button":"left"} {"cmd":"mouse","action":"click","button":"right","double":true} {"cmd":"mouse","action":"down","button":"left"} {"cmd":"mouse","action":"up","button":"left"} {"cmd":"mouse","action":"scroll","amount":3} (正数向上) {"cmd":"mouse","action":"scroll","amount":-3,"horizontal":true} {"cmd":"show_desktop"} -> Win+D,用户要求的快捷栏固定按钮 == 为什么 move 用绝对坐标而不是"相对位移" == 手表上那块的触摸位置和电脑屏幕不是 1:1 的(表盘才多大)。 相对位移要手表自己算倍率,算错了鼠标就飘。 绝对坐标由手表把触摸点按比例映射到 [0,宽]x[0,高], 电脑端只管落到那个像素 —— 责任清晰,也不会累积漂移。 == 安全 == 注入鼠标键盘的能力很强,所以**必须登录配对之后才能用** —— 这一点由现有的握手逻辑保证(没通过配对,一条命令都不执行, 见 _do_pair 和命令分发最前面那道校验)。这里不做额外放松。 ''' from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent PROTO = ROOT / "windows" / "lanremote" / "protocol.py" SERVER = ROOT / "windows" / "lanremote" / "server.py" PROTO_ADD = '''CMD_UPDATE = "update" # 远程鼠标控制:{"action":"move|click|down|up|scroll", ...} CMD_MOUSE = "mouse" # 一键显示桌面(Win+D)。用户要求的快捷栏固定按钮。 CMD_SHOW_DESKTOP = "show_desktop"''' def main() -> int: # ---- protocol.py ---- raw = PROTO.read_bytes() bom = raw.startswith(b"\xef\xbb\xbf") text = raw.decode("utf-8-sig") if "CMD_MOUSE" in text: print(" protocol.py 已经加过了") else: old = 'CMD_UPDATE = "update"' if old not in text: print(" ✗ protocol.py 找不到 CMD_UPDATE") return 1 text = text.replace(old, PROTO_ADD, 1) PROTO.write_bytes((b"\xef\xbb\xbf" if bom else b"") + text.encode("utf-8")) print(" ✓ protocol.py 加了 CMD_MOUSE / CMD_SHOW_DESKTOP") # ---- server.py ---- raw = SERVER.read_bytes() bom = raw.startswith(b"\xef\xbb\xbf") text = raw.decode("utf-8-sig") if "_do_mouse" in text: print(" server.py 已经加过了") return 0 anchors = { "imports": "from . import updater as U", "update_branch": " if cmd == P.CMD_UPDATE:\n return self._do_update(msg)", "unknown": ' return {"ok": False, "error": f"未知命令: {cmd}"}', } missing = [k for k, v in anchors.items() if v not in text] if missing: print(" ✗ server.py 这些锚点找不到:%s" % ", ".join(missing)) return 1 print(" ✓ server.py 锚点全部就位") text = text.replace(anchors["imports"], anchors["imports"] + "\nfrom . import input as IN", 1) text = text.replace( anchors["update_branch"], anchors["update_branch"] + ''' if cmd == P.CMD_MOUSE: return self._do_mouse(msg) if cmd == P.CMD_SHOW_DESKTOP: try: IN.show_desktop() except IN.InputError as exc: return {"ok": False, "error": str(exc)} return {"ok": True, "done": "show_desktop"}''', 1) handler = ''' # ---------------- 远程鼠标 / 键盘注入 ---------------- def _do_mouse(self, msg: dict) -> dict: """处理 {"cmd":"mouse","action":...}。 坐标系是**屏幕像素**(相对虚拟屏幕左上角),由手表按比例映射好 再发过来 —— 电脑端不做缩放猜测,免得两边算法不一致导致漂移。 """ action = str(msg.get("action") or "").lower() try: if action == "move": # 分辨率可能被改过(投屏切换等),每次移动前刷一下缓存。 # 不刷的话,插上投影仪之后鼠标会跑到屏幕外。 IN.refresh_screen() IN.move_to(int(msg.get("x", 0)), int(msg.get("y", 0))) return {"ok": True} if action == "moveby": IN.move_by(int(msg.get("dx", 0)), int(msg.get("dy", 0))) return {"ok": True} if action == "click": IN.click(str(msg.get("button") or "left"), double=bool(msg.get("double"))) return {"ok": True} if action == "down": IN.down(str(msg.get("button") or "left")) return {"ok": True} if action == "up": IN.up(str(msg.get("button") or "left")) return {"ok": True} if action == "scroll": IN.scroll(int(msg.get("amount", 0)), horizontal=bool(msg.get("horizontal"))) return {"ok": True} if action == "desktop": IN.show_desktop() return {"ok": True} return {"ok": False, "error": "不认识的鼠标动作:%s" % action} except IN.InputError as exc: return {"ok": False, "error": str(exc)} except (TypeError, ValueError) as exc: return {"ok": False, "error": "参数不对:%s" % exc} ''' text = text.replace(anchors["unknown"], handler + "\n" + anchors["unknown"], 1) SERVER.write_bytes((b"\xef\xbb\xbf" if bom else b"") + text.encode("utf-8")) print(" ✓ server.py 加了 _do_mouse") return 0 if __name__ == "__main__": sys.exit(main())