diff --git a/README.md b/README.md new file mode 100644 index 0000000..d404e1c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Wifi-Connector +Written in Rust. There will be GUI as well, hopefully. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a61610b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod network; diff --git a/src/main.rs b/src/main.rs index e8fad5c..16b2872 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,6 @@ -use std::{process::Command, vec}; use anyhow::*; - -struct Network { - in_use: bool, - ssid: String, - signal: u8, -} - -impl Network { - fn from_nmcli_stdout(line: String) { - for element in line.split(' ') { - println!("{}", element); - } - } -} +use std::{collections::HashMap, io::Write, process::Command, vec}; +use wifi_connector::network::Network; fn get_available_wifis() -> Result { let output = Command::new("nmcli") @@ -23,26 +10,147 @@ fn get_available_wifis() -> Result { .output() .expect("Failed to execute command"); - Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) + Ok(String::from_utf8_lossy(output.stdout.as_slice()).into()) } fn get_descriptor_positions(header_line: &String) -> Vec { let mut positions: Vec = vec![]; positions.push(0); - positions.push(header_line.find(" SSID").expect("SSID not found in header string") as u8 +1); - positions.push(header_line.find("SIGNAL").expect("SIGNAL not found in header string") as u8); + positions.push( + header_line + .find(" SSID") + .expect("SSID not found in header string") as u8 + + 1, + ); + positions.push( + header_line + .find("SIGNAL") + .expect("SIGNAL not found in header string") as u8, + ); return positions; } -fn main() { - let all_wifis: String = get_available_wifis().expect("Wifi fetching exploded"); - let positions = get_descriptor_positions(&all_wifis); - for position in (positions) { - println!("{}", position); +fn get_all_networks() -> Vec { + let nmcli_output: String = get_available_wifis().expect("Wifi fetching exploded"); + let positions = get_descriptor_positions(&nmcli_output); + + let mut all_wifi_lines: Vec = + nmcli_output.split('\n').map(|s| String::from(s)).collect(); + let mut all_networks: Vec = vec![]; + all_wifi_lines.remove(0); + + for line in all_wifi_lines { + if line.is_empty() { + continue; + } + all_networks.push(Network::from_nmcli_stdout(line.to_owned(), &positions)); + } + + return all_networks; +} + +fn connect_to_network(network: &Network, password: &str) -> Result<()> { + let output = Command::new("nmcli") + .arg("device") + .arg("wifi") + .arg("connect") + .arg(&network.ssid.trim()) + .arg("password") + .arg(password.trim()) + .output() + .expect("Failed to execute command"); + + if output.status.success() { + Ok(()) + } else { + Err(anyhow!("Failed to connect to network")) + } +} + +fn try_connect_to_network_without_password(network: &Network) -> Result<()> { + let output = Command::new("nmcli") + .arg("device") + .arg("wifi") + .arg("connect") + .arg(&network.ssid.trim()) + .output() + .expect("Failed to execute command"); + + if output.status.success() { + Ok(()) + } else { + Err(anyhow!("Couldn't connect to wifi without password")) + } +} + +fn print_networks(networks: &Vec) { + for (idx, network) in networks.iter().enumerate() { + println!("{} - {}", idx, network); + } +} + +fn password_prompt_for_network(network: &Network) -> String { + let mut password = String::new(); + print!( + "Now enter the password for the selected wifi '{}': ", + network.ssid + ); + std::io::stdout().flush().unwrap(); + std::io::stdin().read_line(&mut password).unwrap(); + password +} + +fn choose_network(max: usize) -> String { + println!("Which network do you choose? (0-{})", max); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input +} + +fn print_header() { + println!("Index - In use - SSID - Signal"); +} + +fn uniquify_networks(networks: Vec) -> Vec { + let mut unique_networks: HashMap = HashMap::new(); + + for network in &networks { + let current_ssid = &network.ssid; + if unique_networks.keys().any(|key| key == current_ssid) { + if unique_networks[current_ssid].signal < network.signal { + unique_networks.insert(current_ssid.to_owned(), network.to_owned()); + } + } else { + unique_networks.insert(current_ssid.to_owned(), network.to_owned()); + } + } + + return unique_networks.into_values().collect(); +} + +fn main() { + let all_networks = uniquify_networks(get_all_networks()); + + print_header(); + print_networks(&all_networks); + + let chosen_network_index = choose_network(all_networks.len() - 1); + + let network = all_networks + .get(chosen_network_index.trim().parse::().unwrap()) + .expect("Given number does not reference a network"); + let mut connected = try_connect_to_network_without_password(network); + + if !connected.is_ok() { + let password = password_prompt_for_network(network); + connected = connect_to_network(&network, password.trim()); + } + + if connected.is_ok() { + println!("Successfully connected to network '{}'", network.ssid); + } else { + println!("Connection failed"); } - // for line in all_wifis.split("\n") { - // Network::from_nmcli_stdout(line.to_owned()); - // } } diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 0000000..757813a --- /dev/null +++ b/src/network.rs @@ -0,0 +1,81 @@ +use std::fmt::Display; + +#[derive(Debug, Clone)] +pub struct Network { + pub in_use: bool, + pub ssid: String, + pub signal: u8, +} + +impl Network { + pub fn from_nmcli_stdout(line: String, header_positions: &Vec) -> Self { + let mut network = Self { + in_use: false, + ssid: "".to_string(), + signal: 0, + }; + + let ssid_pos = header_positions + .get(1) + .expect("header_positions has no index for SSID") + .to_owned() as usize; + let signal_pos = header_positions + .get(2) + .expect("header_positions has no index for SSID") + .to_owned() as usize; + + if line + .chars() + .nth( + header_positions + .get(0) + .expect("header_positions has no index 0") + .to_owned() as usize, + ) + .expect("Given line has no index 0") + == '*' + { + network.in_use = true; + } + + let mut current_spaces = 0; + for char in line.chars().skip(ssid_pos) { + if char == ' ' { + current_spaces += 1; + if current_spaces == 2 { + break; + } + } else { + if current_spaces == 1 { + network.ssid.push(' '); + } + current_spaces = 0; + network.ssid.push(char); + } + } + + let mut signal_as_string: String = line + .chars() + .nth(signal_pos) + .expect("Line has no Signal pos") + .to_string(); + + signal_as_string.push( + line.chars() + .nth(signal_pos + 1) + .expect("Line has no Signal pos"), + ); + + network.signal = signal_as_string + .parse::() + .expect("Could not parse signal"); + + return network; + } +} + +impl Display for Network { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + return write!(f, "{} - {} - {} ", self.in_use, self.ssid, self.signal); + } +}