Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 42 additions & 44 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use std::env;
use std::error::Error;
use std::process;
use tokio::signal::unix::SignalKind;
use zbus::fdo::ObjectManager;
use zbus::Connection;

Expand Down Expand Up @@ -43,6 +44,12 @@ async fn main() -> Result<(), Box<dyn Error>> {

log::info!("Starting InputPlumber v{}", VERSION);

// Unhide any devices previously hidden by InputPlumber. This can happen
// if InputPlumber is killed before it can restore the devices.
if let Err(e) = unhide_all().await {
log::debug!("Failed to unhide devices at startup: {e}");
}

// Configure the DBus connection
let connection = Connection::system().await?;

Expand All @@ -54,57 +61,48 @@ async fn main() -> Result<(), Box<dyn Error>> {
.at(object_manager_path, object_manager)
.await?;

// Request the named bus
if let Err(err) = connection.request_name(BUS_NAME).await {
log::error!("Error requesting dbus name: {err}");
process::exit(-1);
}

// Create an InputManager instance
let mut input_manager = Manager::new(connection.clone());

let (ctrl_c_result, input_man_result, request_name_result) = tokio::join!(
// Setup CTRL+C handler
tokio::spawn(async move {
tokio::signal::ctrl_c().await.unwrap();
log::info!("Un-hiding all devices");
if let Err(e) = unhide_all().await {
log::error!("Unable to un-hide devices: {:?}", e);
}
log::info!("Shutting down");
process::exit(0);
}),
// Start the input manager and listen on DBus
input_manager.run(),
// Request the named bus
connection.request_name(BUS_NAME)
);
// Setup signal handlers
let mut sig_term = tokio::signal::unix::signal(SignalKind::terminate())?;
let mut sig_int = tokio::signal::unix::signal(SignalKind::interrupt())?;

match ctrl_c_result {
Ok(_) => {
log::info!("The input manager task has exited");
}
Err(err) => {
log::error!("Error in joining ctrl+C watcher: {err}");
return Err(Box::new(err) as Box<dyn Error>);
// Start the main run loop
let mut exit_code = 0;
tokio::select! {
// Start the input manager and listen on DBus
result = input_manager.run() => {
if let Err(err) = result {
log::error!("Error running input manager: {err}");
exit_code = -1;
}
},
// Setup CTRL+C handler
_ = tokio::signal::ctrl_c() => {
log::info!("Received CTRL+C. Shutting down.");
},
// Setup SIGINT handler
_ = sig_int.recv() => {
log::info!("Received SIGINT. Shutting down.");
},
// Setup SIGTERM handler
_ = sig_term.recv() => {
log::info!("Received SIGTERM. Shutting down.");
}
}

match request_name_result {
Ok(_) => {
log::info!("The input manager task has exited");
}
Err(err) => {
log::error!("Error in joining dbus request name operation: {err}");
return Err(Box::new(err));
}
};

match input_man_result {
Ok(_) => {
log::info!("The input manager task has exited");
}
Err(err) => {
log::error!("Error in joining ctrl+C watcher: {err}");
return Err(err);
}
};
// Unhide all devices on shutdown
if let Err(e) = unhide_all().await {
log::error!("Unable to un-hide devices: {:?}", e);
}

log::info!("InputPlumber stopped");

Ok(())
process::exit(exit_code);
}
72 changes: 67 additions & 5 deletions src/udev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,33 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
return Err("Unable to create match rule for device".into());
};

// Create the directory to move devnodes to
tokio::fs::create_dir_all("/dev/inputplumber/sources").await?;

// Find the chmod command to use for hiding
let chmod_cmd = if Path::new("/bin/chmod").exists() {
"/bin/chmod"
"/bin/chmod".to_string()
} else if Path::new("/usr/bin/chmod").exists() {
"/usr/bin/chmod".to_string()
} else {
let output = Command::new("which").arg("chmod").output().await?;
if !output.status.success() {
return Err("Unable to determine chmod command location".into());
}
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
};

// Find the mv command to use for hiding
let mv_cmd = if Path::new("/bin/mv").exists() {
"/bin/mv".to_string()
} else if Path::new("/usr/bin/mv").exists() {
"/usr/bin/mv".to_string()
} else {
"/usr/bin/chmod"
let output = Command::new("which").arg("mv").output().await?;
if !output.status.success() {
return Err("Unable to determine mv command location".into());
}
str::from_utf8(output.stdout.as_slice())?.trim().to_string()
};

// Create an early udev rule to hide the device
Expand All @@ -44,8 +66,10 @@ pub async fn hide_device(path: &str) -> Result<(), Box<dyn Error>> {
{match_rule}, GOTO="inputplumber_valid"
GOTO="inputplumber_end"
LABEL="inputplumber_valid"
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN:="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN:="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/input/%k", SYMLINK+="inputplumber/by-hidden/%k"
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE:="0000", GROUP:="root", RUN+="{chmod_cmd} 000 /dev/%k", SYMLINK+="inputplumber/by-hidden/%k"
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
LABEL="inputplumber_end"
"#
);
Expand All @@ -65,6 +89,8 @@ GOTO="inputplumber_end"
LABEL="inputplumber_valid"
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/input/%k"
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", MODE="000", GROUP="root", TAG-="uaccess", RUN+="{chmod_cmd} 000 /dev/%k"
KERNEL=="js[0-9]*|event[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/input/%k /dev/inputplumber/sources/%k"
KERNEL=="hidraw[0-9]*", SUBSYSTEM=="{subsystem}", RUN+="{mv_cmd} /dev/%k /dev/inputplumber/sources/%k"
LABEL="inputplumber_end"
"#
);
Expand All @@ -83,19 +109,33 @@ LABEL="inputplumber_end"
pub async fn unhide_device(path: String) -> Result<(), Box<dyn Error>> {
// Get the device to unhide
let device = get_device(path.clone()).await?;
let name = device.name.clone();
let name = device.name.as_str();
let Some(parent) = device.get_parent() else {
return Err("Unable to determine parent for device".into());
};
let rule_path = format!(
"{RULES_PREFIX}/{RULE_HIDE_DEVICE_EARLY_PRIORITY}-inputplumber-hide-{name}-early.rules"
);
log::debug!("Removing hide rule: {rule_path}");
fs::remove_file(rule_path)?;
let rule_path = format!(
"{RULES_PREFIX}/{RULE_HIDE_DEVICE_LATE_PRIORITY}-inputplumber-hide-{name}-late.rules"
);
log::debug!("Removing hide rule: {rule_path}");
fs::remove_file(rule_path)?;

// Move the device back
let src_path = format!("/dev/inputplumber/sources/{name}");
let dst_path = if name.starts_with("event") || name.starts_with("js") {
format!("/dev/input/{name}")
} else {
format!("/dev/{name}")
};
log::debug!("Restoring device node path '{src_path}' to '{dst_path}'");
if let Err(e) = fs::rename(&src_path, &dst_path) {
log::warn!("Failed to move device node from {src_path} to {dst_path}: {e}");
}

// Reload udev
reload_children(parent).await?;

Expand All @@ -104,6 +144,7 @@ pub async fn unhide_device(path: String) -> Result<(), Box<dyn Error>> {

/// Unhide all devices hidden by InputPlumber
pub async fn unhide_all() -> Result<(), Box<dyn Error>> {
// Remove all created udev rules
let entries = fs::read_dir(RULES_PREFIX)?;
for entry in entries {
let Ok(entry) = entry else {
Expand All @@ -114,9 +155,30 @@ pub async fn unhide_all() -> Result<(), Box<dyn Error>> {
continue;
}
let path = entry.path().to_string_lossy().to_string();
log::debug!("Removing hide rule: {path}");
fs::remove_file(path)?;
}

// Move all devices back
let entries = fs::read_dir("/dev/inputplumber/sources")?;
for entry in entries {
let Ok(entry) = entry else {
continue;
};
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
let name = name.as_str();
let dst_path = if name.starts_with("event") || name.starts_with("js") {
format!("/dev/input/{name}")
} else {
format!("/dev/{name}")
};
log::debug!("Restoring device node path {path:?} to '{dst_path}'");
if let Err(e) = fs::rename(&path, &dst_path) {
log::warn!("Failed to move device node from {path:?} to {dst_path}: {e}");
}
}

// Reload udev rules
reload_all().await?;

Expand Down