feat(agent-core): ScreenCapturer 新增 pointer_state 默认实现并接入帧头
v5 光标字段在平台无关会话层的接缝(4b/Q4=A:Linux 实装另立循环): - trait 新增 pointer_state(),默认实现返回 None + 会话期一次性日志—— 与 InputInjector::text() 未实现先例同款纪律(异常路径必须可见,且不逐帧刷屏) - encode_send 增 cursor 参数,两条发送路径(新帧/静止强制 IDR)都传入 - 静止保活重发分支:split 出的旧帧头沿用会让标记冻住(画面静止≠指针静止), 与 seq/ts_ms 同规重建 cursor - 新增单测锁默认实现契约(None 且 panic-free,Linux/X11 端真实形态) - linux-agent 不强制实现(cargo build --workspace 复验编译通过)
This commit is contained in:
parent
b1ac32175c
commit
ad46fc805b
@ -40,6 +40,22 @@ pub trait ScreenCapturer: Send {
|
||||
|
||||
/// 取出最近一帧,BGRA 紧凑排列(已去掉 row pitch),供 openh264 直接编码。
|
||||
fn copy_frame(&mut self) -> Result<Vec<u8>>;
|
||||
|
||||
/// 协议 v5 的光标采样:当前指针在**采集画面内**的物理像素坐标。
|
||||
/// 语义与折叠规则(隐藏/离屏 → None)见 `common::VideoFrameHeader::cursor`。
|
||||
///
|
||||
/// **默认实现刻意返回 None,但必须留下可见痕迹**:Linux 后端本轮未实现光标采样
|
||||
/// (X11 `query_pointer` 与 Windows `GetCursorInfo` 不同构,且本机无 X11 无法给
|
||||
/// proof——4b/Q4=A 裁决另立循环)。全局规则要求异常路径有可见后果——安卓用户对着
|
||||
/// Linux 被控端看不到标记不能是静默的,日志必须说明是没实现。一次性日志防刷屏
|
||||
/// (本方法每帧都会被调,最高 60Hz)。
|
||||
fn pointer_state(&self) -> Option<(i16, i16)> {
|
||||
static LOGGED_UNIMPL: AtomicBool = AtomicBool::new(false);
|
||||
if !LOGGED_UNIMPL.swap(true, Ordering::Relaxed) {
|
||||
eprintln!("[agent-core] 光标采样后端未实现:帧头 cursor 将恒为 None");
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 输入注入后端。实现需可在异步运行时中被多任务共享,故要求 `Send + Sync`。
|
||||
@ -176,6 +192,7 @@ fn encode_send(
|
||||
(w, h): (u32, u32),
|
||||
ts_ms: u64,
|
||||
seq: u64,
|
||||
cursor: Option<(i16, i16)>,
|
||||
tx: &watch::Sender<Option<Vec<u8>>>,
|
||||
last_frame: &mut Option<Vec<u8>>,
|
||||
) -> bool {
|
||||
@ -189,8 +206,7 @@ fn encode_send(
|
||||
ts_ms,
|
||||
seq,
|
||||
keyframe: h264_contains_idr(&payload),
|
||||
// v5 光标字段:占位 None,本 crate 的真实采样在下一步接入
|
||||
cursor: None,
|
||||
cursor,
|
||||
};
|
||||
let frame = encode_video_frame(&header, &payload);
|
||||
if tx.send(Some(frame.clone())).is_err() {
|
||||
@ -249,7 +265,16 @@ fn capture_session(
|
||||
encoder.force_intra_frame();
|
||||
}
|
||||
let ts = start.elapsed().as_millis() as u64;
|
||||
if !encode_send(&mut encoder, &raw, (w, h), ts, seq, tx, &mut last_frame) {
|
||||
if !encode_send(
|
||||
&mut encoder,
|
||||
&raw,
|
||||
(w, h),
|
||||
ts,
|
||||
seq,
|
||||
cap.pointer_state(),
|
||||
tx,
|
||||
&mut last_frame,
|
||||
) {
|
||||
return LoopEnd::ChannelClosed; // 会话结束
|
||||
}
|
||||
seq = seq.wrapping_add(1);
|
||||
@ -288,7 +313,16 @@ fn capture_session(
|
||||
encoder.force_intra_frame();
|
||||
let raw = last_raw.as_ref().expect("can_key 已保证");
|
||||
let ts = start.elapsed().as_millis() as u64;
|
||||
if !encode_send(&mut encoder, raw, (w, h), ts, seq, tx, &mut last_frame) {
|
||||
if !encode_send(
|
||||
&mut encoder,
|
||||
raw,
|
||||
(w, h),
|
||||
ts,
|
||||
seq,
|
||||
cap.pointer_state(),
|
||||
tx,
|
||||
&mut last_frame,
|
||||
) {
|
||||
return LoopEnd::ChannelClosed;
|
||||
}
|
||||
seq = seq.wrapping_add(1);
|
||||
@ -302,6 +336,9 @@ fn capture_session(
|
||||
Ok((mut hdr, payload)) => {
|
||||
hdr.seq = seq;
|
||||
hdr.ts_ms = start.elapsed().as_millis() as u64;
|
||||
// v5 与 seq/ts_ms 同理:重发帧的 cursor 若沿用旧值会让标记
|
||||
// 冻住(画面静止≠指针静止,触控板用户尤甚)
|
||||
hdr.cursor = cap.pointer_state();
|
||||
let frame = encode_video_frame(&hdr, payload);
|
||||
if tx.send(Some(frame)).is_err() {
|
||||
return LoopEnd::ChannelClosed;
|
||||
@ -692,4 +729,25 @@ mod tests {
|
||||
dispatch_input(&inj, InputEvent::Text { units: vec![] });
|
||||
assert_eq!(inj.received.lock().unwrap().clone(), vec![0]);
|
||||
}
|
||||
|
||||
/// v5:采集后端未实现 `pointer_state` 时必须返回 None 且不 panic——Linux/X11 端
|
||||
/// 本轮的真实形态(4b/Q4=A:默认 None + 一次性日志,实装另立循环)。
|
||||
/// 锁的是「默认实现存在且语义为 None」这条契约:若有人把默认实现改成 panic 或
|
||||
/// 伪造坐标,这条会红。
|
||||
#[test]
|
||||
fn pointer_state_default_returns_none_without_panic() {
|
||||
struct NoPointerCapturer;
|
||||
impl ScreenCapturer for NoPointerCapturer {
|
||||
fn size(&self) -> (u32, u32) {
|
||||
(100, 100)
|
||||
}
|
||||
fn acquire(&mut self, _timeout_ms: u32) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
fn copy_frame(&mut self) -> Result<Vec<u8>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
assert_eq!(NoPointerCapturer.pointer_state(), None);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user