use crate::kernel::event::KeySource;
use crate::kernel::key_aggregator::PressedKeyMeta;
use crate::kernel::protocol_hid::{find_key_index_by_value, get_key_name, is_knob_pulse_key_index};
use std::time::{Duration, Instant};
const KNOB_PULSE_TAIL_WINDOW: Duration = Duration::from_millis(100);
pub(crate) struct ConsumerFrame {
pub(crate) batches: Vec<Vec<PressedKeyMeta>>,
pub(crate) cleared: bool,
pub(crate) counts_as_key_event: bool,
}
#[derive(Default)]
pub(crate) struct ConsumerHeldTracker {
held: Vec<PressedKeyMeta>,
last_knob_pulse_at: Option<Instant>,
}
impl ConsumerHeldTracker {
pub(crate) fn on_frame(&mut self, key_value: u16, now: Instant) -> ConsumerFrame {
if key_value == 0x0000 {
let within_pulse_tail = self
.last_knob_pulse_at
.is_some_and(|at| now.saturating_duration_since(at) <= KNOB_PULSE_TAIL_WINDOW);
if within_pulse_tail {
log::debug!(
target: "hid",
"Consumer 0x0000 是旋钮脉冲收尾,按住的 {:?} 保持不变",
self.held_indices()
);
self.last_knob_pulse_at = None;
return ConsumerFrame {
batches: Vec::new(),
cleared: false,
counts_as_key_event: false,
};
}
log::debug!(
target: "hid",
"Consumer 0x0000 全部松开(此前按住 {:?})",
self.held_indices()
);
self.held.clear();
return ConsumerFrame {
batches: vec![Vec::new()],
cleared: true,
counts_as_key_event: false,
};
}
let Some(key_index) = find_key_index_by_value(key_value) else {
log::debug!(target: "hid", "Consumer 0x{key_value:04X} 不是已知键值,忽略");
return ConsumerFrame {
batches: Vec::new(),
cleared: false,
counts_as_key_event: false,
};
};
let meta = PressedKeyMeta {
key_index,
key_name: get_key_name(key_index).to_string(),
key_value,
source: KeySource::Consumer,
};
if is_knob_pulse_key_index(key_index) {
log::debug!(
target: "hid",
"Consumer 0x{:04X} 旋钮脉冲 key{}(按住的 {:?} 保持不变)",
key_value, key_index, self.held_indices()
);
let mut with_pulse = self.held.clone();
with_pulse.push(meta);
self.last_knob_pulse_at = Some(now);
return ConsumerFrame {
batches: vec![with_pulse, self.held.clone()],
cleared: false,
counts_as_key_event: true,
};
}
if !self.held.iter().any(|m| m.key_index == key_index) {
self.held.push(meta);
}
log::debug!(
target: "hid",
"Consumer 0x{:04X} 按下 key{},当前按住 {:?}",
key_value, key_index, self.held_indices()
);
self.last_knob_pulse_at = None;
ConsumerFrame {
batches: vec![self.held.clone()],
cleared: false,
counts_as_key_event: true,
}
}
fn held_indices(&self) -> Vec<usize> {
self.held.iter().map(|m| m.key_index).collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::event::BoardEvent;
use tokio::sync::broadcast;
const TAB: u16 = 0x0F01;
const ESC: u16 = 0x0F03;
const KNOB_CW: u16 = 0x0F08;
const RELEASE: u16 = 0x0000;
fn t0() -> Instant {
Instant::now()
}
fn at(base: Instant, ms: u64) -> Instant {
base + Duration::from_millis(ms)
}
fn batches_of(frame: &ConsumerFrame) -> Vec<Vec<usize>> {
frame
.batches
.iter()
.map(|b| b.iter().map(|m| m.key_index).collect())
.collect()
}
#[test]
fn knob_pulse_keeps_the_held_key() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
assert_eq!(batches_of(&tracker.on_frame(TAB, base)), vec![vec![3]]);
let pulse = tracker.on_frame(KNOB_CW, at(base, 500));
assert_eq!(batches_of(&pulse), vec![vec![3, 1], vec![3]]);
assert!(!pulse.cleared);
let tail = tracker.on_frame(RELEASE, at(base, 502));
assert!(batches_of(&tail).is_empty(), "脉冲收尾不该改变按住状态");
assert!(!tail.cleared);
assert_eq!(
batches_of(&tracker.on_frame(KNOB_CW, at(base, 700))),
vec![vec![3, 1], vec![3]]
);
}
#[test]
fn real_release_after_knob_still_clears() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
tracker.on_frame(KNOB_CW, at(base, 500));
tracker.on_frame(RELEASE, at(base, 502));
let released = tracker.on_frame(RELEASE, at(base, 1067));
assert_eq!(batches_of(&released), vec![Vec::<usize>::new()]);
assert!(released.cleared);
assert!(tracker.held.is_empty());
}
#[test]
fn one_pulse_only_swallows_one_tail() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
tracker.on_frame(KNOB_CW, at(base, 500));
let tail = tracker.on_frame(RELEASE, at(base, 502));
assert!(batches_of(&tail).is_empty(), "第一帧是脉冲收尾");
let released = tracker.on_frame(RELEASE, at(base, 520));
assert!(released.cleared, "紧接着的第二帧就是真松手,必须清空");
assert!(tracker.held.is_empty());
}
#[test]
fn stale_pulse_does_not_swallow_a_later_release() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
tracker.on_frame(KNOB_CW, at(base, 500));
let just_past_window = at(base, 500) + KNOB_PULSE_TAIL_WINDOW + Duration::from_millis(1);
let released = tracker.on_frame(RELEASE, just_past_window);
assert!(released.cleared, "超出收尾窗口的 0x0000 必须清空按住状态");
}
#[test]
fn unknown_frame_between_pulse_and_tail_does_not_break_the_hold() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
tracker.on_frame(KNOB_CW, at(base, 500));
tracker.on_frame(0x3B13, at(base, 501));
let tail = tracker.on_frame(RELEASE, at(base, 502));
assert!(batches_of(&tail).is_empty(), "未知帧不该让收尾被误判成松手");
assert_eq!(tracker.held_indices(), vec![3]);
let released = tracker.on_frame(RELEASE, at(base, 1100));
assert!(released.cleared, "未知帧也不该遮住之后真正的松手");
}
#[test]
fn second_key_is_added_not_replacing_the_first() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
assert_eq!(
batches_of(&tracker.on_frame(ESC, at(base, 100))),
vec![vec![3, 5]]
);
let released = tracker.on_frame(RELEASE, at(base, 200));
assert_eq!(batches_of(&released), vec![Vec::<usize>::new()]);
}
#[test]
fn separate_presses_do_not_pile_up() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
tracker.on_frame(RELEASE, at(base, 100));
assert_eq!(
batches_of(&tracker.on_frame(ESC, at(base, 200))),
vec![vec![5]]
);
}
#[test]
fn same_key_repeated_does_not_duplicate() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
assert_eq!(
batches_of(&tracker.on_frame(TAB, at(base, 10))),
vec![vec![3]]
);
}
#[test]
fn pressing_a_key_cancels_the_pending_pulse_tail() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(KNOB_CW, base);
tracker.on_frame(TAB, at(base, 5));
let released = tracker.on_frame(RELEASE, at(base, 10));
assert!(released.cleared, "按下过键之后,0x0000 必须按真松手处理");
assert!(tracker.held.is_empty());
}
#[test]
fn knob_alone_reports_a_tap_and_holds_nothing() {
let mut tracker = ConsumerHeldTracker::default();
let pulse = tracker.on_frame(KNOB_CW, t0());
assert_eq!(batches_of(&pulse), vec![vec![1], Vec::<usize>::new()]);
assert!(tracker.held.is_empty(), "旋钮不该被当成按住的键");
}
#[test]
fn unknown_key_value_changes_nothing() {
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
tracker.on_frame(TAB, base);
let unknown = tracker.on_frame(0xFFFF, at(base, 10));
assert!(batches_of(&unknown).is_empty());
assert_eq!(tracker.held_indices(), vec![3], "未知键值不该动按住状态");
}
#[test]
fn aggregator_never_releases_the_held_key_while_the_knob_turns() {
use crate::kernel::key_aggregator::KeyStateAggregator;
let (tx, mut rx) = broadcast::channel(64);
let aggregator = KeyStateAggregator::new(tx);
let base = t0();
let mut tracker = ConsumerHeldTracker::default();
let frames = [
(TAB, 0),
(KNOB_CW, 300),
(RELEASE, 302),
(KNOB_CW, 450),
(RELEASE, 452),
(KNOB_CW, 600),
(RELEASE, 602),
(RELEASE, 1200),
];
for (value, ms) in frames {
for batch in tracker.on_frame(value, at(base, ms)).batches {
aggregator.report_change(KeySource::Consumer, batch, None);
}
}
let mut events = Vec::new();
while let Ok(event) = rx.try_recv() {
if let BoardEvent::KeyPress(key) = event {
events.push((key.key_index, key.pressed));
}
}
let tab_events: Vec<bool> = events
.iter()
.filter(|(index, _)| *index == 3)
.map(|(_, pressed)| *pressed)
.collect();
assert_eq!(
tab_events,
vec![true, false],
"按住 Tab 转旋钮期间,Tab 不该被判成松开:{events:?}"
);
let knob_presses = events
.iter()
.filter(|(index, pressed)| *index == 1 && *pressed)
.count();
assert_eq!(knob_presses, 3, "三格旋钮应各产生一次按下:{events:?}");
}
}