diff --git a/docs/DESIGN.md b/docs/DESIGN.md index b79d136..2ac76c0 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -570,6 +570,11 @@ https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/-/blob/main/docs/ld-lib https://github.com/ValveSoftware/steam-for-linux/issues/10175 https://github.com/ValveSoftware/steam-for-linux/issues/8042 +#### 5.3.3 Selkies Project + +Absolutely untested. +https://github.com/selkies-project/selkies/pull/173 + ### 5.4 Applications that use the created devices #### 5.4.1 SDL @@ -598,6 +603,10 @@ https://gitlab.freedesktop.org/libevdev/libevdev/-/blob/master/libevdev/libevdev https://github.com/GloriousEggroll/proton-ge-custom/blob/master/docs/CONTROLLERS.md +wine control + +https://github.com/flatpak/xdg-desktop-portal/issues/536 + ## 6. HIDAPI https://github.com/libusb/hidapi diff --git a/vuinputd/Cargo.toml b/vuinputd/Cargo.toml index 5616e67..0aa4dfa 100644 --- a/vuinputd/Cargo.toml +++ b/vuinputd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vuinputd" -version = "0.2.0" +version = "0.2.1" edition = "2021" authors = ["Johannes Leupolz "] license = "MIT" diff --git a/vuinputd/src/container/mod.rs b/vuinputd/src/container/mod.rs deleted file mode 100644 index 610b907..0000000 --- a/vuinputd/src/container/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Author: Johannes Leupolz - -pub mod inject_in_container_job; -pub mod remove_from_container_job; -pub mod mknod_input_device; -pub mod runtime_data; -pub mod netlink_message; \ No newline at end of file diff --git a/vuinputd/src/jobs/closure_job.rs b/vuinputd/src/job_engine/closure_job.rs similarity index 97% rename from vuinputd/src/jobs/closure_job.rs rename to vuinputd/src/job_engine/closure_job.rs index 9236e94..7ac1b8f 100644 --- a/vuinputd/src/jobs/closure_job.rs +++ b/vuinputd/src/job_engine/closure_job.rs @@ -5,7 +5,8 @@ use std::future::Future; use std::pin::Pin; -use crate::jobs::job::{Dispatcher, Job, JobTarget}; +use crate::job_engine::job::{Dispatcher, Job, JobTarget}; + pub struct ClosureJob { desc: String, @@ -56,7 +57,7 @@ impl Job for ClosureJob { } /// Example usage -#[allow(dead_code)] +#[test] pub fn example() { let mut dispatcher = Dispatcher::new(); diff --git a/vuinputd/src/jobs/job.rs b/vuinputd/src/job_engine/job.rs similarity index 100% rename from vuinputd/src/jobs/job.rs rename to vuinputd/src/job_engine/job.rs diff --git a/vuinputd/src/jobs/job_builder.rs b/vuinputd/src/job_engine/job_builder.rs similarity index 100% rename from vuinputd/src/jobs/job_builder.rs rename to vuinputd/src/job_engine/job_builder.rs diff --git a/vuinputd/src/job_engine/mod.rs b/vuinputd/src/job_engine/mod.rs new file mode 100644 index 0000000..3639120 --- /dev/null +++ b/vuinputd/src/job_engine/mod.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +// +// Author: Johannes Leupolz +//! # Design: Async Per-Container Job Executor (Tokio) +//! +//! ## Overview +//! A scalable, structured design for running async jobs per container. +//! +//! - Global dispatcher routes jobs to per-container async loops or to global queue. +//! - Each container has its own unbounded job queue (no backpressure). +//! - Loops are spawned lazily on first job and exit when their sender drops. +//! - Graceful shutdown happens automatically (channel close → loop exit). +//! - Periodic cleanup removes idle container queues. +//! +//! ## Async Jobs +//! - Each `Job` contains an async closure `task: Box Pin>> + Send>` +//! - This allows full async/await usage inside the job body. +//! +//! +//! +--------------------------------------+ +//! | Global dispatcher | +//! +----------+---------------------------+ +//! | | +//! v v +//! +----------+-----------+ +------------+ +//! | Per-container queues | | Host queue | +//! +----+------+----+-----+ +------------+ +//! | | | +//! +----v----+ +---v----+ +---v----+ +//! | Cont A | | Cont B | | Cont C | +//! | loop() | | loop() | | loop() | +//! +---------+ +--------+ +--------+ + +pub mod closure_job; +pub mod job; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/vuinputd/src/jobs/tests.rs b/vuinputd/src/job_engine/tests.rs similarity index 89% rename from vuinputd/src/jobs/tests.rs rename to vuinputd/src/job_engine/tests.rs index 45d8f26..db87e02 100644 --- a/vuinputd/src/jobs/tests.rs +++ b/vuinputd/src/job_engine/tests.rs @@ -1,5 +1,7 @@ -use crate::jobs::closure_job::ClosureJob; -use crate::jobs::job::{Dispatcher, Job, JobTarget}; + + +use crate::job_engine::closure_job::ClosureJob; +use crate::job_engine::job::{Dispatcher, JobTarget}; use super::*; use futures::executor::LocalPool; @@ -49,6 +51,7 @@ fn test_job_ordering() { }), ))); + dispatcher.close(); dispatcher.wait_until_finished(); assert_eq!(*c.lock().unwrap(), 6); diff --git a/vuinputd/src/container/inject_in_container_job.rs b/vuinputd/src/jobs/inject_in_container_job.rs similarity index 93% rename from vuinputd/src/container/inject_in_container_job.rs rename to vuinputd/src/jobs/inject_in_container_job.rs index 53e6fcc..19abaea 100644 --- a/vuinputd/src/container/inject_in_container_job.rs +++ b/vuinputd/src/jobs/inject_in_container_job.rs @@ -7,9 +7,8 @@ use std::{collections::HashMap, future::Future, pin::Pin, sync::{Arc, Condvar, M use async_io::Timer; use log::debug; -use crate::{ - container::{mknod_input_device::ensure_input_device, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{self, ensure_udev_structure, read_udev_data, write_udev_data}}, jobs::job::{Job, JobTarget}, monitor_udev::EVENT_STORE, requesting_process::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace} -}; +use crate::{job_engine::job::{Job, JobTarget}, jobs::{mknod_input_device::ensure_input_device, monitor_udev_job::EVENT_STORE, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{ensure_udev_structure, read_udev_data, write_udev_data}}, requesting_process::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace}}; + #[derive(Clone,Debug,Copy,PartialOrd,PartialEq)] pub enum State { diff --git a/vuinputd/src/container/mknod_input_device.rs b/vuinputd/src/jobs/mknod_input_device.rs similarity index 100% rename from vuinputd/src/container/mknod_input_device.rs rename to vuinputd/src/jobs/mknod_input_device.rs diff --git a/vuinputd/src/jobs/mod.rs b/vuinputd/src/jobs/mod.rs index 3639120..cf229cf 100644 --- a/vuinputd/src/jobs/mod.rs +++ b/vuinputd/src/jobs/mod.rs @@ -1,38 +1,10 @@ // SPDX-License-Identifier: MIT // // Author: Johannes Leupolz -//! # Design: Async Per-Container Job Executor (Tokio) -//! -//! ## Overview -//! A scalable, structured design for running async jobs per container. -//! -//! - Global dispatcher routes jobs to per-container async loops or to global queue. -//! - Each container has its own unbounded job queue (no backpressure). -//! - Loops are spawned lazily on first job and exit when their sender drops. -//! - Graceful shutdown happens automatically (channel close → loop exit). -//! - Periodic cleanup removes idle container queues. -//! -//! ## Async Jobs -//! - Each `Job` contains an async closure `task: Box Pin>> + Send>` -//! - This allows full async/await usage inside the job body. -//! -//! -//! +--------------------------------------+ -//! | Global dispatcher | -//! +----------+---------------------------+ -//! | | -//! v v -//! +----------+-----------+ +------------+ -//! | Per-container queues | | Host queue | -//! +----+------+----+-----+ +------------+ -//! | | | -//! +----v----+ +---v----+ +---v----+ -//! | Cont A | | Cont B | | Cont C | -//! | loop() | | loop() | | loop() | -//! +---------+ +--------+ +--------+ -pub mod closure_job; -pub mod job; - -#[cfg(test)] -mod tests; \ No newline at end of file +pub mod inject_in_container_job; +pub mod remove_from_container_job; +pub mod monitor_udev_job; +pub mod mknod_input_device; +pub mod runtime_data; +pub mod netlink_message; \ No newline at end of file diff --git a/vuinputd/src/monitor_udev.rs b/vuinputd/src/jobs/monitor_udev_job.rs similarity index 99% rename from vuinputd/src/monitor_udev.rs rename to vuinputd/src/jobs/monitor_udev_job.rs index 0d648b0..3697e07 100644 --- a/vuinputd/src/monitor_udev.rs +++ b/vuinputd/src/jobs/monitor_udev_job.rs @@ -19,7 +19,7 @@ use libudev::Monitor; use log::debug; use regex::Regex; -use crate::jobs::job::{Job, JobTarget}; +use crate::job_engine::job::{Job, JobTarget}; // === Basic types === diff --git a/vuinputd/src/container/netlink_message.rs b/vuinputd/src/jobs/netlink_message.rs similarity index 100% rename from vuinputd/src/container/netlink_message.rs rename to vuinputd/src/jobs/netlink_message.rs diff --git a/vuinputd/src/container/remove_from_container_job.rs b/vuinputd/src/jobs/remove_from_container_job.rs similarity index 91% rename from vuinputd/src/container/remove_from_container_job.rs rename to vuinputd/src/jobs/remove_from_container_job.rs index 1f7989b..a08bd81 100644 --- a/vuinputd/src/container/remove_from_container_job.rs +++ b/vuinputd/src/jobs/remove_from_container_job.rs @@ -7,9 +7,8 @@ use std::{collections::HashMap, future::Future, pin::Pin, sync::{Arc, Condvar, M use async_io::Timer; use log::debug; -use crate::{ - container::{mknod_input_device::{ensure_input_device, remove_input_device}, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{self, delete_udev_data, ensure_udev_structure, read_udev_data, write_udev_data}}, jobs::job::{Job, JobTarget}, monitor_udev::EVENT_STORE, requesting_process::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace} -}; +use crate::{job_engine::job::{Job, JobTarget}, jobs::{mknod_input_device::remove_input_device, monitor_udev_job::EVENT_STORE, netlink_message::send_udev_monitor_message_with_properties, runtime_data::{delete_udev_data, ensure_udev_structure, read_udev_data, write_udev_data}}, requesting_process::{Pid, RequestingProcess, await_process, run_in_net_and_mnt_namespace}}; + #[derive(Clone,Debug,Copy,PartialOrd,PartialEq)] diff --git a/vuinputd/src/container/runtime_data.rs b/vuinputd/src/jobs/runtime_data.rs similarity index 100% rename from vuinputd/src/container/runtime_data.rs rename to vuinputd/src/jobs/runtime_data.rs diff --git a/vuinputd/src/main.rs b/vuinputd/src/main.rs index befb57b..59f1ef8 100644 --- a/vuinputd/src/main.rs +++ b/vuinputd/src/main.rs @@ -38,18 +38,17 @@ use std::sync::{Arc, Mutex, OnceLock, RwLock}; use uinput_ioctls::*; pub mod requesting_process; -pub mod monitor_udev; pub mod compat; use crate::compat::input_event_compat; -use crate::container::inject_in_container_job::InjectInContainerJob; -use crate::container::remove_from_container_job::RemoveFromContainerJob; -use crate::monitor_udev::MonitorBackgroundLoop; +use crate::jobs::inject_in_container_job::InjectInContainerJob; +use crate::jobs::monitor_udev_job::MonitorBackgroundLoop; +use crate::jobs::remove_from_container_job::RemoveFromContainerJob; use crate::requesting_process::*; -pub mod jobs; -use crate::jobs::job::*; +pub mod job_engine; +use crate::job_engine::job::*; -pub mod container; +pub mod jobs; #[derive(Debug)] @@ -351,7 +350,7 @@ unsafe extern "C" fn vuinput_release( let remove_job=RemoveFromContainerJob::new(vuinput_state.requesting_process.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); let awaiter = remove_job.get_awaiter_for_state(); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); - awaiter(&container::remove_from_container_job::State::Finished); + awaiter(&jobs::remove_from_container_job::State::Finished); } drop(vuinput_state); @@ -364,7 +363,15 @@ unsafe extern "C" fn vuinput_release( drop(vuinput_state_mutex); // this also closes the file when no other references are open // TODO: maybe also ensure that nothing is left in the containers - // This _must_ be fuse_reply_err. fuse_reply_none would lead to a deadlock of the uinput user. + // Note: For CUSE, the kernel always issues RELEASE via fuse_sync_release(), + // which forces a *synchronous* request (fuse_simple_request()). + // + // That means the kernel thread blocks until userspace sends a reply header. + // Calling fuse_reply_none() would send no header at all, causing the kernel + // to wait forever and the caller to deadlock. + // + // Therefore we must always send a real reply for RELEASE. + // `fuse_reply_err(req, 0)` is enough to wake the kernel and is safe here. fuse_lowlevel::fuse_reply_err(_req, 0); } @@ -522,7 +529,7 @@ unsafe extern "C" fn vuinput_ioctl( let inject_job=InjectInContainerJob::new(vuinput_state.requesting_process.clone(),devnode.clone(),sysname.clone(),major,minor); let awaiter = inject_job.get_awaiter_for_state(); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(inject_job)); - awaiter(&container::inject_in_container_job::State::Finished); + awaiter(&jobs::inject_in_container_job::State::Finished); debug!("fh {}: injecting dev-nodes in container has been finished ", fh); } @@ -542,7 +549,7 @@ unsafe extern "C" fn vuinput_ioctl( let remove_job=RemoveFromContainerJob::new(vuinput_state.requesting_process.clone(),input_device.devnode.clone(),input_device.syspath.clone(),input_device.major,input_device.minor); let awaiter = remove_job.get_awaiter_for_state(); JOB_DISPATCHER.get().unwrap().lock().unwrap().dispatch(Box::new(remove_job)); - awaiter(&container::remove_from_container_job::State::Finished); + awaiter(&jobs::remove_from_container_job::State::Finished); debug!("fh {}: removing dev-nodes from container has been finished ", fh); }