Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement get_sys_path() and dev node iterator for virtual devices #72

Merged
merged 8 commits into from
Aug 3, 2022
11 changes: 11 additions & 0 deletions src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ ioctl_write_buf!(ui_set_phys, UINPUT_IOCTL_BASE, 108, u8);
ioctl_write_int!(ui_set_swbit, UINPUT_IOCTL_BASE, 109);
ioctl_write_int!(ui_set_propbit, UINPUT_IOCTL_BASE, 110);

pub unsafe fn ui_get_sysname(
fd: ::libc::c_int,
bytes: &mut [u8],
) -> ::nix::Result<c_int> {
convert_ioctl_res!(::nix::libc::ioctl(
fd,
request_code_read!(UINPUT_IOCTL_BASE, 300, bytes.len()),
bytes.as_mut_ptr(),
))
}

macro_rules! eviocgbit_ioctl {
($mac:ident!($name:ident, $ev:ident, $ty:ty)) => {
eviocgbit_ioctl!($mac!($name, $crate::EventType::$ev.0, $ty));
Expand Down
134 changes: 134 additions & 0 deletions src/uinput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ use libc::{O_NONBLOCK, uinput_abs_setup};
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd};
use std::path::PathBuf;

const UINPUT_PATH: &str = "/dev/uinput";
const SYSFS_PATH: &str = "/sys/devices/virtual/input";
const DEV_PATH: &str = "/dev/input";

#[derive(Debug)]
pub struct VirtualDeviceBuilder<'a> {
Expand Down Expand Up @@ -175,6 +178,47 @@ impl VirtualDevice {
self.file.write_all(bytes)
}

/// Get the syspath representing this uinput device.
///
/// The syspath returned is the one of the input node itself (e.g.
/// `/sys/devices/virtual/input/input123`), not the syspath of the device node.
pub fn get_syspath(&mut self) -> io::Result<PathBuf> {
let mut bytes = vec![0u8; 256];
unsafe { sys::ui_get_sysname(self.file.as_raw_fd(), &mut bytes)? };

if let Some(end) = bytes.iter().position(|c| *c == 0) {
bytes.truncate(end);
}

let s = std::str::from_utf8(&bytes).expect("invalid sys path");
jeff-hiner marked this conversation as resolved.
Show resolved Hide resolved
let mut path = PathBuf::from(SYSFS_PATH);
path.push(s);

Ok(path)
}

/// Get the syspaths of the corresponding device nodes in /dev/input.
#[cfg(not(feature = "tokio"))]
pub fn enumerate_dev_nodes(&mut self) -> io::Result<DevNodes> {
StephanvanSchaik marked this conversation as resolved.
Show resolved Hide resolved
let path = self.get_syspath()?;
let dir = std::fs::read_dir(path)?;

Ok(DevNodes {
dir,
})
}

/// Get the syspaths of the corresponding device nodes in /dev/input.
#[cfg(feature = "tokio")]
pub async fn enumerate_dev_nodes(&mut self) -> io::Result<DevNodes> {
let path = self.get_syspath()?;
let dir = tokio_1::fs::read_dir(path).await?;

Ok(DevNodes {
dir,
})
}

/// Post a batch of events to the virtual device.
///
/// The batch is automatically terminated with a `SYN_REPORT` event.
Expand All @@ -188,3 +232,93 @@ impl VirtualDevice {
self.write_raw(&[syn])
}
}

/// This struct is returned from the [VirtualDevice::enumerate_dev_nodes] function and will yield
/// the syspaths corresponding to the virtual device. These are of the form `/dev/input123`.
#[cfg(not(feature = "tokio"))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of a further nit, but can we either ungate this or gate this behind a blocking feature? In a single binary if one crate user were to define tokio and another does not it'll really confuse cargo. I'd like to make sure that if someone really wants to they could build and use both simultaneously.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, it makes sense to not even have the feature gate there if we are differentiating them anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to somehow disambiguate the two DevNodes structs as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. I had a much better look now and fixed a few other things too:

  • Added fs feature flag to the tokio dependency, so cargo b --features tokio actually works.
  • Because of the .await?, the async version of the iterator is dealing with just Option<T> which simplifies the code.
  • Renamed the DevNodes variant for the sync version to DevNodesBlocking so they can co-exist.

With the tokio feature flag, one should have access to both the sync and async versions.

pub struct DevNodes {
dir: std::fs::ReadDir,
}

#[cfg(not(feature = "tokio"))]
impl Iterator for DevNodes {
type Item = io::Result<PathBuf>;

fn next(&mut self) -> Option<Self::Item> {
jeff-hiner marked this conversation as resolved.
Show resolved Hide resolved
loop {
let entry = match self.dir.next() {
Some(entry) => entry,
_ => return None,
};

let entry = match entry {
Ok(entry) => entry,
Err(e) => return Some(Err(e)),
};

let path = entry.path();

let name = match path.file_name() {
Some(name) => name,
_ => continue,
};

let name = match name.to_str() {
Some(name) => name,
_ => continue,
};

if !name.starts_with("event") {
continue;
}

let mut path = PathBuf::from(DEV_PATH);
path.push(name);

return Some(Ok(path));
}
}
}

/// This struct is returned from the [VirtualDevice::enumerate_dev_nodes] function and will yield
/// the syspaths corresponding to the virtual device. These are of the form `/dev/input123`.
#[cfg(feature = "tokio")]
pub struct DevNodes {
dir: tokio_1::fs::ReadDir,
}

#[cfg(feature = "tokio")]
impl DevNodes {
/// Returns the next entry in the set of device nodes.
pub async fn next_entry(&mut self) -> io::Result<Option<PathBuf>> {
loop {
let entry = self.dir.next_entry().await?;

let entry = match entry {
Some(entry) => entry,
None => return Ok(None),
};

let path = entry.path();

let name = match path.file_name() {
Some(name) => name,
_ => continue,
};

let name = match name.to_str() {
Some(name) => name,
_ => continue,
};

if !name.starts_with("event") {
continue;
}

let mut path = PathBuf::from(DEV_PATH);
path.push(name);

return Ok(Some(path));
}
}
}