From 1fcbeb5630d9944330fe9b00f86b44b6f286f702 Mon Sep 17 00:00:00 2001 From: Zewei Yang Date: Thu, 16 Apr 2026 09:59:17 +0800 Subject: [PATCH] muvm: gracefully disable hidpipe when /dev/input is missing Treat missing /dev/input as non-fatal, skip hidpipe setup, and avoid starting guest hidpipe when host hidpipe is disabled. Signed-off-by: Zewei Yang --- crates/muvm/src/bin/muvm.rs | 29 +++++++++++++++---------- crates/muvm/src/guest/bin/muvm-guest.rs | 16 +++++++++----- crates/muvm/src/hidpipe_server.rs | 16 +++++++++----- crates/muvm/src/utils/launch.rs | 6 +++++ 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/crates/muvm/src/bin/muvm.rs b/crates/muvm/src/bin/muvm.rs index 0827e8db..0402ed9b 100644 --- a/crates/muvm/src/bin/muvm.rs +++ b/crates/muvm/src/bin/muvm.rs @@ -291,6 +291,7 @@ fn main() -> Result { } } + let mut enable_hidpipe = false; if let Ok(run_path) = env::var("XDG_RUNTIME_DIR") { let pulse_path = Path::new(&run_path).join("pulse/native"); if pulse_path.exists() { @@ -309,19 +310,22 @@ fn main() -> Result { } let hidpipe_path = Path::new(&run_path).join("hidpipe"); - spawn_hidpipe_server(hidpipe_path.clone()).context("Failed to spawn hidpipe thread")?; - let hidpipe_path = CString::new( - hidpipe_path - .to_str() - .expect("hidpipe_path should not contain invalid UTF-8"), - ) - .context("Failed to process `hidpipe` path as it contains NUL character")?; + enable_hidpipe = + spawn_hidpipe_server(hidpipe_path.clone()).context("Failed to spawn hidpipe thread")?; + if enable_hidpipe { + let hidpipe_path = CString::new( + hidpipe_path + .to_str() + .expect("hidpipe_path should not contain invalid UTF-8"), + ) + .context("Failed to process `hidpipe` path as it contains NUL character")?; - // SAFETY: `hidpipe_path` is a pointer to a `CString` with long enough lifetime. - let err = unsafe { krun_add_vsock_port(ctx_id, HIDPIPE_SOCKET, hidpipe_path.as_ptr()) }; - if err < 0 { - let err = Errno::from_raw_os_error(-err); - return Err(err).context("Failed to configure vsock for hidpipe socket"); + // SAFETY: `hidpipe_path` is a pointer to a `CString` with long enough lifetime. + let err = unsafe { krun_add_vsock_port(ctx_id, HIDPIPE_SOCKET, hidpipe_path.as_ptr()) }; + if err < 0 { + let err = Errno::from_raw_os_error(-err); + return Err(err).context("Failed to configure vsock for hidpipe socket"); + } } let socket_dir = Path::new(&run_path).join("krun/socket"); @@ -413,6 +417,7 @@ fn main() -> Result { cwd, init_commands, user_init_commands: options.user_init_commands, + enable_hidpipe, }; let mut muvm_config_file = NamedTempFile::new() .context("Failed to create a temporary file to store the muvm guest config")?; diff --git a/crates/muvm/src/guest/bin/muvm-guest.rs b/crates/muvm/src/guest/bin/muvm-guest.rs index 6bac1838..0760d454 100644 --- a/crates/muvm/src/guest/bin/muvm-guest.rs +++ b/crates/muvm/src/guest/bin/muvm-guest.rs @@ -147,12 +147,16 @@ fn main() -> Result { setup_x11_forwarding(run_path, &host_display)?; } - let uid = options.uid; - thread::spawn(move || { - if catch_unwind(|| start_hidpipe(uid)).is_err() { - eprintln!("hidpipe thread crashed, input device passthrough will no longer function"); - } - }); + if options.enable_hidpipe { + let uid = options.uid; + thread::spawn(move || { + if catch_unwind(|| start_hidpipe(uid)).is_err() { + eprintln!( + "hidpipe thread crashed, input device passthrough will no longer function" + ); + } + }); + } thread::spawn(|| { if catch_unwind(start_pwbridge).is_err() { diff --git a/crates/muvm/src/hidpipe_server.rs b/crates/muvm/src/hidpipe_server.rs index 04022185..9cb08ee3 100644 --- a/crates/muvm/src/hidpipe_server.rs +++ b/crates/muvm/src/hidpipe_server.rs @@ -280,12 +280,18 @@ where } } -pub fn spawn_hidpipe_server(socket_path: PathBuf) -> anyhow::Result<()> { +pub fn spawn_hidpipe_server(socket_path: PathBuf) -> anyhow::Result { let mut evdevs = EvdevContainer::new(); let epoll = Epoll::new(EpollCreateFlags::empty()).context("Failed to create epoll object")?; - for dir_ent in - fs::read_dir("/dev/input/").context("Failed to read \"/dev/input/\" directory")? - { + let dir_entries = match fs::read_dir("/dev/input/") { + Ok(entries) => entries, + Err(err) if err.kind() == ErrorKind::NotFound => { + debug!("Skipping hidpipe initialization: /dev/input is not present"); + return Ok(false); + }, + Err(err) => return Err(err).context("Failed to read \"/dev/input/\" directory"), + }; + for dir_ent in dir_entries { let dir_ent = dir_ent.context("Failed to read directory entry")?; if dir_ent .file_type() @@ -321,7 +327,7 @@ pub fn spawn_hidpipe_server(socket_path: PathBuf) -> anyhow::Result<()> { .unwrap(); thread::spawn(move || run(evdevs, listen_sock, epoll)); - Ok(()) + Ok(true) } fn run(mut evdevs: EvdevContainer, listen_sock: UnixListener, epoll: Epoll) { diff --git a/crates/muvm/src/utils/launch.rs b/crates/muvm/src/utils/launch.rs index 6a50d5a6..fdb000a4 100644 --- a/crates/muvm/src/utils/launch.rs +++ b/crates/muvm/src/utils/launch.rs @@ -46,6 +46,12 @@ pub struct GuestConfiguration { pub cwd: PathBuf, pub init_commands: Vec, pub user_init_commands: Vec, + #[serde(default = "default_true")] + pub enable_hidpipe: bool, +} + +fn default_true() -> bool { + true } pub const PULSE_SOCKET: u32 = 3333;