Use RAII wrappers for libevdev objects

This commit is contained in:
Jan Trefil 2023-09-02 09:28:07 +02:00
parent cd77a50cbb
commit 0c16cdd144
5 changed files with 169 additions and 138 deletions

73
rkvm-input/src/evdev.rs Normal file
View file

@ -0,0 +1,73 @@
use crate::glue::{self, libevdev};
use std::fs::File;
use std::io::{Error, ErrorKind};
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::path::Path;
use std::ptr::NonNull;
use tokio::fs::OpenOptions;
use tokio::io::unix::AsyncFd;
pub struct Evdev {
evdev: NonNull<libevdev>,
file: Option<AsyncFd<File>>,
}
impl Evdev {
pub fn new() -> Result<Self, Error> {
let evdev = unsafe { glue::libevdev_new() };
let evdev = NonNull::new(evdev)
.ok_or_else(|| Error::new(ErrorKind::Other, "Failed to create device"))?;
Ok(Self { evdev, file: None })
}
pub async fn open(path: &Path) -> Result<Self, Error> {
let file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.await?
.into_std()
.await;
let file = AsyncFd::new(file)?;
let mut evdev = MaybeUninit::uninit();
let ret = unsafe { glue::libevdev_new_from_fd(file.as_raw_fd(), evdev.as_mut_ptr()) };
if ret < 0 {
return Err(Error::from_raw_os_error(-ret).into());
}
let evdev = unsafe { evdev.assume_init() };
let evdev = unsafe { NonNull::new_unchecked(evdev) };
Ok(Self {
evdev,
file: Some(file),
})
}
pub fn file(&self) -> Option<&AsyncFd<File>> {
self.file.as_ref()
}
pub fn as_ptr(&self) -> *mut libevdev {
self.evdev.as_ptr()
}
}
impl Drop for Evdev {
fn drop(&mut self) {
unsafe {
glue::libevdev_free(self.evdev.as_ptr());
}
}
}
unsafe impl Send for Evdev {}
unsafe impl Sync for Evdev {}

View file

@ -3,8 +3,9 @@ mod caps;
pub use caps::{AbsCaps, KeyCaps, RelCaps};
use crate::abs::{AbsAxis, AbsEvent, ToolType};
use crate::evdev::Evdev;
use crate::event::Event;
use crate::glue::{self, libevdev};
use crate::glue;
use crate::key::{Key, KeyEvent};
use crate::registry::{Entry, Handle, Registry};
use crate::rel::{RelAxis, RelEvent};
@ -13,21 +14,14 @@ use crate::writer::Writer;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::fs::{File, OpenOptions};
use std::io::{Error, ErrorKind};
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::os::unix::fs::MetadataExt;
use std::os::unix::prelude::OpenOptionsExt;
use std::path::Path;
use std::ptr::NonNull;
use thiserror::Error;
use tokio::io::unix::AsyncFd;
use tokio::task;
pub struct Interceptor {
file: AsyncFd<File>,
evdev: NonNull<libevdev>,
evdev: Evdev,
writer: Writer,
// The state of `read` is stored here to make it cancel safe.
events: VecDeque<Event>,
@ -142,8 +136,10 @@ impl Interceptor {
}
async fn read_raw(&mut self) -> Result<(u16, u16, i32), Error> {
let file = self.evdev.file().unwrap();
loop {
let result = self.file.readable().await?.try_io(|_| {
let result = file.readable().await?.try_io(|_| {
let mut event = MaybeUninit::uninit();
let ret = unsafe {
glue::libevdev_next_event(
@ -179,22 +175,9 @@ impl Interceptor {
}
pub(crate) async fn open(path: &Path, registry: &Registry) -> Result<Self, OpenError> {
let path = path.to_owned();
let registry = registry.clone();
let evdev = Evdev::open(path).await?;
let metadata = evdev.file().unwrap().get_ref().metadata()?;
task::spawn_blocking(move || Self::open_sync(&path, &registry))
.await
.map_err(|err| OpenError::Io(err.into()))?
}
fn open_sync(path: &Path, registry: &Registry) -> Result<Self, OpenError> {
let file = OpenOptions::new()
.read(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.and_then(AsyncFd::new)?;
let metadata = file.get_ref().metadata()?;
let reader_handle = registry
.register(Entry {
device: metadata.dev(),
@ -202,26 +185,12 @@ impl Interceptor {
})
.ok_or(OpenError::NotAppliable)?;
let mut evdev = MaybeUninit::uninit();
let ret = unsafe { glue::libevdev_new_from_fd(file.as_raw_fd(), evdev.as_mut_ptr()) };
if ret < 0 {
return Err(Error::from_raw_os_error(-ret).into());
}
let evdev = unsafe { evdev.assume_init() };
let evdev = NonNull::new(evdev).unwrap();
// "Upon binding to a device or resuming from suspend, a driver must report
// the current switch state. This ensures that the device, kernel, and userspace
// state is in sync."
// We have no way of knowing that.
let sw = unsafe { glue::libevdev_has_event_type(evdev.as_ptr(), glue::EV_SW) };
if sw == 1 {
unsafe {
glue::libevdev_free(evdev.as_ptr());
}
return Err(OpenError::NotAppliable);
}
@ -250,10 +219,6 @@ impl Interceptor {
unsafe { glue::libevdev_disable_event_code(evdev.as_ptr(), glue::EV_ABS, i) };
if ret < 0 {
unsafe {
glue::libevdev_free(evdev.as_ptr());
}
return Err(Error::from_raw_os_error(-ret).into());
}
}
@ -267,41 +232,17 @@ impl Interceptor {
unsafe { glue::libevdev_grab(evdev.as_ptr(), glue::libevdev_grab_mode_LIBEVDEV_GRAB) };
if ret < 0 {
unsafe {
glue::libevdev_free(evdev.as_ptr());
}
return Err(Error::from_raw_os_error(-ret).into());
}
let writer = unsafe { Writer::from_evdev(evdev.as_ptr()) };
let writer = match writer {
Ok(writer) => writer,
Err(err) => {
unsafe {
glue::libevdev_free(evdev.as_ptr());
}
let writer = Writer::from_evdev(&evdev).await?;
let entry = writer.entry()?;
return Err(err.into());
}
};
// TODO: This is ugly. Need to refactor the evdev wrappers.
let entry = match writer.entry() {
Ok(entry) => entry,
Err(err) => {
unsafe {
glue::libevdev_free(evdev.as_ptr());
}
return Err(err.into());
}
};
let writer_handle = registry.register(entry).unwrap();
let writer_handle = registry
.register(entry)
.ok_or_else(|| Error::new(ErrorKind::Other, "Writer already registered"))?;
Ok(Self {
file,
evdev,
writer,
events: VecDeque::new(),
@ -314,14 +255,6 @@ impl Interceptor {
}
}
impl Drop for Interceptor {
fn drop(&mut self) {
unsafe {
glue::libevdev_free(self.evdev.as_ptr());
}
}
}
unsafe impl Send for Interceptor {}
#[derive(Error, Debug)]

View file

@ -7,5 +7,7 @@ pub mod rel;
pub mod sync;
pub mod writer;
mod evdev;
mod glue;
mod registry;
mod uinput;

69
rkvm-input/src/uinput.rs Normal file
View file

@ -0,0 +1,69 @@
use crate::evdev::Evdev;
use crate::glue::{self, libevdev_uinput};
use std::fs::File;
use std::io::Error;
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::ptr::NonNull;
use tokio::fs::OpenOptions;
use tokio::io::unix::AsyncFd;
pub struct Uinput {
file: AsyncFd<File>,
uinput: NonNull<libevdev_uinput>,
}
impl Uinput {
pub async fn from_evdev(evdev: &Evdev) -> Result<Self, Error> {
let file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open("/dev/uinput")
.await?
.into_std()
.await;
let file = AsyncFd::new(file)?;
let mut uinput = MaybeUninit::uninit();
let ret = unsafe {
glue::libevdev_uinput_create_from_device(
evdev.as_ptr(),
file.as_raw_fd(),
uinput.as_mut_ptr(),
)
};
if ret < 0 {
return Err(Error::from_raw_os_error(-ret));
}
let uinput = unsafe { uinput.assume_init() };
let uinput = unsafe { NonNull::new_unchecked(uinput) };
Ok(Self { file, uinput })
}
pub fn file(&self) -> &AsyncFd<File> {
&self.file
}
pub fn as_ptr(&self) -> *mut libevdev_uinput {
self.uinput.as_ptr()
}
}
impl Drop for Uinput {
fn drop(&mut self) {
unsafe {
glue::libevdev_uinput_destroy(self.uinput.as_ptr());
}
}
}
unsafe impl Send for Uinput {}
unsafe impl Sync for Uinput {}

View file

@ -1,27 +1,21 @@
use crate::abs::{AbsAxis, AbsEvent, AbsInfo};
use crate::evdev::Evdev;
use crate::event::Event;
use crate::glue::{self, input_absinfo, libevdev, libevdev_uinput};
use crate::glue::{self, input_absinfo};
use crate::key::{Key, KeyEvent};
use crate::registry::Entry;
use crate::rel::{RelAxis, RelEvent};
use crate::uinput::Uinput;
use std::ffi::{CStr, OsStr};
use std::fs::{File, OpenOptions};
use std::io::{Error, ErrorKind};
use std::mem::MaybeUninit;
use std::os::fd::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::os::unix::prelude::OpenOptionsExt;
use std::path::Path;
use std::ptr::NonNull;
use std::{fs, ptr};
use tokio::io::unix::AsyncFd;
use tokio::task;
pub struct Writer {
file: AsyncFd<File>,
uinput: NonNull<libevdev_uinput>,
uinput: Uinput,
}
impl Writer {
@ -69,28 +63,10 @@ impl Writer {
})
}
pub(crate) unsafe fn from_evdev(evdev: *const libevdev) -> Result<Self, Error> {
let file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open("/dev/uinput")
.and_then(AsyncFd::new)?;
let mut uinput = MaybeUninit::uninit();
let ret = unsafe {
glue::libevdev_uinput_create_from_device(evdev, file.as_raw_fd(), uinput.as_mut_ptr())
};
if ret < 0 {
return Err(Error::from_raw_os_error(-ret));
}
let uinput = unsafe { uinput.assume_init() };
let uinput = NonNull::new(uinput).unwrap();
Ok(Self { file, uinput })
pub(crate) async fn from_evdev(evdev: &Evdev) -> Result<Self, Error> {
Ok(Self {
uinput: Uinput::from_evdev(evdev).await?,
})
}
pub(crate) async fn write_raw(
@ -100,7 +76,7 @@ impl Writer {
value: i32,
) -> Result<(), Error> {
loop {
let result = self.file.writable().await?.try_io(|_| {
let result = self.uinput.file().writable().await?.try_io(|_| {
let ret = unsafe {
glue::libevdev_uinput_write_event(
self.uinput.as_ptr(),
@ -125,25 +101,13 @@ impl Writer {
}
}
impl Drop for Writer {
fn drop(&mut self) {
unsafe {
glue::libevdev_uinput_destroy(self.uinput.as_ptr());
}
}
}
unsafe impl Send for Writer {}
pub struct WriterBuilder {
evdev: NonNull<libevdev>,
evdev: Evdev,
}
impl WriterBuilder {
pub fn new() -> Result<Self, Error> {
let evdev = unsafe { glue::libevdev_new() };
let evdev = NonNull::new(evdev)
.ok_or_else(|| Error::new(ErrorKind::Other, "Failed to create device"))?;
let evdev = Evdev::new()?;
unsafe {
glue::libevdev_set_id_bustype(evdev.as_ptr(), glue::BUS_VIRTUAL as _);
@ -269,16 +233,6 @@ impl WriterBuilder {
}
pub async fn build(self) -> Result<Writer, Error> {
task::spawn_blocking(move || unsafe { Writer::from_evdev(self.evdev.as_ref()) }).await?
}
}
unsafe impl Send for WriterBuilder {}
impl Drop for WriterBuilder {
fn drop(&mut self) {
unsafe {
glue::libevdev_free(self.evdev.as_ptr());
}
Writer::from_evdev(&self.evdev).await
}
}