2026-10-30 00:09:48 -04:00
|
|
|
|
//! 协议 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<String> {
|
|
|
|
|
|
let a: Vec<String> = std::env::args().collect();
|
|
|
|
|
|
a.iter()
|
|
|
|
|
|
.position(|x| x == name)
|
|
|
|
|
|
.and_then(|i| a.get(i + 1))
|
|
|
|
|
|
.cloned()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn parse_units(spec: &str) -> Result<Vec<u16>> {
|
|
|
|
|
|
if spec.trim().is_empty() {
|
2026-10-30 00:21:19 -04:00
|
|
|
|
bail!(
|
|
|
|
|
|
"--units 缺失或为空:本工具要验证的是「有文本送出并被注入」,\
|
|
|
|
|
|
空输入没有验证价值(空 Text 在产品路径上由 Session::send_text 拦掉)"
|
|
|
|
|
|
);
|
2026-10-30 00:09:48 -04:00
|
|
|
|
}
|
|
|
|
|
|
spec.split(',')
|
|
|
|
|
|
.map(|s| s.trim())
|
|
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
|
|
.map(|s| {
|
|
|
|
|
|
s.parse::<u32>()
|
|
|
|
|
|
.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(())
|
|
|
|
|
|
}
|