mod cli;
mod config;
mod display;
mod error;
mod select;
mod wlan;
use std::process::ExitCode;
use std::time::Duration;
use clap::CommandFactory;
use clap_version_flag::{colorful_version, parse_with_version};
use cli::Cli;
use display::DisplayOpts;
use error::{IwconfError, Result};
fn main() -> ExitCode {
let version = colorful_version!();
let cli: Cli = match parse_with_version(Cli::command(), &version) {
Ok(cli) => cli,
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
}
};
match run(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("iwconf: {e}");
ExitCode::FAILURE
}
}
}
#[cfg(not(windows))]
fn run(_cli: Cli) -> Result<()> {
Err(IwconfError::UnsupportedPlatform)
}
#[cfg(windows)]
fn run(cli: Cli) -> Result<()> {
let settings = config::load()?;
let opts = DisplayOpts::from_cli(
cli.no_color || settings.no_color,
cli.no_emoji || settings.no_emoji,
cli.plain,
cli.json,
);
let timeout = Duration::from_secs(cli.timeout.unwrap_or(settings.connect_timeout));
let handle = wlan::native::WlanHandle::open()?;
let interfaces = wlan::native::list_interfaces(&handle)?;
let pattern = cli.iface.clone().or_else(|| settings.default_iface.clone());
if cli.is_action() {
let iface = match &pattern {
Some(p) => select::select(&interfaces, p, cli.regex)?,
None if interfaces.len() == 1 => &interfaces[0],
None => {
return Err(IwconfError::Other(
"multiple interfaces present; use -i/--iface to pick one".into(),
))
}
};
let guid_str = iface.guid.clone();
let guid = parse_guid(&guid_str)?;
if cli.disconnect {
wlan::native::disconnect(&handle, &guid)?;
println!("{}", success_line("disconnected", &iface.name, &opts));
return Ok(());
}
if let Some(ssid) = &cli.essid {
if cli.profile.is_none() && cli.key.is_none() {
let networks = wlan::native::available_networks(&handle, &guid)?;
let target = networks.iter().find(|n| &n.ssid == ssid);
if let Some(net) = target {
if !net.authentication.eq_ignore_ascii_case("open")
&& net.profile_name.is_none()
{
return Err(IwconfError::MissingPassphrase);
}
}
}
let outcome = wlan::native::connect(
&handle,
&guid,
ssid,
cli.key.as_deref(),
cli.profile.as_deref(),
timeout,
)?;
match outcome {
wlan::ConnectOutcome::Connected => {
println!("{}", success_line(&format!("connected to {ssid}"), &iface.name, &opts));
}
wlan::ConnectOutcome::TimedOut => {
return Err(IwconfError::ConnectTimeout(ssid.clone()));
}
wlan::ConnectOutcome::Failed => {
return Err(IwconfError::Other(format!("failed to connect to {ssid}")));
}
}
}
return Ok(());
}
let selected: Vec<&wlan::Interface> = match &pattern {
Some(p) => vec![select::select(&interfaces, p, cli.regex)?],
None => interfaces.iter().collect(),
};
if cli.scan {
for iface in &selected {
let guid = parse_guid(&iface.guid)?;
if cli.rescan {
wlan::native::scan(&handle, &guid)?;
std::thread::sleep(Duration::from_secs(2));
}
let networks = wlan::native::available_networks(&handle, &guid)?;
if opts.json {
println!("{}", serde_json::to_string_pretty(&networks).unwrap_or_default());
} else {
if selected.len() > 1 {
println!("== {} ==", iface.name);
}
display::print_networks(&networks, &opts);
}
}
return Ok(());
}
if opts.json {
let owned: Vec<&wlan::Interface> = selected;
println!("{}", serde_json::to_string_pretty(&owned).unwrap_or_default());
} else {
let owned: Vec<wlan::Interface> = selected.into_iter().cloned().collect();
display::print_interfaces(&owned, &opts);
}
Ok(())
}
#[cfg(windows)]
fn parse_guid(s: &str) -> Result<windows::core::GUID> {
if s.len() != 36 {
return Err(IwconfError::Other(format!("malformed interface GUID: {s}")));
}
Ok(windows::core::GUID::from(s))
}
fn success_line(action: &str, iface_name: &str, opts: &DisplayOpts) -> String {
let check = if opts.emoji { "✅ " } else { "" };
format!("{check}{iface_name}: {action}")
}