Make epoll and inotify optional dependencies enabled by default with the epoll_inotify feature
moparisthebest/rusty-keys/pipeline/head This commit looks good Details

This commit is contained in:
Travis Burtrum 2021-09-24 01:03:27 -04:00
parent c420323cdf
commit 60a3f24c86
3 changed files with 93 additions and 81 deletions

View File

@ -32,5 +32,9 @@ lazy_static = "1.4.0"
[target.'cfg(target_os="linux")'.dependencies] [target.'cfg(target_os="linux")'.dependencies]
libc = "0.2.102" libc = "0.2.102"
nix = "0.22.1" nix = "0.22.1"
epoll = "4.3.1" epoll = { version = "4.3.1", optional = true }
inotify = { version = "0.9.3", default-features = false, features = [] } inotify = { version = "0.9.3", default-features = false, features = [], optional = true }
[features]
default = ["epoll_inotify"]
epoll_inotify = ["epoll", "inotify"]

View File

@ -5,9 +5,11 @@ use std::os::unix::io::AsRawFd;
use std::os::unix::prelude::RawFd; use std::os::unix::prelude::RawFd;
use libc::{input_event, c_int}; use libc::{input_event, c_int};
use nix::{ioctl_write_ptr, ioctl_read_buf}; use nix::{ioctl_write_ptr, ioctl_read_buf};
use epoll::ControlOptions::{EPOLL_CTL_ADD, EPOLL_CTL_DEL};
use nix::fcntl::{OFlag, fcntl, FcntlArg}; use nix::fcntl::{OFlag, fcntl, FcntlArg};
#[cfg(feature = "epoll_inotify")]
use epoll::ControlOptions::{EPOLL_CTL_ADD, EPOLL_CTL_DEL};
use crate::{Error,Result}; use crate::{Error,Result};
use crate::linux::{EV_KEY, KEY_MAX, NAME, KEY_W, KEY_A, KEY_S, KEY_D}; use crate::linux::{EV_KEY, KEY_MAX, NAME, KEY_W, KEY_A, KEY_S, KEY_D};
@ -105,6 +107,7 @@ impl InputDevice {
Ok(()) Ok(())
} }
#[cfg(feature = "epoll_inotify")]
pub fn epoll_add(mut self, epoll_fd: RawFd, data: u64) -> Result<Self> { pub fn epoll_add(mut self, epoll_fd: RawFd, data: u64) -> Result<Self> {
if None != self.epoll_fd { if None != self.epoll_fd {
return Err(Error::EpollAlreadyAdded); return Err(Error::EpollAlreadyAdded);
@ -122,6 +125,7 @@ impl InputDevice {
Ok(self) Ok(self)
} }
#[cfg(feature = "epoll_inotify")]
pub fn epoll_del(&mut self) -> Result<&mut Self> { pub fn epoll_del(&mut self) -> Result<&mut Self> {
if let Some(epoll_fd) = self.epoll_fd { if let Some(epoll_fd) = self.epoll_fd {
// set this to None first, if we end up returning an Err early, we can't do anything else anyway... // set this to None first, if we end up returning an Err early, we can't do anything else anyway...
@ -137,6 +141,7 @@ impl Drop for InputDevice {
fn drop(&mut self) { fn drop(&mut self) {
// ignore any errors here, what could we do anyhow? // ignore any errors here, what could we do anyhow?
self.release().ok(); self.release().ok();
#[cfg(feature = "epoll_inotify")]
self.epoll_del().ok(); self.epoll_del().ok();
} }
} }

View File

@ -9,6 +9,7 @@ pub use device::{Device,InputDevice, Builder};
use libc::input_event; use libc::input_event;
use std::process::exit; use std::process::exit;
use std::env; use std::env;
use std::collections::HashMap;
const INPUT_FOLDER: &str = "/dev/input/"; const INPUT_FOLDER: &str = "/dev/input/";
@ -18,12 +19,6 @@ const UP: i32 = 0;
use getopts::Options; use getopts::Options;
use inotify::{
Inotify,
WatchMask,
};
use std::collections::HashMap;
const EV_KEY_U16: u16 = EV_KEY as u16; const EV_KEY_U16: u16 = EV_KEY as u16;
type LinuxKeyMaps = KeyMaps<Device, u16, input_event>; type LinuxKeyMaps = KeyMaps<Device, u16, input_event>;
@ -127,87 +122,96 @@ pub fn main_res() -> Result<()> {
send_event(&mut key_map, event, &device)? send_event(&mut key_map, event, &device)?
} }
} else { } else {
let epoll_fd = epoll::create(true)?; #[cfg(not(feature = "epoll_inotify"))]
const INOTIFY_DATA: u64 = u64::max_value(); panic!("without epoll_inotify feature, only exactly 1 device is supported");
let (device_files, mut inotify) = if config.device_files.len() > 0 { #[cfg(feature = "epoll_inotify")]
// we operate on exactly the devices sent in and never watch for new devices {
(config.device_files.iter().map(|device_file| InputDevice::open(&device_file).expect("device_file does not exist!")).collect(), None) use inotify::{Inotify, WatchMask};
} else {
use std::os::unix::io::AsRawFd;
// we want to wait forever starting new threads for any new keyboard devices
// there is a slight race condition here, if a keyboard is plugged in between the time we
// enumerate the devices and set up the inotify watch, we'll miss it, doing it the other way
// can bring duplicates though, todo: think about this...
let device_files = get_keyboard_devices();
let mut inotify = Inotify::init()?;
inotify.add_watch(INPUT_FOLDER, WatchMask::CREATE)?;
let epoll_event = epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLET, INOTIFY_DATA);
epoll::ctl(epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, inotify.as_raw_fd(), epoll_event)?;
(device_files, Some(inotify))
};
let mut input_devices = Vec::with_capacity(device_files.len());
for (idx, device_file) in device_files.into_iter().enumerate() {
input_devices.push(Some(device_file.grab()?.epoll_add(epoll_fd, idx as u64)?));
}
let mut epoll_buf = [epoll::Event::new(epoll::Events::empty(), 0); 4]; let epoll_fd = epoll::create(true)?;
let mut inotify_buf = [0u8; 256]; const INOTIFY_DATA: u64 = u64::max_value();
loop { let (device_files, mut inotify) = if config.device_files.len() > 0 {
let num_events = epoll::wait(epoll_fd, -1, &mut epoll_buf)?; // we operate on exactly the devices sent in and never watch for new devices
for event in &epoll_buf[0..num_events] { (config.device_files.iter().map(|device_file| InputDevice::open(&device_file).expect("device_file does not exist!")).collect(), None)
let idx = event.data as usize; } else {
if let Some(Some(input_device)) = &mut input_devices.get_mut(idx) { use std::os::unix::io::AsRawFd;
loop { // we want to wait forever starting new threads for any new keyboard devices
match input_device.read_event(&mut input_event_buf) { // there is a slight race condition here, if a keyboard is plugged in between the time we
Ok(event) => { // enumerate the devices and set up the inotify watch, we'll miss it, doing it the other way
//println!("input event: {:?}", event); // can bring duplicates though, todo: think about this...
send_event(&mut key_map, event, &device)? let device_files = get_keyboard_devices();
} let mut inotify = Inotify::init()?;
Err(err) => { inotify.add_watch(INPUT_FOLDER, WatchMask::CREATE)?;
if let Error::Io(ref err) = err { let epoll_event = epoll::Event::new(epoll::Events::EPOLLIN | epoll::Events::EPOLLET, INOTIFY_DATA);
if err.kind() == std::io::ErrorKind::WouldBlock { epoll::ctl(epoll_fd, epoll::ControlOptions::EPOLL_CTL_ADD, inotify.as_raw_fd(), epoll_event)?;
// go back to epoll event loop (device_files, Some(inotify))
};
let mut input_devices = Vec::with_capacity(device_files.len());
for (idx, device_file) in device_files.into_iter().enumerate() {
input_devices.push(Some(device_file.grab()?.epoll_add(epoll_fd, idx as u64)?));
}
let mut epoll_buf = [epoll::Event::new(epoll::Events::empty(), 0); 4];
let mut inotify_buf = [0u8; 256];
loop {
let num_events = epoll::wait(epoll_fd, -1, &mut epoll_buf)?;
for event in &epoll_buf[0..num_events] {
let idx = event.data as usize;
if let Some(Some(input_device)) = &mut input_devices.get_mut(idx) {
loop {
match input_device.read_event(&mut input_event_buf) {
Ok(event) => {
//println!("input event: {:?}", event);
send_event(&mut key_map, event, &device)?
}
Err(err) => {
if let Error::Io(ref err) = err {
if err.kind() == std::io::ErrorKind::WouldBlock {
// go back to epoll event loop
break;
}
}
// otherwise it's some other error, don't read anything from this again
println!("input err: {:?}", err);
// remove it from input_devices and drop it
let _ = std::mem::replace(&mut input_devices[idx], None);
if inotify.is_none() {
// if we aren't watching with inotify, and the last device is removed (Vec only has None's in it), exit the program
if input_devices.iter().all(|id| id.is_none()) {
println!("last device went away, exiting...");
return Ok(());
}
}
break; break;
} }
} }
// otherwise it's some other error, don't read anything from this again
println!("input err: {:?}", err);
// remove it from input_devices and drop it
let _ = std::mem::replace(&mut input_devices[idx], None);
if inotify.is_none() {
// if we aren't watching with inotify, and the last device is removed (Vec only has None's in it), exit the program
if input_devices.iter().all(|id| id.is_none()) {
println!("last device went away, exiting...");
return Ok(());
}
}
break;
} }
} } else if event.data == INOTIFY_DATA {
} if let Some(inotify) = &mut inotify {
} else if event.data == INOTIFY_DATA { for event in inotify.read_events(&mut inotify_buf)? {
if let Some(inotify) = &mut inotify { if let Some(device_file) = event.name.and_then(|name| name.to_str()) {
for event in inotify.read_events(&mut inotify_buf)? { // check if this is an eligible keyboard device
if let Some(device_file) = event.name.and_then(|name| name.to_str()) { let mut path = std::path::PathBuf::new();
// check if this is an eligible keyboard device path.push(INPUT_FOLDER);
let mut path = std::path::PathBuf::new(); path.push(device_file);
path.push(INPUT_FOLDER);
path.push(device_file);
if let Ok(input_device) = InputDevice::open(path).and_then(|id|id.valid_keyboard_device()) {
println!("starting mapping for new keyboard: {}", device_file);
let idx = input_devices.iter().position(|id| id.is_none()).unwrap_or(input_devices.len()); if let Ok(input_device) = InputDevice::open(path).and_then(|id| id.valid_keyboard_device()) {
println!("starting mapping for new keyboard: {}", device_file);
let input_device = input_device.grab()?.epoll_add(epoll_fd, idx as u64)?; let idx = input_devices.iter().position(|id| id.is_none()).unwrap_or(input_devices.len());
if idx == input_devices.len() { let input_device = input_device.grab()?.epoll_add(epoll_fd, idx as u64)?;
input_devices.push(Some(input_device));
} else { if idx == input_devices.len() {
// simply replacing None here input_devices.push(Some(input_device));
let _ = std::mem::replace(&mut input_devices[idx], Some(input_device)); } else {
// simply replacing None here
let _ = std::mem::replace(&mut input_devices[idx], Some(input_device));
}
}
} }
} }
} }
@ -215,7 +219,6 @@ pub fn main_res() -> Result<()> {
} }
} }
} }
}
} }
} }