diff --git a/Cargo.lock b/Cargo.lock index c0ebe7c..e498b80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,7 +361,6 @@ dependencies = [ "ksni", "shell-escape", "thistermination", - "users", ] [[package]] @@ -641,16 +640,6 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 4c6f96a..ed2bedf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,4 @@ 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" diff --git a/src/bin/hyper_headset_cli.rs b/src/bin/hyper_headset_cli.rs index 3cd8af1..f95ba14 100644 --- a/src/bin/hyper_headset_cli.rs +++ b/src/bin/hyper_headset_cli.rs @@ -5,118 +5,15 @@ use std::{ }; use clap::{Arg, Command}; -use hyper_headset::{debug_println, devices::connect_compatible_device}; -use std::process::Command as CommandShell; +use hyper_headset::{ + check_rule, debug_println, devices::connect_compatible_device, prompt_user_for_udev_rule, + update_rule, RuleState, UDEV_RULES, UDEV_RULE_PATH_SYSTEM, UDEV_RULE_PATH_USER, +}; 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." - ); - } - } - } - } + prompt_user_for_udev_rule(); let mut device = match connect_compatible_device() { Ok(device) => device, Err(error) => { diff --git a/src/lib.rs b/src/lib.rs index f73ac5c..2ed3ce3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +use std::{fs, io, process::Command, time::Duration}; + +// #![warn(missing_docs)] pub mod devices; #[macro_export] @@ -7,3 +10,112 @@ macro_rules! debug_println { println!($($args)*); }; } + +pub const UDEV_RULE_PATH_SYSTEM: &str = "/etc/udev/rules.d/99-HyperHeadset.rules"; +pub const UDEV_RULE_PATH_USER: &str = "/usr/lib/udev/rules.d/99-HyperHeadset.rules"; +pub 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 = Command::new("sudo") + .arg("sh") + .arg("-c") + .arg(format!( + "echo {} > {} && udevadm control --reload-rules && udevadm trigger", + shell_escape::escape(rules.into()), + shell_escape::escape(path.into()) + )) + .status(); + // a little delay so the rules are active before trying to connect + std::thread::sleep(Duration::from_millis(500)); + + 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."); + } + } +} + +pub fn prompt_user_for_udev_rule() { + let user_rule_state = check_rule(UDEV_RULE_PATH_USER, UDEV_RULES); + let system_rule_state = check_rule(UDEV_RULE_PATH_SYSTEM, UDEV_RULES); + + 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." + ); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 5e1b578..29de5b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,11 @@ use enigo::{Direction, Enigo, Key, Keyboard, Settings}; use std::time::Duration; mod status_tray; -use hyper_headset::devices::connect_compatible_device; +use hyper_headset::{devices::connect_compatible_device, prompt_user_for_udev_rule}; use status_tray::{StatusTray, TrayHandler}; fn main() { + prompt_user_for_udev_rule(); let matches = Command::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS"))