feat(agent): 相对鼠标注入(读光标+增量+clamp 绝对注入)+ 显式 DPI 感知
注入实现刻意**不用** SendInput(MOUSEEVENTF_MOVE):实测该路径的 dx 不是 像素而是 mickey,会被「指针加速」与「DPI 缩放」双重放大——本机 dx=1 走 0px、 dx=20 走 41px、dx=100 走 309px,且倍率随用户系统设置与机器变化,无法标定。 改为「GetCursorPos + 增量 + clamp + 绝对注入」,实测 1:1 精确(±1/2/3/5/17/100 四次重复全部等于请求值),撞边后回滑立即响应、不积步数债。 同时显式设 PER_MONITOR_AWARE_V2:实测本机 agent.exe 虽是 per-monitor, 但那是注册表 AppCompatFlags\\Layers 里一条**路径绑定**的 HIGHDPIAWARE 覆盖 造成的(同一 exe 复制到 %TEMP% 启动即变 UNAWARE),换机器/换构建路径即失效。 UNAWARE 时 GetCursorPos 返回逻辑坐标(1536x864)而采集是物理像素(1920x1080), clamp 上界与增量尺度都会错。设置失败时区分「已被外部设定」与「真正降级」并分别报日志。
This commit is contained in:
1 parent
39af42e1b3
commit
78ac9d9972
4 files changed
+116
-2
No files matched your search
@@ -32,8 +32,60 @@ fn parse_args() -> Result<(u16, String)> {
|
||||
Ok((port, code))
|
||||
}
|
||||
|
||||
/// 显式声明进程 DPI 感知(**必须在任何采集/注入调用之前**,越早越好)。
|
||||
///
|
||||
/// 为什么不能省(3b 开工实测,见个人层 `docs/output/2026-10-31-3b-开工前实测.md` §4):
|
||||
/// 相对鼠标走「`GetCursorPos` + 增量」,而 `GetCursorPos` 的坐标空间取决于进程的
|
||||
/// DPI 感知模式——UNAWARE 进程拿到的是**逻辑坐标**(本机 125% 缩放下是 1536x864),
|
||||
/// 而 DXGI 采集与 `AuthOk` 下发的是**物理像素**(1920x1080)。两者混用会让
|
||||
/// clamp 上界偏小、增量尺度偏小:指针到不了屏幕右/下边缘,且移动距离只有 80%。
|
||||
///
|
||||
/// 为什么不能依赖现状:本机 `agent.exe` 实测确实是 `PER_MONITOR_AWARE(2)`,但那是
|
||||
/// `HKCU\...\AppCompatFlags\Layers` 里一条**路径绑定**的 `HIGHDPIAWARE` 覆盖造成的
|
||||
/// ——同一个 exe 复制到 `%TEMP%` 启动就变成 `UNAWARE(0)`。换机器、换构建路径、
|
||||
/// CI 产物、用户机器上都不成立,所以必须由代码显式设置。
|
||||
///
|
||||
/// 失败时只告警不退出:DPI 感知不可用是老系统(< Win10 1703)的情况,
|
||||
/// 此时相对鼠标会退化(精度下降),但绝对注入与其余功能不受影响——
|
||||
/// 让整个被控端起不来是更差的选择。
|
||||
fn init_dpi_awareness() {
|
||||
use windows::Win32::UI::HiDpi::{
|
||||
GetThreadDpiAwarenessContext, SetProcessDpiAwarenessContext, GetAwarenessFromDpiAwarenessContext,
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
};
|
||||
match unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) } {
|
||||
Ok(()) => println!("[agent] DPI awareness: PER_MONITOR_AWARE_V2 (set by this process)"),
|
||||
Err(e) => {
|
||||
// 失败有两种完全不同的原因,**必须分开报告**,否则日志会误导排查方向:
|
||||
// a) 进程的感知模式已被别处设定(注册表 AppCompat 覆盖、清单、父进程)——
|
||||
// ERROR_ACCESS_DENIED(0x80070005),此时若已经是 per-monitor 就完全没问题;
|
||||
// b) 系统过旧不支持该 API——这才是真正的降级。
|
||||
// 本机实测是 (a):HKCU 的 AppCompatFlags\Layers 里有一条路径绑定的 HIGHDPIAWARE。
|
||||
let actual = unsafe {
|
||||
GetAwarenessFromDpiAwarenessContext(GetThreadDpiAwarenessContext())
|
||||
};
|
||||
// DPI_AWARENESS(WinUser.h):INVALID=-1、UNAWARE=0、SYSTEM_AWARE=1、
|
||||
// PER_MONITOR_AWARE=2。注意 PER_MONITOR 是 **2** 而不是 3。
|
||||
let is_per_monitor = actual.0 == 2;
|
||||
if is_per_monitor {
|
||||
println!(
|
||||
"[agent] DPI awareness: 已由外部设定为 PER_MONITOR_AWARE(无需本进程设置;{e})"
|
||||
);
|
||||
} else {
|
||||
eprintln!(
|
||||
"[agent] 警告:DPI 感知为 {}(非 per-monitor),且本进程设置失败({e})。\
|
||||
相对鼠标的位移比例会与被采集桌面不一致;绝对触摸不受影响。",
|
||||
actual.0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// 第一件事:定下 DPI 感知,后续所有坐标运算都依赖它
|
||||
init_dpi_awareness();
|
||||
let (port, code) = parse_args()?;
|
||||
rustls::crypto::ring::default_provider()
|
||||
.install_default()
|
||||
|
||||
Reference in new issue
Block a user