use epoll::ControlOptions::{EPOLL_CTL_ADD, EPOLL_CTL_DEL};
pub use evdev_rs::InputEvent;
use evdev_rs::{Device, UInputDevice};
use inotify::{Inotify, WatchMask};
use std::collections::BTreeMap;
use std::convert::{TryFrom, TryInto};
use std::ffi::{OsStr, OsString};
use std::fs::{read_dir, File};
use std::io;
use std::os::unix::{
ffi::OsStrExt,
fs::FileTypeExt,
io::{AsRawFd, IntoRawFd, RawFd},
};
use std::path::Path;
use std::time::Instant;
static DEV_PATH: &str = "/dev/input";
const INOTIFY_DATA: u64 = u64::max_value();
const EPOLLIN: epoll::Events = epoll::Events::EPOLLIN;
#[derive(Debug, Eq, PartialEq, Hash)]
pub enum GrabStatus {
Continue,
Stop,
}
fn get_device_files<T>(path: T) -> io::Result<Vec<File>>
where
T: AsRef<Path>,
{
let mut res = Vec::new();
for entry in read_dir(path)? {
let entry = entry?;
if !entry.file_type()?.is_char_device() {
continue;
}
let path = entry.path();
let file_name_bytes = match path.file_name() {
Some(file_name) => file_name.as_bytes(),
None => continue,
};
if file_name_bytes == OsStr::new("mice").as_bytes()
|| file_name_bytes
.get(0..=1)
.map(|s| s == OsStr::new("js").as_bytes())
.unwrap_or(false)
|| file_name_bytes
.get(0..=4)
.map(|s| s == OsStr::new("mouse").as_bytes())
.unwrap_or(false)
{
continue;
}
res.push(File::open(path)?);
}
Ok(res)
}
fn epoll_watch_all<'a, T>(device_files: T) -> io::Result<RawFd>
where
T: Iterator<Item = &'a File>,
{
let epoll_fd = epoll::create(true)?;
for (file_idx, file) in device_files.enumerate() {
let epoll_event = epoll::Event::new(EPOLLIN, file_idx as u64);
epoll::ctl(epoll_fd, EPOLL_CTL_ADD, file.as_raw_fd(), epoll_event)?;
}
Ok(epoll_fd)
}
fn inotify_devices() -> io::Result<Inotify> {
let mut inotify = Inotify::init()?;
inotify.add_watch(DEV_PATH, WatchMask::CREATE)?;
Ok(inotify)
}
fn add_device_to_epoll_from_inotify_event(
epoll_fd: RawFd,
event: inotify::Event<&OsStr>,
devices: &mut Vec<Device>,
) -> io::Result<()> {
let mut device_path = OsString::from(DEV_PATH);
device_path.push(OsString::from("/"));
device_path.push(event.name.unwrap());
let file = File::open(device_path)?;
let fd = file.as_raw_fd();
let device = Device::new_from_fd(file)?;
let event = epoll::Event::new(EPOLLIN, devices.len() as u64);
devices.push(device);
epoll::ctl(epoll_fd, EPOLL_CTL_ADD, fd, event)?;
Ok(())
}
fn setup_devices() -> io::Result<(RawFd, Vec<Device>, Vec<UInputDevice>)> {
let device_files = get_device_files(DEV_PATH)?;
let epoll_fd = epoll_watch_all(device_files.iter())?;
let devices = device_files
.into_iter()
.map(|file| Device::new_from_fd(file))
.collect::<io::Result<Vec<Device>>>()?;
let output_devices = devices
.iter()
.map(|device| UInputDevice::create_from_device(device))
.collect::<io::Result<Vec<UInputDevice>>>()?;
Ok((epoll_fd, devices, output_devices))
}
fn setup_inotify(epoll_fd: RawFd, devices: &Vec<Device>) -> io::Result<Inotify> {
if u64::try_from(devices.len()).unwrap_or(u64::max_value()) >= INOTIFY_DATA {
eprintln!("number of devices: {}", devices.len());
Err(io::Error::new(
io::ErrorKind::Other,
"too many device files!",
))?
}
let inotify = inotify_devices()?;
let epoll_event = epoll::Event::new(EPOLLIN, INOTIFY_DATA);
epoll::ctl(epoll_fd, EPOLL_CTL_ADD, inotify.as_raw_fd(), epoll_event)?;
Ok(inotify)
}
pub fn filter_map_events_with_delay_noreturn<F>(mut func: F) -> io::Result<()>
where
F: FnMut(InputEvent) -> (Instant, Option<InputEvent>),
{
filter_map_events_with_delay(|input_event| {
let (instant, output_event) = func(input_event);
(instant, output_event, GrabStatus::Continue)
})
}
pub fn filter_map_events_with_delay<F>(mut func: F) -> io::Result<()>
where
F: FnMut(InputEvent) -> (Instant, Option<InputEvent>, GrabStatus),
{
let (epoll_fd, mut devices, output_devices) = setup_devices()?;
let mut inotify = setup_inotify(epoll_fd, &devices)?;
let _grab = devices
.iter_mut()
.map(|device| device.grab(evdev_rs::GrabMode::Grab))
.collect::<io::Result<()>>()?;
let mut epoll_buffer = [epoll::Event::new(epoll::Events::empty(), 0); 4];
let mut inotify_buffer = vec![0_u8; 4096];
let mut events_buffer =
BTreeMap::<Instant, (Option<(Instant, InputEvent, usize)>, GrabStatus)>::new();
'event_loop: loop {
let process_later = events_buffer.split_off(&Instant::now());
let process_now = events_buffer;
events_buffer = process_later;
for (opt, grab_status) in process_now.values() {
for (recieved_inst, event, idx) in opt {
println!(
"Actual time waited: {}ms",
recieved_inst.elapsed().as_millis()
);
let output_device = match output_devices.get(*idx) {
Some(out_dev) => out_dev,
None => continue,
};
output_device.write_event(event)?;
}
if grab_status == &GrabStatus::Stop {
break 'event_loop;
}
}
let wait_ms: i32 = events_buffer
.keys()
.nth(0)
.and_then(|sim_inst| {
sim_inst
.checked_duration_since(Instant::now())
.unwrap_or_default()
.as_millis()
.checked_add(1)?
.try_into()
.ok()
})
.unwrap_or(-1i32);
println!("Waiting for {}ms for new events...", wait_ms);
let num_events = epoll::wait(epoll_fd, wait_ms, &mut epoll_buffer)?;
'events: for event in epoll_buffer.get(0..num_events).unwrap() {
if event.data == INOTIFY_DATA {
for event in inotify.read_events(&mut inotify_buffer)? {
assert!(event.mask.contains(inotify::EventMask::CREATE),
"inotify is listening for events other than file creation");
add_device_to_epoll_from_inotify_event(epoll_fd, event, &mut devices)?;
}
} else {
let device_idx = event.data as usize;
let device = devices.get(device_idx).unwrap();
while device.has_event_pending() {
let (_, event) = match device.next_event(evdev_rs::ReadFlag::NORMAL) {
Ok(event) => event,
Err(_) => {
let device_fd = device.fd().unwrap().into_raw_fd();
let empty_event = epoll::Event::new(epoll::Events::empty(), 0);
epoll::ctl(epoll_fd, EPOLL_CTL_DEL, device_fd, empty_event)?;
continue 'events;
}
};
let (instant, opt_event, grab_status) = func(event);
let value = opt_event.map(|event| (Instant::now(), event, device_idx));
events_buffer.insert(instant, (value, grab_status));
}
}
}
}
for device in devices.iter_mut() {
device.grab(evdev_rs::GrabMode::Ungrab).ok();
}
epoll::close(epoll_fd)?;
Ok(())
}
pub fn filter_map_events_noreturn<F>(func: F) -> io::Result<()>
where
F: Fn(InputEvent) -> Option<InputEvent>,
{
filter_map_events(|input_event| {
let output_event = func(input_event);
(output_event, GrabStatus::Continue)
})
}
pub fn filter_map_events<F>(mut func: F) -> io::Result<()>
where
F: FnMut(InputEvent) -> (Option<InputEvent>, GrabStatus),
{
let (epoll_fd, mut devices, output_devices) = setup_devices()?;
let mut inotify = setup_inotify(epoll_fd, &devices)?;
let _grab = devices
.iter_mut()
.map(|device| device.grab(evdev_rs::GrabMode::Grab))
.collect::<io::Result<()>>()?;
let mut epoll_buffer = [epoll::Event::new(epoll::Events::empty(), 0); 4];
let mut inotify_buffer = vec![0_u8; 4096];
'event_loop: loop {
let num_events = epoll::wait(epoll_fd, -1, &mut epoll_buffer)?;
'events: for event in &epoll_buffer[0..num_events] {
if event.data == INOTIFY_DATA {
for event in inotify.read_events(&mut inotify_buffer)? {
assert!(event.mask.contains(inotify::EventMask::CREATE),
"inotify is listening for events other than file creation");
add_device_to_epoll_from_inotify_event(epoll_fd, event, &mut devices)?;
}
} else {
let device_idx = event.data as usize;
let device = devices.get(device_idx).unwrap();
while device.has_event_pending() {
let (_, event) = match device.next_event(evdev_rs::ReadFlag::NORMAL) {
Ok(event) => event,
Err(_) => {
let device_fd = device.fd().unwrap().into_raw_fd();
let empty_event = epoll::Event::new(epoll::Events::empty(), 0);
epoll::ctl(epoll_fd, EPOLL_CTL_DEL, device_fd, empty_event)?;
continue 'events;
}
};
let (event, grab_status) = func(event);
match (event, output_devices.get(device_idx)) {
(Some(event), Some(out_device)) => out_device.write_event(&event)?,
_ => {}
}
if grab_status == GrabStatus::Stop {
break 'event_loop;
}
}
}
}
}
for device in devices.iter_mut() {
device.grab(evdev_rs::GrabMode::Ungrab).ok();
}
epoll::close(epoll_fd)?;
Ok(())
}