diff --git a/vuinputd/src/cuse_device/evdev_write_watcher.rs b/vuinputd/src/cuse_device/evdev_write_watcher.rs index 8ca7563..485ec03 100644 --- a/vuinputd/src/cuse_device/evdev_write_watcher.rs +++ b/vuinputd/src/cuse_device/evdev_write_watcher.rs @@ -18,7 +18,7 @@ use anyhow::Context; use cuse_lowlevel::fuse_lowlevel; use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags}; -use crate::cuse_device::state::{get_vuinput_state, VuFileHandle}; +use crate::cuse_device::state::{get_vuinput_state, PollPhase, VuFileHandle}; pub static EVDEV_WRITE_WATCHER: OnceLock> = OnceLock::new(); @@ -113,7 +113,7 @@ fn evdev_write_watch_loop(shutdown: Arc, epoll: Arc) { fuse_lowlevel::fuse_pollhandle_destroy(handle.as_ptr()); } } - state.poll.readable = true; + state.poll.pollphase = PollPhase::Readable; state.poll.pending.clear(); } } diff --git a/vuinputd/src/cuse_device/mod.rs b/vuinputd/src/cuse_device/mod.rs index fe3cb9d..209ec00 100644 --- a/vuinputd/src/cuse_device/mod.rs +++ b/vuinputd/src/cuse_device/mod.rs @@ -38,7 +38,6 @@ pub fn vuinput_make_cuse_ops() -> cuse_lowlevel::cuse_lowlevel_ops { release: Some(vuinput_release::vuinput_release), fsync: None, ioctl: Some(vuinput_ioctl::vuinput_ioctl), - //poll: Some(vuinput_poll::vuinput_poll), - poll: None, + poll: Some(vuinput_poll::vuinput_poll), } } diff --git a/vuinputd/src/cuse_device/state.rs b/vuinputd/src/cuse_device/state.rs index 1f1f3bc..7e9ce53 100644 --- a/vuinputd/src/cuse_device/state.rs +++ b/vuinputd/src/cuse_device/state.rs @@ -43,6 +43,24 @@ impl KeyTracker { } } +/// EMPTY -> READY -> READING -> { EMPTY | READY } +/// EMPTY -> READABLE (new data arrives / watcher can observe) +/// READABLE -> READING (read callback starts draining) +/// READING -> EMPTY (read drained everything) +/// READING -> READABLE (read finished, but more data still remains) +#[derive(Debug)] +pub enum PollPhase { + Empty, + Readable, + Reading, +} + +impl Default for PollPhase { + fn default() -> Self { + Self::Empty + } +} + /// this data structure ensures poll and read are synchronized. /// poll() and read() must synchronize through one shared readines /// state, and the state transitions must be done under the same per-handle mutex. @@ -55,7 +73,7 @@ impl KeyTracker { pub struct PollState { /// Sticky readiness latch: /// true once evdev became readable, false again after read/drain. - pub readable: bool, + pub pollphase: PollPhase, /// Pending FUSE poll waiters for this device. /// Optimized for the common case of 0 or 1 waiter, but supports @@ -66,7 +84,7 @@ pub struct PollState { impl PollState { pub fn new() -> PollState { PollState { - readable: false, + pollphase: PollPhase::Empty, pending: smallvec![], } } diff --git a/vuinputd/src/cuse_device/vuinput_poll.rs b/vuinputd/src/cuse_device/vuinput_poll.rs index 51af16f..507415d 100644 --- a/vuinputd/src/cuse_device/vuinput_poll.rs +++ b/vuinputd/src/cuse_device/vuinput_poll.rs @@ -12,6 +12,7 @@ use log::{debug, trace}; use std::io::{Read, Write}; use std::os::fd::AsRawFd; use std::os::raw::c_char; +use std::ptr::NonNull; use uinput_ioctls::*; // https://github.com/libfuse/libfuse/blob/master/example/poll.c @@ -20,14 +21,22 @@ pub unsafe extern "C" fn vuinput_poll( fi: *mut fuse_lowlevel::fuse_file_info, ph: *mut fuse_lowlevel::fuse_pollhandle, ) { + let vuinput_state_mutex = + get_vuinput_state(&VuFileHandle::from_fuse_file_info(fi.as_ref().unwrap())).unwrap(); + let mut vuinput_state = vuinput_state_mutex.lock().unwrap(); - /* - let vuinput_state_mutex = - get_vuinput_state(&VuFileHandle::from_fuse_file_info(fi.as_ref().unwrap())).unwrap(); - let mut vuinput_state = vuinput_state_mutex.lock().unwrap(); - - if state.poll.readable { - // return POLLIN immediately - } else if let Some(handle) = NonNull::new(ph) { - state.poll.add_waiter(handle); */ + match vuinput_state.poll.pollphase { + PollPhase::Empty => { + let ph = NonNull::::new(ph); + vuinput_state.poll.add_waiter(ph.unwrap()); + } + PollPhase::Readable => { + fuse_lowlevel::fuse_lowlevel_notify_poll(ph); + fuse_lowlevel::fuse_pollhandle_destroy(ph); + } + PollPhase::Reading => { + fuse_lowlevel::fuse_lowlevel_notify_poll(ph); + fuse_lowlevel::fuse_pollhandle_destroy(ph); + } + } } diff --git a/vuinputd/src/cuse_device/vuinput_read.rs b/vuinputd/src/cuse_device/vuinput_read.rs index 8f7dcdc..bc34dfa 100644 --- a/vuinputd/src/cuse_device/vuinput_read.rs +++ b/vuinputd/src/cuse_device/vuinput_read.rs @@ -3,15 +3,12 @@ // Author: Johannes Leupolz use crate::cuse_device::*; -use crate::global_config::get_device_policy; use ::cuse_lowlevel::*; -use libc::{__s32, __u16, input_event}; +use libc::{__s32, __u16, input_event, EAGAIN}; use libc::{off_t, size_t, EIO}; -use libc::{uinput_abs_setup, uinput_setup}; use log::{debug, trace}; use std::io::{Read, Write}; use std::os::fd::AsRawFd; -use std::os::raw::c_char; use uinput_ioctls::*; // TODO: compat-mode+ ensure sizeof(struct input_event) @@ -38,12 +35,12 @@ pub unsafe extern "C" fn vuinput_read( let mut buffer: [u8; 24] = [0; 24]; + vuinput_state.poll.pollphase = PollPhase::Reading; // read up to 24 bytes - // todo: non-blocking or timeout let result = vuinput_state.file.read(&mut buffer); match result { Ok(NORMAL_SIZE) => { - if (!is_compat) { + if !is_compat { let buffer = buffer.as_ptr() as *const i8; fuse_lowlevel::fuse_reply_buf(_req, buffer, 24); } else { @@ -56,18 +53,19 @@ pub unsafe extern "C" fn vuinput_read( } } Err(e) => { - debug!("fh {}: error reading from uinput: {e:?}", fh); - fuse_lowlevel::fuse_reply_err(_req, EIO); + if e.kind() == io::ErrorKind::WouldBlock { + // EAGAIN / EWOULDBLOCK + //println!("Received EAGAIN: The read would block!"); + vuinput_state.poll.pollphase = PollPhase::Empty; + fuse_lowlevel::fuse_reply_err(_req, EAGAIN); + } else { + debug!("fh {}: error reading from uinput: {e:?}", fh); + fuse_lowlevel::fuse_reply_err(_req, EIO); + } } Ok(_) => { debug!("fh {}: error reading from uinput: wrong size", fh); fuse_lowlevel::fuse_reply_err(_req, EIO); } } - /* - if drained_to_eagain { - state.poll.readable = false; - } else { - state.poll.readable = true; - } */ }