1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Emit-rate pacing override: what paces injected motion, and the rate in effect (§4.14).
/// What paces injected motion (`OPTION(EMIT)`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum EmitPace {
/// Pace to the mouse's learnt native report rate (the default).
#[default]
Learned,
/// Pace to the cloned mouse's `bInterval` poll rate.
Interval,
/// A fixed rate in Hz. The 1 ms frame clock snaps it to `1000/n` Hz and caps it at 1 kHz.
Fixed(u16),
/// A learned model decides per millisecond whether injected cursor motion emits and what counts it
/// carries, from a profile of the attached mouse's own report stream. See [`RenderState`].
Rendered,
}
/// Whether [`EmitPace::Rendered`] is actually rendering, reported by `RESP(OPTIONS, EMIT)` (§4.14).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum RenderState {
/// The selected mode is not [`EmitPace::Rendered`].
#[default]
Off,
/// Still collecting the attached mouse's profile; the box is pacing as [`EmitPace::Learned`] does.
Warming,
/// Rendering.
Live,
/// No usable model, or the game PC does not poll the clone every millisecond.
Unavailable,
}
/// The configured [`EmitPace`] plus the emit-rate ceiling and the wire rate actually in effect (§4.14).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct EmitPaceStatus {
/// The selected mode (for [`EmitPace::Fixed`], the rate the host requested).
pub mode: EmitPace,
/// The ceiling currently in effect (Hz); 0 = learnt/adaptive, or no device yet in [`EmitPace::Interval`].
pub resolved_hz: u16,
/// The forced wire rate the host asked for (Hz); `None` leaves the device's own.
pub force_hz: Option<u16>,
/// What the clone's input endpoints advertise now (Hz), forced or native; 0 = no clone.
pub advertised_hz: u16,
/// Whether a forced interval is written into the descriptor being served.
pub force_active: bool,
/// Whether [`EmitPace::Rendered`] is rendering, warming up, or refused.
pub render_state: RenderState,
}
impl EmitPaceStatus {
/// Decode a `RESP(OPTIONS, EMIT)` payload (§4.14).
pub(crate) fn from_payload(p: &[u8]) -> Option<EmitPaceStatus> {
if p.len() < 13 {
return None;
}
let fixed_hz = u16::from_le_bytes([p[3], p[4]]);
let resolved_hz = u16::from_le_bytes([p[5], p[6]]);
let force_hz = u16::from_le_bytes([p[7], p[8]]);
let advertised_hz = u16::from_le_bytes([p[9], p[10]]);
let mode = match p[2] {
0 => EmitPace::Learned,
1 => EmitPace::Interval,
2 => EmitPace::Fixed(fixed_hz),
3 => EmitPace::Rendered,
_ => return None,
};
let render_state = match p[12] {
0 => RenderState::Off,
1 => RenderState::Warming,
2 => RenderState::Live,
3 => RenderState::Unavailable,
_ => return None,
};
Some(EmitPaceStatus {
mode,
resolved_hz,
force_hz: (force_hz != 0).then_some(force_hz),
advertised_hz,
force_active: p[11] != 0,
render_state,
})
}
}