#![cfg(target_os = "linux")]
use crate::api::common_capnp;
use crate::api::Endpoint;
use crate::api::UhidInfo;
use crate::mailbox;
use crate::module::vhid;
use hid_io_protocol::HidIoCommandId;
use libc::{c_int, c_short, c_ulong, c_void};
use std::io::{Error, ErrorKind};
use std::os::unix::io::AsRawFd;
fn default_output_event(
output_event: Result<uhid_virt::OutputEvent, uhid_virt::StreamError>,
params: uhid_virt::CreateParams,
) -> Result<(), Error> {
match output_event {
Ok(event) => match event {
uhid_virt::OutputEvent::Start { dev_flags } => {
let mut flags: String = "".to_string();
for flag in dev_flags {
match flag {
uhid_virt::DevFlags::FeatureReportsNumbered => {
flags += "FeatureReportsNumbered,"
}
uhid_virt::DevFlags::InputReportsNumbered => {
flags += "InputReportsNumbered,"
}
uhid_virt::DevFlags::OutputReportsNumbered => {
flags += "OutputReportsNumbered,"
}
}
}
debug!("Start({}): dev_flags={}", params.name, flags);
Ok(())
}
uhid_virt::OutputEvent::Stop => {
debug!("Stop({})", params.name);
Ok(())
}
uhid_virt::OutputEvent::Open => {
debug!("Open({})", params.name);
Ok(())
}
uhid_virt::OutputEvent::Close => {
debug!("Close({})", params.name);
Ok(())
}
uhid_virt::OutputEvent::Output { data } => {
debug!("Output({}): {:?}", params.name, data);
Ok(())
}
uhid_virt::OutputEvent::GetReport {
id,
report_number,
report_type,
} => {
warn!(
"GetReport({}): id={} report_number={} report_type={:?}",
params.name, id, report_number, report_type
);
Ok(())
}
uhid_virt::OutputEvent::SetReport {
id,
report_number,
report_type,
data,
} => {
warn!(
"SetReport({}): id={} report_number={} report_type={:?} data={:?}",
params.name, id, report_number, report_type, data
);
Ok(())
}
},
Err(msg) => {
match msg {
uhid_virt::StreamError::Io(err) => Err(err),
uhid_virt::StreamError::UnknownEventType(code) => Err(Error::new(
ErrorKind::Other,
format!("Unknown error code: {code}"),
)),
}
}
}
}
pub struct KeyboardNkro {
mailbox: mailbox::Mailbox,
uid: u64,
_endpoint: Endpoint,
params: uhid_virt::CreateParams,
device: uhid_virt::UHIDDevice<std::fs::File>,
}
impl KeyboardNkro {
#![allow(clippy::too_many_arguments)]
pub fn new(
mailbox: mailbox::Mailbox,
name: String,
phys: String,
uniq: String,
bus: uhid_virt::Bus,
vendor: u32,
product: u32,
version: u32,
country: u32,
) -> std::io::Result<KeyboardNkro> {
let params = uhid_virt::CreateParams {
name,
phys,
uniq,
bus,
vendor,
product,
version,
country,
rd_data: vhid::KEYBOARD_NKRO.to_vec(),
};
let device = uhid_virt::UHIDDevice::create(params.clone())?;
let path = "/dev/uhid".to_string();
let mut uhid_info = UhidInfo::new(params.clone());
let uid = mailbox.clone().assign_uid(uhid_info.key(), path).unwrap();
let mut endpoint = Endpoint::new(common_capnp::NodeType::HidKeyboard, uid);
endpoint.set_uhid_params(uhid_info);
mailbox.clone().register_node(endpoint.clone());
Ok(KeyboardNkro {
mailbox,
uid,
_endpoint: endpoint,
params,
device,
})
}
pub fn send(&mut self, keyboard_hid_codes: Vec<u8>) -> Result<(), Error> {
let mut data = vec![0; 28];
for key in &keyboard_hid_codes {
match key {
224..=231 => {
data[0] |= 1 << (key ^ 0xE0);
}
4..=164 | 176..=221 => {
let byte_pos = key / 8; let bit_mask = 1 << (key - 8 * byte_pos); data[byte_pos as usize + 1] |= bit_mask; }
_ => {}
};
}
debug!("NKRO: {:?}", data);
match self.device.write(&data) {
Ok(_) => Ok(()),
Err(msg) => Err(msg),
}
}
pub fn process(&mut self) -> Result<(), Error> {
let output_event = self.device.read();
if let Ok(uhid_virt::OutputEvent::Output { data }) = &output_event {
self.mailbox
.try_send_command(
mailbox::Address::DeviceHid { uid: self.uid },
mailbox::Address::All,
HidIoCommandId::HidKeyboardLed,
data.to_vec(),
false,
)
.unwrap();
}
default_output_event(output_event, self.params.clone())
}
}
impl Drop for KeyboardNkro {
fn drop(&mut self) {
self.mailbox.unregister_node(self.uid);
}
}
pub struct Keyboard6kro {
mailbox: mailbox::Mailbox,
uid: u64,
_endpoint: Endpoint,
params: uhid_virt::CreateParams,
device: uhid_virt::UHIDDevice<std::fs::File>,
}
impl Keyboard6kro {
#![allow(clippy::too_many_arguments)]
pub fn new(
mailbox: mailbox::Mailbox,
name: String,
phys: String,
uniq: String,
bus: uhid_virt::Bus,
vendor: u32,
product: u32,
version: u32,
country: u32,
) -> std::io::Result<Keyboard6kro> {
let params = uhid_virt::CreateParams {
name,
phys,
uniq,
bus,
vendor,
product,
version,
country,
rd_data: vhid::KEYBOARD_6KRO.to_vec(),
};
let device = uhid_virt::UHIDDevice::create(params.clone())?;
let path = "/dev/uhid".to_string();
let mut uhid_info = UhidInfo::new(params.clone());
let uid = mailbox.clone().assign_uid(uhid_info.key(), path).unwrap();
let mut endpoint = Endpoint::new(common_capnp::NodeType::HidKeyboard, uid);
endpoint.set_uhid_params(uhid_info);
mailbox.clone().register_node(endpoint.clone());
Ok(Keyboard6kro {
mailbox,
uid,
_endpoint: endpoint,
params,
device,
})
}
pub fn send(&mut self, keyboard_hid_codes: Vec<u8>) -> Result<(), Error> {
let mut data = vec![0; 8];
let mut key_pos = 2;
for key in &keyboard_hid_codes {
match key {
224..=231 => {
data[0] |= 1 << (key ^ 0xE0);
}
4..=164 | 176..=221 => {
if key_pos < 8 {
data[key_pos] = *key;
key_pos += 1;
}
}
_ => {}
};
}
debug!("6KRO: {:?}", data);
match self.device.write(&data) {
Ok(_) => Ok(()),
Err(msg) => Err(msg),
}
}
pub fn process(&mut self) -> Result<(), Error> {
let output_event = self.device.read();
if let Ok(uhid_virt::OutputEvent::Output { data }) = &output_event {
self.mailbox
.try_send_command(
mailbox::Address::DeviceHid { uid: self.uid },
mailbox::Address::All,
HidIoCommandId::HidKeyboardLed,
data.to_vec(),
false,
)
.unwrap();
}
default_output_event(output_event, self.params.clone())
}
}
impl Drop for Keyboard6kro {
fn drop(&mut self) {
self.mailbox.unregister_node(self.uid);
}
}
pub async fn initialize(_mailbox: mailbox::Mailbox) {
info!("Initializing vhid/uhid...");
}
#[allow(dead_code)]
#[repr(C)]
struct pollfd {
fd: c_int,
events: c_short,
revents: c_short,
}
#[repr(C)]
struct sigset_t {
__private: c_void,
}
#[allow(non_camel_case_types)]
type nfds_t = c_ulong;
const POLLIN: c_short = 0x0001;
extern "C" {
fn ppoll(
fds: *mut pollfd,
nfds: nfds_t,
timeout_ts: *mut libc::timespec,
sigmask: *const sigset_t,
) -> c_int;
}
pub fn udev_find_device(
vid: u16,
pid: u16,
subsystem: String,
uniq: String,
timeout: std::time::Duration,
) -> Result<udev::Device, std::io::Error> {
let mut enumerator = udev::Enumerator::new().unwrap();
enumerator.match_subsystem("input").unwrap();
enumerator
.match_attribute("id/vendor", format!("{:04x}", vhid::IC_VID))
.unwrap();
enumerator
.match_attribute("id/product", format!("{:04x}", vhid::IC_PID_KEYBOARD))
.unwrap();
enumerator.match_attribute("uniq", uniq.clone()).unwrap();
let mut devices = enumerator.scan_devices().unwrap();
if let Some(device) = devices.next() {
return Ok(device);
}
let mut socket = udev::MonitorBuilder::new()
.unwrap()
.match_subsystem(subsystem)
.unwrap()
.listen()
.unwrap();
let mut fds = vec![pollfd {
fd: socket.as_raw_fd(),
events: POLLIN,
revents: 0,
}];
let mut ptimeout = libc::timespec {
tv_sec: 1,
tv_nsec: 0,
};
let start_time = std::time::Instant::now();
while start_time.elapsed() < timeout {
let result = unsafe {
ppoll(
fds[..].as_mut_ptr(),
fds.len() as nfds_t,
&mut ptimeout,
std::ptr::null(),
)
};
if result < 0 {
panic!("Error: {}", std::io::Error::last_os_error());
}
let event = match socket.next() {
Some(evt) => evt,
None => {
std::thread::sleep(std::time::Duration::from_millis(10));
continue;
}
};
if event.event_type() == udev::EventType::Add || event.event_type() == udev::EventType::Bind
{
if let Some(parent) = event.parent() {
let found_vid = parent
.attribute_value("id/vendor")
.unwrap_or_else(|| std::ffi::OsStr::new(""))
.to_str()
.unwrap();
let found_pid = parent
.attribute_value("id/product")
.unwrap_or_else(|| std::ffi::OsStr::new(""))
.to_str()
.unwrap();
let found_uniq = parent
.attribute_value("uniq")
.unwrap_or_else(|| std::ffi::OsStr::new(""))
.to_str()
.unwrap();
if found_vid == format!("{vid:04x}")
&& found_pid == format!("{pid:04x}")
&& found_uniq == uniq
{
return Ok(event.device());
}
}
}
}
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Could not locate udev device",
))
}
#[cfg(test)]
mod test {
use super::*;
use crate::device::evdev;
use crate::logging::setup_logging_lite;
use std::sync::{Arc, RwLock};
#[test]
#[ignore]
fn uhid_keyboard_nkro_test() {
setup_logging_lite().ok();
let name = "uhid-keyboard-nkro-test".to_string();
let mailbox = mailbox::Mailbox {
..Default::default()
};
*mailbox.last_uid.write().unwrap() = 20;
let uniq = nanoid::nanoid!();
let mut keyboard = KeyboardNkro::new(
mailbox.clone(),
name,
"".to_string(),
uniq.clone(),
uhid_virt::Bus::USB,
vhid::IC_VID as u32,
vhid::IC_PID_KEYBOARD as u32,
0,
0,
)
.unwrap();
let device = match evdev::udev_find_input_event_device(
vhid::IC_VID,
vhid::IC_PID_KEYBOARD,
"input".to_string(),
uniq,
std::time::Duration::new(10, 0),
) {
Ok(device) => device,
Err(err) => {
panic!("Could not find udev device... {}", err);
}
};
while !device.is_initialized() {} let fd_path = format!("/dev/input/{}", device.sysname().to_str().unwrap());
let mut receiver = mailbox.sender.subscribe();
let rt = tokio::runtime::Runtime::new().unwrap();
let status: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
let status2 = status.clone();
rt.spawn(async move {
let expected_codes = vec![
225, 226, 227, 228, 229, 230, 231, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
loop {
match receiver.recv().await {
Ok(msg) => {
if !*status.clone().read().unwrap() {
let mut code_pos = 0;
for code in msg.data.data {
if code != expected_codes[code_pos] {
error!("{} != {}", code, expected_codes[code_pos]);
break;
}
code_pos += 1;
if code_pos >= expected_codes.len() {
*(status.clone().write().unwrap()) = true;
}
}
}
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
panic!("Mailbox has been closed unexpectedly!");
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
panic!(
"Mailbox has received too many messages, lagging by: {}",
skipped
);
}
};
}
});
rt.spawn(async move {
tokio::task::spawn_blocking(move || {
evdev::EvdevDevice::new(mailbox.clone(), fd_path)
.unwrap()
.process()
.unwrap();
});
});
rt.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
keyboard
.send(vec![
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6,
0xE7,
])
.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
keyboard.send(vec![]).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
});
rt.shutdown_timeout(std::time::Duration::from_millis(100));
let status: bool = *status2.read().unwrap();
assert!(status, "Test failed");
}
#[test]
#[ignore]
fn uhid_keyboard_6kro_test() {
setup_logging_lite().ok();
let name = "uhid-keyboard-6kro-test".to_string();
let mailbox = mailbox::Mailbox {
..Default::default()
};
*mailbox.last_uid.write().unwrap() = 30;
let uniq = nanoid::nanoid!();
let mut keyboard = Keyboard6kro::new(
mailbox.clone(),
name,
"".to_string(),
uniq.clone(),
uhid_virt::Bus::USB,
vhid::IC_VID as u32,
vhid::IC_PID_KEYBOARD as u32,
0,
0,
)
.unwrap();
let device = match evdev::udev_find_input_event_device(
vhid::IC_VID,
vhid::IC_PID_KEYBOARD,
"input".to_string(),
uniq,
std::time::Duration::new(10, 0),
) {
Ok(device) => device,
Err(err) => {
panic!("Could not find udev device... {}", err);
}
};
while !device.is_initialized() {} let fd_path = format!("/dev/input/{}", device.sysname().to_str().unwrap());
let mut receiver = mailbox.sender.subscribe();
let rt = tokio::runtime::Runtime::new().unwrap();
let status: Arc<RwLock<bool>> = Arc::new(RwLock::new(false));
let status2 = status.clone();
rt.spawn(async move {
let expected_codes = vec![225, 226, 227, 228, 229, 230, 231, 4, 5, 6, 7, 8, 9];
loop {
match receiver.recv().await {
Ok(msg) => {
if !*status.clone().read().unwrap() {
let mut code_pos = 0;
for code in msg.data.data {
if code != expected_codes[code_pos] {
error!("{} != {}", code, expected_codes[code_pos]);
break;
}
code_pos += 1;
if code_pos >= expected_codes.len() {
*(status.clone().write().unwrap()) = true;
}
}
}
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
panic!("Mailbox has been closed unexpectedly!");
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
panic!(
"Mailbox has received too many messages, lagging by: {}",
skipped
);
}
};
}
});
rt.spawn(async move {
tokio::task::spawn_blocking(move || {
evdev::EvdevDevice::new(mailbox.clone(), fd_path)
.unwrap()
.process()
.unwrap();
});
});
rt.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
keyboard
.send(vec![
4, 5, 6, 7, 8, 9, 10, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
])
.unwrap();
keyboard.send(vec![]).unwrap();
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
});
rt.shutdown_timeout(std::time::Duration::from_millis(100));
let status: bool = *status2.read().unwrap();
assert!(status, "Test failed");
}
}