166 lines
5.8 KiB
Rust
166 lines
5.8 KiB
Rust
#[cfg(target_os = "linux")]
|
|
use std::{fs, io, process::Command, time::Duration};
|
|
|
|
#[cfg(target_os = "linux")]
|
|
use dialog::{Choice, DialogBox};
|
|
|
|
// #![warn(missing_docs)]
|
|
pub mod devices;
|
|
|
|
#[macro_export]
|
|
macro_rules! debug_println {
|
|
($($args:tt)*) => {
|
|
#[cfg(debug_assertions)]
|
|
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),
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
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
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn act_as_askpass_handler() -> ! {
|
|
let a = dialog::Password::new("Created rule at /usr/lib/udev/rules.d/99-HyperHeadset.rules")
|
|
.title("HyperHeadset")
|
|
.show()
|
|
.expect("Failed to open dialog");
|
|
println!("{}", a.unwrap_or("".to_string()));
|
|
std::process::exit(0)
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn update_rule(path: &str, rules: &str) {
|
|
let status = if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
|
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()
|
|
} else {
|
|
Command::new("sudo")
|
|
.env("SUDO_ASKPASS", std::env::current_exe().unwrap())
|
|
.arg("--askpass")
|
|
.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() => {
|
|
show_message(&format!("created rule at {path}.\nYou may need to replug your headset for the udev rules to take effect."));
|
|
}
|
|
Ok(e) => {
|
|
show_message(&format!("Failed to create rule at {path}: {}.\nYour headset may not be recognized without the correct udev rules.", e));
|
|
}
|
|
Err(e) => {
|
|
show_message(&format!("Failed to create rule at {path}: {}\nYour headset may not be recognized without the correct udev rules.", e));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn show_message(message: &str) {
|
|
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
|
println!("{message}");
|
|
} else {
|
|
let _ = dialog::Message::new(message.to_string())
|
|
.title("HyperHeadset")
|
|
.show();
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn handle_udev_rule_user_interaction(path: &str, ask_message: &str, decline_message: &str) {
|
|
if std::io::IsTerminal::is_terminal(&std::io::stdin()) {
|
|
print!("{ask_message} (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(path, UDEV_RULES);
|
|
} else {
|
|
println!("{decline_message}");
|
|
}
|
|
} else if dialog::Question::new(ask_message.to_string())
|
|
.title("HyperHeadset")
|
|
.show()
|
|
.unwrap_or(Choice::No)
|
|
== Choice::Yes
|
|
{
|
|
update_rule(path, UDEV_RULES);
|
|
} else {
|
|
let _ = dialog::Message::new(decline_message.to_string())
|
|
.title("HyperHeadset")
|
|
.show();
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
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), _) => {
|
|
handle_udev_rule_user_interaction(UDEV_RULE_PATH_USER,
|
|
&format!("Udev rules at {UDEV_RULE_PATH_USER} do not have the expected value. Do you want to recreate them?"),
|
|
"Your headset may not be recognized without the correct udev rules.");
|
|
}
|
|
(RuleState::RuleExists(false), RuleState::RuleMatch(false))
|
|
| (RuleState::RuleExists(false), RuleState::RuleExists(true)) => {
|
|
handle_udev_rule_user_interaction(UDEV_RULE_PATH_SYSTEM,
|
|
&format!("Udev rules at {UDEV_RULE_PATH_SYSTEM} do not have the expected value. Do you want to recreate them?"),
|
|
"Your headset may not be recognized without the correct udev rules.");
|
|
}
|
|
|
|
(RuleState::RuleExists(false), RuleState::RuleExists(false)) => {
|
|
handle_udev_rule_user_interaction(
|
|
UDEV_RULE_PATH_USER,
|
|
&format!("No udev rules found. Do you want to create {UDEV_RULE_PATH_USER}?"),
|
|
"Without udev rules your headset can only be accessed when running as root.",
|
|
);
|
|
}
|
|
}
|
|
}
|