use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::hosts::HostKind;
use crate::core::record::{Channel, InputSink as RecordSink};
use crate::core::sync::{LockRank, Mutex};
use super::chardev::CharPort;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Keysym(pub u32);
impl Keysym {
pub const BACKSPACE: Keysym = Keysym(0xff08);
pub const TAB: Keysym = Keysym(0xff09);
pub const RETURN: Keysym = Keysym(0xff0d);
pub const ESCAPE: Keysym = Keysym(0xff1b);
pub const HOME: Keysym = Keysym(0xff50);
pub const LEFT: Keysym = Keysym(0xff51);
pub const UP: Keysym = Keysym(0xff52);
pub const RIGHT: Keysym = Keysym(0xff53);
pub const DOWN: Keysym = Keysym(0xff54);
pub const PAGE_UP: Keysym = Keysym(0xff55);
pub const PAGE_DOWN: Keysym = Keysym(0xff56);
pub const END: Keysym = Keysym(0xff57);
pub const INSERT: Keysym = Keysym(0xff63);
pub const F1: Keysym = Keysym(0xffbe);
pub const F12: Keysym = Keysym(0xffc9);
pub const SHIFT_L: Keysym = Keysym(0xffe1);
pub const SHIFT_R: Keysym = Keysym(0xffe2);
pub const CONTROL_L: Keysym = Keysym(0xffe3);
pub const CONTROL_R: Keysym = Keysym(0xffe4);
pub const CAPS_LOCK: Keysym = Keysym(0xffe5);
pub const ALT_L: Keysym = Keysym(0xffe9);
pub const ALT_R: Keysym = Keysym(0xffea);
pub const DELETE: Keysym = Keysym(0xffff);
#[inline]
#[must_use]
pub const fn from_ascii(ch: u8) -> Keysym {
Keysym(ch as u32)
}
#[inline]
#[must_use]
pub const fn ascii(self) -> Option<u8> {
if self.0 >= 0x20 && self.0 < 0x7f {
#[allow(clippy::cast_possible_truncation)]
Some(self.0 as u8)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn is_shift(self) -> bool {
self.0 == Keysym::SHIFT_L.0 || self.0 == Keysym::SHIFT_R.0
}
}
impl fmt::Display for Keysym {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.ascii() {
Some(ch) => write!(f, "{}", ch as char),
None => write!(f, "0x{:04x}", self.0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InputEvent {
Key {
keysym: Keysym,
down: bool,
},
Pointer {
x: u32,
y: u32,
buttons: u8,
},
}
pub const EVENT_BYTES: usize = 12;
impl InputEvent {
const KIND_KEY: u8 = 1;
const KIND_POINTER: u8 = 2;
#[must_use]
pub const fn encode(self) -> [u8; EVENT_BYTES] {
let (kind, flags, a, b) = match self {
InputEvent::Key { keysym, down } => (InputEvent::KIND_KEY, down as u8, keysym.0, 0u32),
InputEvent::Pointer { x, y, buttons } => (InputEvent::KIND_POINTER, buttons, x, y),
};
let a = a.to_le_bytes();
let b = b.to_le_bytes();
[
kind, flags, 0, 0, a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3],
]
}
#[must_use]
pub const fn decode(bytes: &[u8; EVENT_BYTES]) -> Option<InputEvent> {
let a = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let b = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
match bytes[0] {
InputEvent::KIND_KEY => Some(InputEvent::Key {
keysym: Keysym(a),
down: bytes[1] != 0,
}),
InputEvent::KIND_POINTER => Some(InputEvent::Pointer {
x: a,
y: b,
buttons: bytes[1],
}),
_ => None,
}
}
}
pub trait InputSink: Send + Sync + fmt::Debug {
fn deliver(&self, event: InputEvent);
}
pub fn deliver_all(sinks: &[Box<dyn InputSink>], event: InputEvent) {
for sink in sinks {
sink.deliver(event);
}
}
pub const KIND: HostKind = HostKind::new("input");
pub const DEFAULT_STREAM: &str = "vnc";
#[must_use]
pub fn channel(name: &str) -> Channel {
Channel::new(KIND, name)
}
pub struct Feed {
sinks: Mutex<Vec<Arc<dyn InputSink>>>,
}
impl Default for Feed {
fn default() -> Feed {
Feed::new()
}
}
impl fmt::Debug for Feed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.sinks.try_lock() {
Some(sinks) => f.debug_struct("Feed").field("sinks", &sinks.len()).finish(),
None => f.debug_struct("Feed").field("sinks", &"<in use>").finish(),
}
}
}
impl Feed {
#[must_use]
pub fn new() -> Feed {
Feed {
sinks: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
pub fn attach(&self, sink: Arc<dyn InputSink>) {
self.sinks.lock().push(sink);
}
#[must_use]
pub fn len(&self) -> usize {
self.sinks.lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.sinks.lock().is_empty()
}
pub fn deliver(&self, event: InputEvent) {
let sinks: Vec<Arc<dyn InputSink>> = self.sinks.lock().clone();
for sink in &sinks {
sink.deliver(event);
}
}
}
impl RecordSink for Feed {
fn deliver(&self, payload: &[u8]) {
for record in payload.as_chunks::<EVENT_BYTES>().0 {
if let Some(event) = InputEvent::decode(record) {
Feed::deliver(self, event);
}
}
}
}
#[must_use]
pub fn sink(feed: &Arc<Feed>) -> Arc<dyn RecordSink> {
Arc::clone(feed) as Arc<dyn RecordSink>
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScanCode {
pub code: u8,
pub extended: bool,
pub shifted: bool,
}
impl ScanCode {
const fn plain(code: u8) -> ScanCode {
ScanCode {
code,
extended: false,
shifted: false,
}
}
const fn shift(code: u8) -> ScanCode {
ScanCode {
code,
extended: false,
shifted: true,
}
}
const fn ext(code: u8) -> ScanCode {
ScanCode {
code,
extended: true,
shifted: false,
}
}
}
const SET2_LSHIFT: u8 = 0x12;
const SET2_BREAK: u8 = 0xf0;
const SET2_EXTEND: u8 = 0xe0;
const fn letter(ch: u8) -> u8 {
match ch {
b'a' => 0x1c,
b'b' => 0x32,
b'c' => 0x21,
b'd' => 0x23,
b'e' => 0x24,
b'f' => 0x2b,
b'g' => 0x34,
b'h' => 0x33,
b'i' => 0x43,
b'j' => 0x3b,
b'k' => 0x42,
b'l' => 0x4b,
b'm' => 0x3a,
b'n' => 0x31,
b'o' => 0x44,
b'p' => 0x4d,
b'q' => 0x15,
b'r' => 0x2d,
b's' => 0x1b,
b't' => 0x2c,
b'u' => 0x3c,
b'v' => 0x2a,
b'w' => 0x1d,
b'x' => 0x22,
b'y' => 0x35,
_ => 0x1a,
}
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub const fn set2(keysym: Keysym) -> Option<ScanCode> {
match keysym.0 {
#[allow(clippy::cast_possible_truncation)]
c @ 0x61..=0x7a => Some(ScanCode::plain(letter(c as u8))),
#[allow(clippy::cast_possible_truncation)]
c @ 0x41..=0x5a => Some(ScanCode::shift(letter(c as u8 + 0x20))),
0x31 => Some(ScanCode::plain(0x16)),
0x32 => Some(ScanCode::plain(0x1e)),
0x33 => Some(ScanCode::plain(0x26)),
0x34 => Some(ScanCode::plain(0x25)),
0x35 => Some(ScanCode::plain(0x2e)),
0x36 => Some(ScanCode::plain(0x36)),
0x37 => Some(ScanCode::plain(0x3d)),
0x38 => Some(ScanCode::plain(0x3e)),
0x39 => Some(ScanCode::plain(0x46)),
0x30 => Some(ScanCode::plain(0x45)),
0x21 => Some(ScanCode::shift(0x16)), 0x40 => Some(ScanCode::shift(0x1e)), 0x23 => Some(ScanCode::shift(0x26)), 0x24 => Some(ScanCode::shift(0x25)), 0x25 => Some(ScanCode::shift(0x2e)), 0x5e => Some(ScanCode::shift(0x36)), 0x26 => Some(ScanCode::shift(0x3d)), 0x2a => Some(ScanCode::shift(0x3e)), 0x28 => Some(ScanCode::shift(0x46)), 0x29 => Some(ScanCode::shift(0x45)), 0x60 => Some(ScanCode::plain(0x0e)), 0x7e => Some(ScanCode::shift(0x0e)), 0x2d => Some(ScanCode::plain(0x4e)), 0x5f => Some(ScanCode::shift(0x4e)), 0x3d => Some(ScanCode::plain(0x55)), 0x2b => Some(ScanCode::shift(0x55)), 0x5b => Some(ScanCode::plain(0x54)), 0x7b => Some(ScanCode::shift(0x54)), 0x5d => Some(ScanCode::plain(0x5b)), 0x7d => Some(ScanCode::shift(0x5b)), 0x5c => Some(ScanCode::plain(0x5d)), 0x7c => Some(ScanCode::shift(0x5d)), 0x3b => Some(ScanCode::plain(0x4c)), 0x3a => Some(ScanCode::shift(0x4c)), 0x27 => Some(ScanCode::plain(0x52)), 0x22 => Some(ScanCode::shift(0x52)), 0x2c => Some(ScanCode::plain(0x41)), 0x3c => Some(ScanCode::shift(0x41)), 0x2e => Some(ScanCode::plain(0x49)), 0x3e => Some(ScanCode::shift(0x49)), 0x2f => Some(ScanCode::plain(0x4a)), 0x3f => Some(ScanCode::shift(0x4a)), 0x20 => Some(ScanCode::plain(0x29)), 0xff08 => Some(ScanCode::plain(0x66)), 0xff09 => Some(ScanCode::plain(0x0d)), 0xff0d => Some(ScanCode::plain(0x5a)), 0xff1b => Some(ScanCode::plain(0x76)), 0xffe1 => Some(ScanCode::plain(0x12)), 0xffe2 => Some(ScanCode::plain(0x59)), 0xffe3 => Some(ScanCode::plain(0x14)), 0xffe4 => Some(ScanCode::ext(0x14)), 0xffe5 => Some(ScanCode::plain(0x58)), 0xffe9 => Some(ScanCode::plain(0x11)), 0xffea => Some(ScanCode::ext(0x11)), 0xff50 => Some(ScanCode::ext(0x6c)), 0xff51 => Some(ScanCode::ext(0x6b)), 0xff52 => Some(ScanCode::ext(0x75)), 0xff53 => Some(ScanCode::ext(0x74)), 0xff54 => Some(ScanCode::ext(0x72)), 0xff55 => Some(ScanCode::ext(0x7d)), 0xff56 => Some(ScanCode::ext(0x7a)), 0xff57 => Some(ScanCode::ext(0x69)), 0xff63 => Some(ScanCode::ext(0x70)), 0xffff => Some(ScanCode::ext(0x71)), 0xffbe => Some(ScanCode::plain(0x05)), 0xffbf => Some(ScanCode::plain(0x06)), 0xffc0 => Some(ScanCode::plain(0x04)), 0xffc1 => Some(ScanCode::plain(0x0c)), 0xffc2 => Some(ScanCode::plain(0x03)), 0xffc3 => Some(ScanCode::plain(0x0b)), 0xffc4 => Some(ScanCode::plain(0x83)), 0xffc5 => Some(ScanCode::plain(0x0a)), 0xffc6 => Some(ScanCode::plain(0x01)), 0xffc7 => Some(ScanCode::plain(0x09)), 0xffc8 => Some(ScanCode::plain(0x78)), 0xffc9 => Some(ScanCode::plain(0x07)), _ => None,
}
}
#[derive(Debug, Clone, Default)]
pub struct KeyMap {
shift_held: bool,
}
impl KeyMap {
#[must_use]
pub const fn new() -> KeyMap {
KeyMap { shift_held: false }
}
#[must_use]
pub const fn shift_held(&self) -> bool {
self.shift_held
}
pub fn reset(&mut self) {
self.shift_held = false;
}
pub fn encode(&mut self, keysym: Keysym, down: bool, out: &mut Vec<u8>) -> bool {
let Some(sc) = set2(keysym) else {
return false;
};
if keysym.is_shift() {
self.shift_held = down;
}
let synth = sc.shifted && !self.shift_held;
if down && synth {
out.push(SET2_LSHIFT);
}
if sc.extended {
out.push(SET2_EXTEND);
}
if !down {
out.push(SET2_BREAK);
}
out.push(sc.code);
if !down && synth {
out.push(SET2_BREAK);
out.push(SET2_LSHIFT);
}
true
}
}
pub struct KeyboardSink {
port: Arc<CharPort>,
map: crate::core::sync::Mutex<KeyMap>,
}
impl fmt::Debug for KeyboardSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KeyboardSink")
.field("port", &self.port)
.finish_non_exhaustive()
}
}
impl KeyboardSink {
#[must_use]
pub fn new(port: Arc<CharPort>) -> KeyboardSink {
KeyboardSink {
port,
map: crate::core::sync::Mutex::new(KeyMap::new()),
}
}
}
impl InputSink for KeyboardSink {
fn deliver(&self, event: InputEvent) {
let InputEvent::Key { keysym, down } = event else {
return;
};
let mut bytes = Vec::new();
{
let mut map = self.map.lock();
if !map.encode(keysym, down, &mut bytes) {
return;
}
}
self.port.feed(&bytes);
}
}
#[cfg(feature = "dev-nes-io")]
#[cfg_attr(docsrs, doc(cfg(feature = "dev-nes-io")))]
#[must_use]
pub const fn nes_button(keysym: Keysym) -> Option<u8> {
use crate::dev::nes::input::buttons;
match keysym.0 {
0xff52 => Some(buttons::UP),
0xff54 => Some(buttons::DOWN),
0xff51 => Some(buttons::LEFT),
0xff53 => Some(buttons::RIGHT),
0x7a | 0x5a => Some(buttons::B), 0x78 | 0x58 => Some(buttons::A), 0xff0d => Some(buttons::START), 0xffe1 | 0xffe2 => Some(buttons::SELECT), _ => None,
}
}
#[cfg(feature = "dev-nes-io")]
#[cfg_attr(docsrs, doc(cfg(feature = "dev-nes-io")))]
#[derive(Debug)]
pub struct PadSink {
pad: Arc<crate::dev::nes::input::Pad>,
port: usize,
held: core::sync::atomic::AtomicU8,
}
#[cfg(feature = "dev-nes-io")]
impl PadSink {
#[must_use]
pub fn new(pad: Arc<crate::dev::nes::input::Pad>, port: usize) -> PadSink {
PadSink {
pad,
port,
held: core::sync::atomic::AtomicU8::new(0),
}
}
}
#[cfg(feature = "dev-nes-io")]
impl InputSink for PadSink {
fn deliver(&self, event: InputEvent) {
use core::sync::atomic::Ordering;
let InputEvent::Key { keysym, down } = event else {
return;
};
let Some(bit) = nes_button(keysym) else {
return;
};
let previous = self.held.load(Ordering::Relaxed);
let next = if down {
previous | bit
} else {
previous & !bit
};
self.held.store(next, Ordering::Relaxed);
self.pad.set(self.port, next);
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
#[test]
fn an_event_survives_its_own_encoding() {
for event in [
InputEvent::Key {
keysym: Keysym::from_ascii(b'a'),
down: true,
},
InputEvent::Key {
keysym: Keysym::DELETE,
down: false,
},
InputEvent::Pointer {
x: 719,
y: 399,
buttons: 0b101,
},
] {
let bytes = event.encode();
assert_eq!(InputEvent::decode(&bytes), Some(event), "{event:?}");
}
}
#[test]
fn an_unknown_event_kind_is_none_not_a_panic() {
let mut bytes = [0u8; EVENT_BYTES];
bytes[0] = 0xfe;
assert_eq!(InputEvent::decode(&bytes), None);
}
#[derive(Debug, Default)]
struct Seen(Mutex<Vec<InputEvent>>);
impl InputSink for Seen {
fn deliver(&self, event: InputEvent) {
self.0.lock().push(event);
}
}
#[test]
fn a_feed_decodes_a_payload_and_hands_it_to_every_sink() {
let seen = Arc::new(Seen::default());
let feed = Arc::new(Feed::new());
feed.attach(Arc::clone(&seen) as Arc<dyn InputSink>);
feed.attach(Arc::clone(&seen) as Arc<dyn InputSink>);
assert_eq!(feed.len(), 2);
assert!(!feed.is_empty());
let mut payload = Vec::new();
payload.extend_from_slice(
&InputEvent::Key {
keysym: Keysym::SHIFT_L,
down: true,
}
.encode(),
);
payload.extend_from_slice(
&InputEvent::Key {
keysym: Keysym::from_ascii(b'a'),
down: true,
}
.encode(),
);
RecordSink::deliver(&*feed, &payload);
let got = seen.0.lock().clone();
assert_eq!(got.len(), 4, "two events, two sinks");
assert_eq!(
got[0],
InputEvent::Key {
keysym: Keysym::SHIFT_L,
down: true
},
"and in the order they were posted"
);
}
#[test]
fn a_feed_ignores_a_record_it_cannot_read() {
let seen = Arc::new(Seen::default());
let feed = Arc::new(Feed::new());
feed.attach(Arc::clone(&seen) as Arc<dyn InputSink>);
let mut payload = vec![0xfeu8; EVENT_BYTES];
payload.extend_from_slice(
&InputEvent::Pointer {
x: 1,
y: 2,
buttons: 4,
}
.encode(),
);
payload.extend_from_slice(&[0, 0, 0]);
RecordSink::deliver(&*feed, &payload);
assert_eq!(
seen.0.lock().as_slice(),
[InputEvent::Pointer {
x: 1,
y: 2,
buttons: 4
}]
);
}
#[test]
fn a_channel_names_the_stream() {
assert_eq!(channel(DEFAULT_STREAM).to_string(), "input:vnc");
assert!(channel("x").is_kind(KIND));
}
#[cfg(feature = "dev-pc")]
#[test]
fn the_scan_codes_agree_with_the_controllers_translation_table() {
use crate::dev::pc::kbc::TRANSLATE;
let expected: &[(Keysym, u8)] = &[
(Keysym::from_ascii(b'a'), 0x1e),
(Keysym::from_ascii(b'z'), 0x2c),
(Keysym::from_ascii(b'1'), 0x02),
(Keysym::from_ascii(b' '), 0x39),
(Keysym::RETURN, 0x1c),
(Keysym::ESCAPE, 0x01),
(Keysym::BACKSPACE, 0x0e),
(Keysym::TAB, 0x0f),
(Keysym::SHIFT_L, 0x2a),
(Keysym::CONTROL_L, 0x1d),
(Keysym::F1, 0x3b),
(Keysym::F12, 0x58),
(Keysym::UP, 0x48),
(Keysym::LEFT, 0x4b),
];
for (keysym, set1) in expected {
let sc = set2(*keysym).unwrap_or_else(|| panic!("{keysym} is on an AT keyboard"));
assert_eq!(
TRANSLATE[sc.code as usize], *set1,
"{keysym}: set-2 {:#04x} should translate to set-1 {set1:#04x}",
sc.code
);
}
}
#[test]
fn a_shifted_character_gets_a_shift_the_client_did_not_send() {
let mut map = KeyMap::new();
let mut out = Vec::new();
map.encode(Keysym::from_ascii(b'A'), true, &mut out);
assert_eq!(out, [SET2_LSHIFT, 0x1c], "shift make, then A make");
out.clear();
map.encode(Keysym::from_ascii(b'A'), false, &mut out);
assert_eq!(
out,
[SET2_BREAK, 0x1c, SET2_BREAK, SET2_LSHIFT],
"A break, then shift break"
);
}
#[test]
fn a_client_holding_shift_does_not_get_a_second_one() {
let mut map = KeyMap::new();
let mut out = Vec::new();
map.encode(Keysym::SHIFT_L, true, &mut out);
assert_eq!(out, [SET2_LSHIFT]);
assert!(map.shift_held());
out.clear();
map.encode(Keysym::from_ascii(b'A'), true, &mut out);
assert_eq!(out, [0x1c], "no synthesised shift on top of a held one");
map.reset();
assert!(!map.shift_held());
}
#[test]
fn an_extended_key_carries_its_prefix_both_ways() {
let mut map = KeyMap::new();
let mut out = Vec::new();
map.encode(Keysym::UP, true, &mut out);
assert_eq!(out, [SET2_EXTEND, 0x75]);
out.clear();
map.encode(Keysym::UP, false, &mut out);
assert_eq!(out, [SET2_EXTEND, SET2_BREAK, 0x75]);
}
#[test]
fn a_key_this_keyboard_does_not_have_produces_nothing() {
let mut map = KeyMap::new();
let mut out = Vec::new();
assert!(
!map.encode(Keysym(0xfe03), true, &mut out),
"ISO_Level3_Shift is not on a 101-key board"
);
assert!(out.is_empty());
}
#[test]
fn typing_at_a_port_puts_scan_codes_in_it() {
let port = Arc::new(CharPort::new());
let sink = KeyboardSink::new(port.clone());
sink.deliver(InputEvent::Key {
keysym: Keysym::from_ascii(b'a'),
down: true,
});
sink.deliver(InputEvent::Key {
keysym: Keysym::from_ascii(b'a'),
down: false,
});
let mut got = [0u8; 8];
let n = crate::host::chardev::CharDevice::read(&*port, &mut got);
assert_eq!(&got[..n], &[0x1c, SET2_BREAK, 0x1c]);
sink.deliver(InputEvent::Pointer {
x: 1,
y: 1,
buttons: 1,
});
assert_eq!(port.pending_input(), 0);
}
#[cfg(feature = "dev-nes-io")]
#[test]
fn a_pad_holds_what_is_pressed_and_releases_what_is_not() {
use crate::dev::nes::input::{Pad, buttons};
let pad = Arc::new(Pad::new());
let sink = PadSink::new(pad.clone(), 0);
sink.deliver(InputEvent::Key {
keysym: Keysym::from_ascii(b'x'),
down: true,
});
sink.deliver(InputEvent::Key {
keysym: Keysym::LEFT,
down: true,
});
assert_eq!(pad.get(0), buttons::A | buttons::LEFT);
sink.deliver(InputEvent::Key {
keysym: Keysym::LEFT,
down: false,
});
assert_eq!(pad.get(0), buttons::A);
assert_eq!(pad.get(1), buttons::NONE, "the other port is untouched");
}
#[test]
fn every_sink_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<KeyboardSink>();
assert_send_sync::<Feed>();
#[cfg(feature = "dev-nes-io")]
assert_send_sync::<PadSink>();
}
}