//! 协议 v3 文本注入链路的桌面真边界验证工具(loop-2b,不依赖 Android)。 //! //! 证明的事:`Session::send_text` 经线格式到被控端后,`agent` 的 //! `KEYEVENTF_UNICODE` 注入把**同一段 UTF-16 code unit** 送进了前台应用。 //! 判据由外部读取目标窗口的实际文本内容(调用方负责),本工具只负责发送。 //! //! 用法: //! 终端1: target\release\agent.exe --port 47800 --code t //! 终端2: 把目标应用(记事本)切到前台,然后 //! cargo run -p viewer-core --example text_check -- \ //! --port 47800 --code t --units 20320,22909,55357,56832 //! //! `--units` 是十进制的 UTF-16 code unit 列表(逗号分隔): //! 「你好😀」= 20320,22909,55357,56832(😀 是代理对)。 //! 刻意不收自然语言字符串参数:命令行到 Rust 的编码往返会引入与本轮无关的变量, //! 由调用方(PowerShell)从 .NET 字符串取 char[] 更忠实。 //! //! `--delay`:连接后等待毫秒再注入。给调用方把目标窗口切到前台的时间—— //! SendInput 注入的是**前台**应用,窗口没在前台等于测了个寂寞。 //! `--hold`:注入后保持会话的毫秒数,等消息在远端消化完再关连接。 use anyhow::{bail, Result}; use std::time::Duration; use viewer_core::{ConnectConfig, Session}; fn arg(name: &str) -> Option { let a: Vec = std::env::args().collect(); a.iter() .position(|x| x == name) .and_then(|i| a.get(i + 1)) .cloned() } fn parse_units(spec: &str) -> Result> { if spec.trim().is_empty() { bail!("--units 为空:本轮要验证的是「有文本送出并被注入」,空输入没有验证价值"); } spec.split(',') .map(|s| s.trim()) .filter(|s| !s.is_empty()) .map(|s| { s.parse::() .ok() .and_then(|v| u16::try_from(v).ok()) .ok_or_else(|| anyhow::anyhow!("--units 必须是 0..=65535 的十进制数,got {s:?}")) }) .collect() } #[tokio::main] async fn main() -> Result<()> { let port: u16 = arg("--port") .map(|s| s.parse()) .transpose()? .unwrap_or(47800); let code = arg("--code").unwrap_or_else(|| "123456".into()); let host = arg("--host").unwrap_or_else(|| "127.0.0.1".into()); let delay = arg("--delay") .map(|s| s.parse()) .transpose()? .unwrap_or(1500u64); let hold = arg("--hold") .map(|s| s.parse()) .transpose()? .unwrap_or(800u64); let units = parse_units(&arg("--units").unwrap_or_default())?; let session = Session::connect(ConnectConfig::direct(&host, port, &code)).await?; println!( "[text_check] connected peer={} units={}", session.peer_addr(), units.len() ); tokio::time::sleep(Duration::from_millis(delay)).await; session.send_text(&units); println!("[text_check] sent (只报长度,不打印内容)"); tokio::time::sleep(Duration::from_millis(hold)).await; let st = session.stats(); println!( "[text_check] frames={} rtt={}ms — 会话活着,注入已发给前台应用", st.frames_in, st.rtt_ms ); session.close().await; Ok(()) }