mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-22 02:35:34 +00:00
Started to implement poll
This commit is contained in:
parent
6f60635ff2
commit
8d4a0c9413
13 changed files with 343 additions and 7 deletions
2
.github/workflows/debian-package.yml
vendored
2
.github/workflows/debian-package.yml
vendored
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
fuse3 \
|
||||
libclang-dev
|
||||
# debian packages, if packages are not downloaded via cargo
|
||||
sudo apt install -y librust-bindgen-dev librust-nix-dev librust-libc-dev librust-time-dev librust-log-dev librust-env-logger-dev librust-libudev-dev librust-regex-dev librust-async-channel-dev librust-futures-dev librust-async-io-dev librust-anyhow-dev librust-clap-dev librust-base64-dev
|
||||
sudo apt install -y librust-bindgen-dev librust-nix-dev librust-libc-dev librust-time-dev librust-log-dev librust-env-logger-dev librust-libudev-dev librust-regex-dev librust-async-channel-dev librust-futures-dev librust-async-io-dev librust-anyhow-dev librust-clap-dev librust-base64-dev librust-smallvec-dev
|
||||
|
||||
- name: Show versions (debug)
|
||||
run: |
|
||||
|
|
|
|||
1
debian/control
vendored
1
debian/control
vendored
|
|
@ -26,6 +26,7 @@ Build-Depends: debhelper (>= 12),
|
|||
librust-anyhow-dev,
|
||||
librust-clap-dev,
|
||||
librust-base64-dev,
|
||||
librust-smallvec-dev,
|
||||
pkg-config,
|
||||
build-essential
|
||||
Standards-Version: 4.6.0
|
||||
|
|
|
|||
|
|
@ -397,6 +397,14 @@ When mapping 32-bit compat input_event formats into 64-bit representation, copy
|
|||
|
||||
No high volume of events expected where we could benefit from multiple threads. But much of the code is already prepared for multithreading, if there is really demand.
|
||||
|
||||
**Poll / event readiness handling**
|
||||
|
||||
- For operations that wait on host device readiness (e.g., force feedback, rumble, vibration, or reading back event state), the CUSE callback must **never block**.
|
||||
- A **poll/wakeup watcher** in a background thread monitors underlying `/dev/uinput` FDs and updates per-handle readiness (`PollState`) in `VuInputState` (see section 3.13).
|
||||
- FUSE poll callbacks may save the provided poll handle and immediately return; the background watcher later invokes `fuse_notify_poll()` to wake the kernel when data arrives.
|
||||
|
||||
*Why:* this separates the fast data-plane (CUSE callbacks) from the asynchronous event-plane (poll watcher) and prevents hanging the filesystem.
|
||||
|
||||
---
|
||||
|
||||
## 3.9 Overriding the type, vendor id, and product id
|
||||
|
|
@ -621,6 +629,80 @@ The filtering operates on two levels (Defense in Depth):
|
|||
* **`strict-gamepad` (Whitelist):** Designed for console-like isolation. It strictly permits only Gamepad/Joystick events (`EV_KEY` buttons, `EV_ABS` axes). It proactively blocks `EV_REL` (mouse movement) and `ABS_MT` (multitouch), effectively "neutering" complex controllers (like DualSense or Wiimotes) so they cannot be used to hijack the host mouse cursor.
|
||||
* **`sanitized` (Blacklist):** Designed for desktop gaming. It allows standard Keyboard and Mouse input but strictly filters dangerous keys (`KEY_SYSRQ`, `KEY_POWER`) and host-management shortcuts (VT switching, CAD), providing a safe "sandboxed keyboard."
|
||||
|
||||
---
|
||||
|
||||
## 3.13 Polling & Readiness Watcher
|
||||
|
||||
**Purpose**
|
||||
|
||||
- Provide non-blocking detection of device readiness on `/dev/vuinput` for operations like force feedback / rumble / vibration.
|
||||
- Ensure CUSE callbacks (`poll`, `read`) never block and the FUSE filesystem remains responsive.
|
||||
|
||||
**Core design**
|
||||
|
||||
- Each `VuInputState` includes a `PollState` struct:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PollState {
|
||||
/// A FUSE poll request is currently waiting to be woken.
|
||||
pub waiting: bool,
|
||||
|
||||
/// Sticky readiness latch: true once evdev became readable, false after read/drain.
|
||||
pub readable: bool,
|
||||
}
|
||||
```
|
||||
|
||||
* A **single background thread** watches all active uinput device file descriptors using **epoll** (or poll).
|
||||
* When data arrives:
|
||||
|
||||
* The watcher locks the corresponding `VuInputState` via `VUINPUT_STATE`.
|
||||
* Marks `poll.readable = true`.
|
||||
* If `poll.waiting = true`, the watcher clears the flag and optionally calls `fuse_notify_poll()` to wake any waiting FUSE poll requests.
|
||||
|
||||
**Adding / removing devices**
|
||||
|
||||
* When creating a new uinput device, the thread performs `epoll_ctl(ADD)` for the file descriptor.
|
||||
* On device close, it performs `epoll_ctl(DEL)` to remove the descriptor from monitoring.
|
||||
* No separate registry is maintained; the **global `VUINPUT_STATE` HashMap** is the single source of truth.
|
||||
|
||||
**Poll callback behavior**
|
||||
|
||||
* FUSE poll callbacks **do not block**: they may store the poll handle and immediately return.
|
||||
* The background watcher ensures that any pending poll handles are notified asynchronously when data is ready.
|
||||
|
||||
**Read handling**
|
||||
|
||||
* Reads from `/dev/vuinput` are non-blocking:
|
||||
|
||||
* `poll()` detects readiness.
|
||||
* `read()` uses `O_NONBLOCK` and drains all available events.
|
||||
* `EAGAIN` indicates the buffer is empty, at which point `poll.readable` is reset to false.
|
||||
|
||||
**Threading / shutdown**
|
||||
|
||||
* The background watcher uses a 500ms epoll_wait timeout to allow clean shutdown.
|
||||
* It is safe to have a single watcher thread for all devices; epoll scales efficiently with multiple descriptors.
|
||||
|
||||
**Benefits**
|
||||
|
||||
* Fully non-blocking CUSE front-end.
|
||||
* Lightweight: epoll manages file descriptors; no extra registry is needed.
|
||||
* Correctly wakes FUSE poll requests for force feedback / rumble operations.
|
||||
* Consistent with existing dispatcher rules: data-plane operations remain fast; control-plane updates scheduled as jobs if needed.
|
||||
|
||||
Poll/read race rule:
|
||||
`PollState` is the single source of truth for readiness and pending poll waiters.
|
||||
Both `poll()` and `read()` must update it only while holding the per-handle `VuInputState` mutex.
|
||||
|
||||
- `poll()` must first check `readable`; only if false may it enqueue a poll handle.
|
||||
- the watcher sets `readable = true` and atomically drains pending waiters for notification.
|
||||
- `read()` may clear `readable` only after draining the proxied evdev fd until `EAGAIN` (or equivalent proof of emptiness).
|
||||
|
||||
This prevents lost wakeups and stale readiness in the proxy.
|
||||
|
||||
---
|
||||
|
||||
## 4. Security Considerations
|
||||
|
||||
`vuinputd` must currently run with **root privileges** to:
|
||||
|
|
@ -645,6 +727,7 @@ While this design is necessary for mediation, it introduces potential attack sur
|
|||
* [ ] Use **seccomp** or `systemd` sandboxing (`ProtectSystem`, `ProtectKernelTunables`, `RestrictNamespaces`, etc.).
|
||||
* [ ] Eventually migrate to **Rust-native FUSE/Netlink** bindings to remove unsafe dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 5. Background: How are input devices created by the kernel using uinput
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use crate::scenarios::ScenarioArgs;
|
|||
use crate::test_log::{LoggedInputEvent, TestLog};
|
||||
use libc::{self, ff_effect, ff_replay, ff_trigger};
|
||||
|
||||
//TODO: poll and erase
|
||||
pub struct FfXboxGamepad;
|
||||
|
||||
impl FfXboxGamepad {
|
||||
|
|
|
|||
|
|
@ -30,3 +30,11 @@ clap = { version = "4", features = ["derive"] }
|
|||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
base64 = "0.22"
|
||||
smallvec = "1.15.1"
|
||||
|
||||
[features]
|
||||
requires-privileges = []
|
||||
requires-rootless = []
|
||||
requires-uinput = []
|
||||
requires-bwrap = []
|
||||
requires-podman = []
|
||||
121
vuinputd/src/cuse_device/evdev_write_watcher.rs
Normal file
121
vuinputd/src/cuse_device/evdev_write_watcher.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
os::fd::{AsFd, BorrowedFd},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex, OnceLock,
|
||||
},
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
pub static EVDEV_WRITE_WATCHER: OnceLock<Mutex<EvdevWriteWatcher>> = OnceLock::new();
|
||||
|
||||
pub fn initialize_evdev_write_watcher() -> anyhow::Result<()> {
|
||||
EVDEV_WRITE_WATCHER
|
||||
.set(Mutex::new(EvdevWriteWatcher::new()?)) // Convert the error from Mutex<T> to a simple string
|
||||
.map_err(|_| anyhow::anyhow!("cell already full"))
|
||||
// Now .context() works because &str is compatible
|
||||
.context("failed to initialize evdev write watcher")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EvdevWriteWatcher {
|
||||
epoll: Arc<Epoll>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
thread_handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl EvdevWriteWatcher {
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
let epoll = Arc::new(Epoll::new(EpollCreateFlags::empty())?);
|
||||
let shutdown = Arc::new(AtomicBool::new(false));
|
||||
let epoll_thread = epoll.clone();
|
||||
let shutdown_thread = shutdown.clone();
|
||||
let thread_handle = Some(thread::spawn(move || {
|
||||
evdev_write_watch_loop(shutdown_thread, epoll_thread);
|
||||
}));
|
||||
Ok(Self {
|
||||
thread_handle: thread_handle,
|
||||
shutdown: shutdown,
|
||||
epoll: epoll,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_device(&self, vu_fh: VuFileHandle) -> nix::Result<()> {
|
||||
let VuFileHandle::Fh(fh) = vu_fh;
|
||||
|
||||
let vuinput_state_mutex = get_vuinput_state(&vu_fh).unwrap();
|
||||
let vuinput_state = vuinput_state_mutex.lock().unwrap();
|
||||
|
||||
self.epoll.add(
|
||||
&vuinput_state.file,
|
||||
EpollEvent::new(EpollFlags::EPOLLIN, fh),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn remove_device<Fd: AsFd>(&self, uinput_fd: Fd) -> nix::Result<()> {
|
||||
self.epoll.delete(uinput_fd)
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
|
||||
if let Some(handle) = self.thread_handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.thread_handle.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
fn evdev_write_watch_loop(shutdown: Arc<AtomicBool>, epoll: Arc<Epoll>) {
|
||||
let mut events = vec![EpollEvent::empty(); 64];
|
||||
|
||||
loop {
|
||||
if shutdown.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
|
||||
let n = match epoll.wait(&mut events, 500u16) {
|
||||
Ok(n) => n,
|
||||
Err(err) => {
|
||||
eprintln!("evdev_write_watcher: epoll_wait failed: {err}");
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for ev in &events[..n] {
|
||||
let fh_val = ev.data() as u64;
|
||||
let fh = VuFileHandle::Fh(fh_val);
|
||||
let state = super::state::get_vuinput_state(&fh);
|
||||
if let Ok(state) = state {
|
||||
let mut state = state.lock().unwrap();
|
||||
|
||||
for handle in state.poll.take_waiters() {
|
||||
unsafe {
|
||||
fuse_lowlevel::fuse_lowlevel_notify_poll(handle.as_ptr());
|
||||
fuse_lowlevel::fuse_pollhandle_destroy(handle.as_ptr());
|
||||
}
|
||||
}
|
||||
state.poll.readable = true;
|
||||
state.poll.pending.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
pub mod device_policy;
|
||||
pub mod evdev_write_watcher;
|
||||
pub mod state;
|
||||
pub mod vuinput_ioctl;
|
||||
pub mod vuinput_open;
|
||||
pub mod vuinput_poll;
|
||||
pub mod vuinput_read;
|
||||
pub mod vuinput_release;
|
||||
pub mod vuinput_write;
|
||||
|
|
@ -36,6 +38,7 @@ 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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock};
|
||||
|
||||
use ::cuse_lowlevel::*;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::process_tools::RequestingProcess;
|
||||
use smallvec::smallvec;
|
||||
|
||||
pub type PendingPollHandles = SmallVec<[*mut fuse_lowlevel::fuse_pollhandle; 1]>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputDevice {
|
||||
|
|
@ -38,12 +43,55 @@ impl KeyTracker {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Ensure, we have no lost-wakeup races like:
|
||||
/// 1) watcher sets readable
|
||||
/// 2) read() drains and clears readable
|
||||
/// 3) poll() stores waiter too late
|
||||
/// 4) nobody wakes it anymore
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PollState {
|
||||
/// Sticky readiness latch:
|
||||
/// true once evdev became readable, false again after read/drain.
|
||||
pub readable: bool,
|
||||
|
||||
/// Pending FUSE poll waiters for this device.
|
||||
/// Optimized for the common case of 0 or 1 waiter, but supports
|
||||
/// multiple concurrent poll() callers correctly.
|
||||
pub pending: SmallVec<[NonNull<fuse_lowlevel::fuse_pollhandle>; 1]>,
|
||||
}
|
||||
|
||||
impl PollState {
|
||||
pub fn new() -> PollState {
|
||||
PollState {
|
||||
readable: false,
|
||||
pending: smallvec![],
|
||||
}
|
||||
}
|
||||
pub fn has_waiters(&self) -> bool {
|
||||
!self.pending.is_empty()
|
||||
}
|
||||
|
||||
pub fn add_waiter(&mut self, handle: NonNull<fuse_lowlevel::fuse_pollhandle>) {
|
||||
self.pending.push(handle);
|
||||
}
|
||||
|
||||
pub fn take_waiters(&mut self) -> SmallVec<[NonNull<fuse_lowlevel::fuse_pollhandle>; 1]> {
|
||||
std::mem::take(&mut self.pending)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for PollState {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputState {
|
||||
pub file: File,
|
||||
pub requesting_process: RequestingProcess,
|
||||
pub input_device: Option<VuInputDevice>,
|
||||
pub keytracker: KeyTracker,
|
||||
pub poll: PollState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone)]
|
||||
|
|
|
|||
|
|
@ -5,13 +5,16 @@
|
|||
use ::cuse_lowlevel::*;
|
||||
use libc::ENOENT;
|
||||
use libc::O_CLOEXEC;
|
||||
use libc::O_NONBLOCK;
|
||||
use log::{debug, error};
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::fd::AsFd;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::cuse_device::evdev_write_watcher::EVDEV_WRITE_WATCHER;
|
||||
use crate::cuse_device::*;
|
||||
use crate::process_tools::{get_requesting_process, Pid};
|
||||
|
||||
|
|
@ -43,21 +46,31 @@ pub unsafe extern "C" fn vuinput_open(
|
|||
let open_vuinput_result = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
//.custom_flags(O_NONBLOCK)
|
||||
.custom_flags(O_NONBLOCK)
|
||||
.custom_flags(O_CLOEXEC)
|
||||
.open(Path::new("/dev/uinput"));
|
||||
match open_vuinput_result {
|
||||
Ok(v) => {
|
||||
let vu_fh: VuFileHandle = VuFileHandle::Fh(fh);
|
||||
let uinput_fd: std::os::unix::prelude::BorrowedFd<'_> = v.as_fd();
|
||||
insert_vuinput_state(
|
||||
&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap()),
|
||||
&vu_fh,
|
||||
VuInputState {
|
||||
file: v,
|
||||
requesting_process,
|
||||
input_device: None,
|
||||
keytracker: KeyTracker::new(),
|
||||
poll: PollState::new(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
EVDEV_WRITE_WATCHER
|
||||
.get()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.add_device(vu_fh)
|
||||
.unwrap();
|
||||
fuse_lowlevel::fuse_reply_open(_req, _fi);
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
|||
33
vuinputd/src/cuse_device/vuinput_poll.rs
Normal file
33
vuinputd/src/cuse_device/vuinput_poll.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use crate::cuse_device::*;
|
||||
use crate::global_config::get_device_policy;
|
||||
use ::cuse_lowlevel::*;
|
||||
use libc::{__s32, __u16, input_event};
|
||||
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::*;
|
||||
|
||||
// https://github.com/libfuse/libfuse/blob/master/example/poll.c
|
||||
pub unsafe extern "C" fn vuinput_poll(
|
||||
req: fuse_lowlevel::fuse_req_t,
|
||||
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();
|
||||
|
||||
if state.poll.readable {
|
||||
// return POLLIN immediately
|
||||
} else if let Some(handle) = NonNull::new(ph) {
|
||||
state.poll.add_waiter(handle); */
|
||||
}
|
||||
|
|
@ -32,16 +32,17 @@ pub unsafe extern "C" fn vuinput_read(
|
|||
get_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap();
|
||||
let mut vuinput_state = vuinput_state_mutex.lock().unwrap();
|
||||
|
||||
let normal_size = std::mem::size_of::<libc::input_event>();
|
||||
const NORMAL_SIZE: usize = std::mem::size_of::<libc::input_event>();
|
||||
let is_compat = vuinput_state.requesting_process.is_compat;
|
||||
// TODO: ARM: && !compat_uses_64bit_time()
|
||||
|
||||
let mut buffer: [u8; 24] = [0; 24];
|
||||
|
||||
// read up to 24 bytes
|
||||
// todo: non-blocking or timeout
|
||||
let result = vuinput_state.file.read(&mut buffer);
|
||||
match result {
|
||||
Ok(normal_size) => {
|
||||
Ok(NORMAL_SIZE) => {
|
||||
if (!is_compat) {
|
||||
let buffer = buffer.as_ptr() as *const i8;
|
||||
fuse_lowlevel::fuse_reply_buf(_req, buffer, 24);
|
||||
|
|
@ -63,4 +64,10 @@ pub unsafe extern "C" fn vuinput_read(
|
|||
fuse_lowlevel::fuse_reply_err(_req, EIO);
|
||||
}
|
||||
}
|
||||
/*
|
||||
if drained_to_eagain {
|
||||
state.poll.readable = false;
|
||||
} else {
|
||||
state.poll.readable = true;
|
||||
} */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use crate::cuse_device::evdev_write_watcher::EVDEV_WRITE_WATCHER;
|
||||
use crate::job_engine::JOB_DISPATCHER;
|
||||
use crate::jobs::remove_device_job::RemoveDeviceJob;
|
||||
use crate::process_tools::SELF_NAMESPACES;
|
||||
use crate::{cuse_device::*, jobs};
|
||||
use ::cuse_lowlevel::*;
|
||||
use log::debug;
|
||||
use std::os::fd::AsFd;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub unsafe extern "C" fn vuinput_release(
|
||||
|
|
@ -15,8 +17,8 @@ pub unsafe extern "C" fn vuinput_release(
|
|||
_fi: *mut fuse_lowlevel::fuse_file_info,
|
||||
) {
|
||||
let fh = &(*_fi).fh;
|
||||
let vuinput_state_mutex =
|
||||
remove_vuinput_state(&VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap())).unwrap();
|
||||
let vu_fh = VuFileHandle::from_fuse_file_info(_fi.as_ref().unwrap());
|
||||
let vuinput_state_mutex = remove_vuinput_state(&vu_fh).unwrap();
|
||||
|
||||
let mut vuinput_state = vuinput_state_mutex.lock().unwrap();
|
||||
let input_device = vuinput_state.input_device.take();
|
||||
|
|
@ -49,6 +51,14 @@ pub unsafe extern "C" fn vuinput_release(
|
|||
awaiter(&jobs::remove_device_job::State::Finished);
|
||||
}
|
||||
|
||||
EVDEV_WRITE_WATCHER
|
||||
.get()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.remove_device(vuinput_state.file.as_fd())
|
||||
.unwrap();
|
||||
|
||||
drop(vuinput_state);
|
||||
|
||||
debug!(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ use std::sync::Mutex;
|
|||
|
||||
pub mod cuse_device;
|
||||
|
||||
use crate::cuse_device::evdev_write_watcher::{
|
||||
initialize_evdev_write_watcher, EVDEV_WRITE_WATCHER,
|
||||
};
|
||||
use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_state};
|
||||
use crate::cuse_device::vuinput_make_cuse_ops;
|
||||
use crate::cuse_device::vuinput_open::VUINPUT_COUNTER;
|
||||
|
|
@ -194,6 +197,9 @@ fn main() -> std::io::Result<()> {
|
|||
vt_tools::check_vt_status();
|
||||
|
||||
global_config::initialize_global_config(&args.device_policy, &args.placement, &args.devname);
|
||||
initialize_evdev_write_watcher().expect(
|
||||
"failed to initialize the watcher that watches for writes on the created evdev devices",
|
||||
);
|
||||
initialize_vuinput_state();
|
||||
VUINPUT_COUNTER.set(AtomicU64::new(3)).expect(
|
||||
"failed to initialize the counter that provides the values of the CUSE file handles",
|
||||
|
|
@ -280,5 +286,7 @@ fn main() -> std::io::Result<()> {
|
|||
.unwrap()
|
||||
.wait_until_finished();
|
||||
|
||||
EVDEV_WRITE_WATCHER.get().unwrap().lock().unwrap().stop();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue