Draft creating udev rules automatically

This commit is contained in:
Lennard Kittner
2026-03-12 10:20:01 +01:00
parent a73023d96d
commit d7806619ec
3 changed files with 133 additions and 2 deletions

18
Cargo.lock generated
View File

@@ -359,7 +359,9 @@ dependencies = [
"enigo",
"hidapi",
"ksni",
"shell-escape",
"thistermination",
"users",
]
[[package]]
@@ -551,6 +553,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "shell-escape"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
[[package]]
name = "shlex"
version = "1.3.0"
@@ -633,6 +641,16 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "users"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
dependencies = [
"libc",
"log",
]
[[package]]
name = "utf8parse"
version = "0.2.2"

View File

@@ -12,3 +12,5 @@ hidapi = { path = "vendor/hidapi" }
thistermination = "1.0.0"
[target.'cfg(target_os = "linux")'.dependencies]
ksni = "0.2.0"
users = "0.11"
shell-escape = "0.1.5"

View File

@@ -1,11 +1,122 @@
use std::time::Duration;
use std::{
fs,
io::{self},
time::Duration,
};
use clap::{Arg, Command};
use hyper_headset::devices::connect_compatible_device;
use hyper_headset::{debug_println, devices::connect_compatible_device};
use std::process::Command as CommandShell;
const SHOW_ALL_OPTIONS: bool = false;
const UDEV_RULE_PATH_SYSTEM: &str = "/etc/udev/rules.d/99-HyperHeadset.rules";
const UDEV_RULE_PATH_USER: &str = "/usr/lib/udev/rules.d/99-HyperHeadset.rules";
const UDEV_RULES: &str = include_str!("./../../99-HyperHeadset.rules");
#[derive(Debug)]
pub enum RuleState {
RuleExists(bool),
RuleMatch(bool),
}
pub fn check_rule(path: &str, rules: &str) -> RuleState {
let mut rule_state;
if !fs::exists(path).unwrap_or(false) {
rule_state = RuleState::RuleExists(false);
} else {
rule_state = RuleState::RuleExists(true);
if let Ok(content) = fs::read_to_string(path) {
if content.trim() != rules.trim() {
rule_state = RuleState::RuleMatch(false);
} else {
rule_state = RuleState::RuleMatch(true);
}
}
}
rule_state
}
pub fn update_rule(path: &str, rules: &str) {
let status = CommandShell::new("sudo")
.arg("sh")
.arg("-c")
.arg(format!(
"echo {} > {}",
shell_escape::escape(rules.into()),
path
))
.status();
match status {
Ok(exit_status) if exit_status.success() => {
println!("created rule at {path}.\nYou may need to replug your headset for the udev rules to take effect.");
}
Ok(e) => {
println!("Failed to create rule at {path}: {}", e);
println!("Your headset may not be recognized without the correct udev rules.");
}
Err(e) => {
println!("Failed to create rule at {path}: {}", e);
println!("Your headset may not be recognized without the correct udev rules.");
}
}
}
fn main() {
let user_rule_state = check_rule(UDEV_RULE_PATH_USER, UDEV_RULES);
let system_rule_state = check_rule(UDEV_RULE_PATH_SYSTEM, UDEV_RULES);
if users::get_current_uid() != 0 {
debug_println!("user rule: {user_rule_state:?}, system rule: {system_rule_state:?}");
match (user_rule_state, system_rule_state) {
(RuleState::RuleMatch(true), _) => (),
(_, RuleState::RuleMatch(true)) => (),
(RuleState::RuleMatch(false), _) | (RuleState::RuleExists(true), _) => {
print!(
"Udev rules at {UDEV_RULE_PATH_USER} do not have the expected value. Do you want to recreate them? (y/N): "
);
io::Write::flush(&mut io::stdout()).unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if matches!(input.trim(), "y" | "Y") {
update_rule(UDEV_RULE_PATH_USER, UDEV_RULES);
} else {
println!("Your headset may not be recognized without the correct udev rules.");
}
}
(RuleState::RuleExists(false), RuleState::RuleMatch(false))
| (RuleState::RuleExists(false), RuleState::RuleExists(true)) => {
print!(
"Udev rules at {UDEV_RULE_PATH_SYSTEM} do not have the expected value. Do you want to recreate them? (y/N): "
);
io::Write::flush(&mut io::stdout()).unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if matches!(input.trim(), "y" | "Y") {
update_rule(UDEV_RULE_PATH_SYSTEM, UDEV_RULES);
} else {
println!("Your headset may not be recognized without the correct udev rules.");
}
}
(RuleState::RuleExists(false), RuleState::RuleExists(false)) => {
print!("No udev rules found. Do you want to create {UDEV_RULE_PATH_USER}? (y/N): ");
io::Write::flush(&mut io::stdout()).unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if matches!(input.trim(), "y" | "Y") {
update_rule(UDEV_RULE_PATH_USER, UDEV_RULES);
} else {
println!(
"Without udev rules your headset can only be accessed when running as root."
);
}
}
}
}
let mut device = match connect_compatible_device() {
Ok(device) => device,
Err(error) => {