From 39af42e1b31985222979f2d5a0d8070927259b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=8D=9A=E6=96=87?= <1323515022@qq.com> Date: Sat, 31 Oct 2026 17:34:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(common):=20=E5=8D=8F=E8=AE=AE=20v4=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20MouseMoveRel=20=E7=9B=B8=E5=AF=B9=E9=BC=A0?= =?UTF-8?q?=E6=A0=87=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v4 新增 InputEvent::MouseMoveRel{dx,dy},单位为被控端桌面物理像素, 语义是「把远端指针从当前位置移动 (dx,dy)」,越界由被控端 clamp。 为什么必须升版本:bincode 按位置编码枚举判别符,新增变体会改变其后 变体的编号,旧端会静默错解成别的输入事件。v4 后旧 v3 端由 Auth 握手 显式拒绝(已用 v3 二进制实测:client=3, server=4)。 为什么单位是像素而不是归一化增量:AuthOk 已下发桌面宽高,主控端换算 一次即可;被控端拿到像素可直接与 GetCursorPos 同尺度相加,少一次取整。 单测锁住:版本号、负值往返(i16 全范围)、与绝对 MouseMove 不混淆。 --- common/src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 57a5867..55435df 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -19,7 +19,10 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// - v3:新增 `InputEvent::Text`(文本注入通道,UTF-16 code unit)。 /// `InputEvent` 因携带 `Vec` 不再是 `Copy`;三端必须同步升级, /// 旧端由 `Auth` 握手显式拒绝。 -pub const PROTO_VERSION: u16 = 3; +/// - v4:新增 `InputEvent::MouseMoveRel`(相对鼠标,单位=被控端桌面物理像素)。 +/// bincode 按位置编码枚举判别序号,新增变体会改变其后变体的编号, +/// 故同样必须三端同步;旧端由 `Auth` 握手显式拒绝。 +pub const PROTO_VERSION: u16 = 4; /// 控制通道消息(双向) #[derive(Debug, Clone, Serialize, Deserialize)] @@ -78,6 +81,25 @@ pub enum InputEvent { /// /// 日志纪律:中间层与注入端只允许打印 `units.len()`,**不得**打印用户输入内容。 Text { units: Vec }, + /// v4 新增:**相对**鼠标位移,单位是**被控端桌面的物理像素**(不是归一化值)。 + /// + /// 语义:把远端指针从**当前位置**移动 `(dx, dy)`,越界部分由被控端 clamp 到桌面内。 + /// 主控端只说「移动多少」,不说「去哪里」——远端指针位置始终是唯一权威, + /// 本机真鼠标同时移动也不会造成跳变。 + /// + /// 为什么单位是像素而不是归一化增量:`AuthOk` 已下发桌面 `width`/`height`, + /// 主控端换算一次即可;被控端拿到像素可直接与 `GetCursorPos` 同尺度相加, + /// 少一次取整(每多一次取整就多一个累积误差源)。 + /// + /// 为什么是 `i16`:单次触摸事件的位移远小于 32767;用定长整数让线格式保持紧凑。 + /// + /// **与 [`InputEvent::MouseMove`] 互斥**:绝对模式发 `MouseMove`,相对模式发本变体, + /// 由主控端的模式开关决定,同一会话内不应混用(混用不会出错,但手感会跳)。 + /// + /// 注入实现注意:Windows 上**不能**直接 `SendInput(MOUSEEVENTF_MOVE)` 透传 + /// ——实测该路径被「指针加速」与「DPI 缩放」双重放大(本机 dx=100 实际走了 309px, + /// 且倍率随系统设置变化)。正确做法见 `agent/src/input.rs::send_mouse_rel`。 + MouseMoveRel { dx: i16, dy: i16 }, } /// [`InputEvent::Text`] 单次携带的 code unit 上限。 @@ -313,10 +335,58 @@ mod tests { // ---- v3 文本注入通道 ---- - /// 版本守卫:v3 是本轮契约变更的登记点,改动必须显式经过这里。 + /// 版本守卫:v3 是文本通道的登记点。v4 之后此断言随版本推进, + /// 保留它的作用是「任何一次版本变更都必须显式经过这里」。 #[test] fn proto_version_is_three_after_text_event() { - assert_eq!(PROTO_VERSION, 3, "新增 InputEvent::Text 后必须递增到 3"); + assert!( + PROTO_VERSION >= 3, + "Text 通道自 v3 起存在,版本不得回退(当前 {PROTO_VERSION})" + ); + } + + // ---- v4 相对鼠标 ---- + + /// 版本守卫:v4 是相对鼠标的登记点。 + #[test] + fn proto_version_is_four_after_rel_move() { + assert_eq!( + PROTO_VERSION, 4, + "新增 InputEvent::MouseMoveRel 后必须递增到 4" + ); + } + + /// 相对位移必须原样往返,**包含负值**(向左/向上移动)。 + /// 负值是本变体的核心语义:绝对坐标是无符号的,相对增量必须有符号。 + #[test] + fn mouse_move_rel_roundtrips_signed_deltas() { + for (dx, dy) in [(0i16, 0i16), (1, -1), (-32768, 32767), (100, -250)] { + let m = ControlMsg::Input(InputEvent::MouseMoveRel { dx, dy }); + let bytes = encode_control(&m); + let back: ControlMsg = bincode::deserialize(&bytes[4..]).unwrap(); + match back { + ControlMsg::Input(InputEvent::MouseMoveRel { dx: gx, dy: gy }) => { + assert_eq!((gx, gy), (dx, dy), "相对增量往返失真"); + } + other => panic!("期望 Input::MouseMoveRel,实际 {other:?}"), + } + } + } + + /// 判别序号守卫:`MouseMoveRel` 必须与既有变体**互不混淆**。 + /// bincode 按位置编码枚举判别符,若新变体插在中间会让旧端静默错解—— + /// 这条测试锁住「新增变体后各变体仍各自往返」。 + #[test] + fn rel_move_does_not_alias_other_input_events() { + let rel = ControlMsg::Input(InputEvent::MouseMoveRel { dx: 7, dy: -9 }); + let abs = ControlMsg::Input(InputEvent::MouseMove { x: 7, y: 9 }); + let rb = encode_control(&rel); + let ab = encode_control(&abs); + assert_ne!(rb, ab, "相对与绝对移动的线格式不得相同"); + match bincode::deserialize::(&rb[4..]).unwrap() { + ControlMsg::Input(InputEvent::MouseMoveRel { .. }) => {} + other => panic!("相对移动被错解为 {other:?}"), + } } #[test]