use make_colors::make_colors_hex;
use crate::wlan::{ConnectionInfo, Interface, InterfaceState, NetworkInfo};
#[derive(Debug, Clone, Copy)]
pub struct DisplayOpts {
pub color: bool,
pub emoji: bool,
pub json: bool,
}
impl DisplayOpts {
pub fn from_cli(no_color: bool, no_emoji: bool, plain: bool, json: bool) -> Self {
let no_color_env = std::env::var_os("NO_COLOR").is_some();
Self {
color: !(no_color || plain || no_color_env),
emoji: !(no_emoji || plain),
json,
}
}
}
mod palette {
pub const CYAN: &str = "#00FFFF";
pub const GREEN: &str = "#33CC66";
pub const YELLOW: &str = "#FFD400";
pub const RED: &str = "#FF5555";
pub const MAGENTA: &str = "#CC66FF";
pub const GRAY: &str = "#888888";
pub const WHITE: &str = "#EEEEEE";
pub const BLUE: &str = "#4FA8FF";
}
fn paint(text: &str, hex: &str, opts: &DisplayOpts) -> String {
if !opts.color {
return text.to_string();
}
make_colors_hex(text, hex, None).unwrap_or_else(|_| text.to_string())
}
fn signal_color(quality: u8) -> &'static str {
if quality >= 70 {
palette::GREEN
} else if quality >= 40 {
palette::YELLOW
} else {
palette::RED
}
}
fn signal_bar(quality: u8, opts: &DisplayOpts) -> String {
const CELLS: usize = 10;
let filled = ((quality as usize * CELLS) / 100).min(CELLS);
let bar: String = "█".repeat(filled) + &"░".repeat(CELLS - filled);
let colored = paint(&bar, signal_color(quality), opts);
format!("{colored} {quality:>3}%")
}
fn state_glyph(state: InterfaceState, opts: &DisplayOpts) -> String {
if !opts.emoji {
return format!("[{state}]");
}
let emoji = match state {
InterfaceState::Connected => "🟢",
InterfaceState::Disconnected => "⚪",
InterfaceState::Associating | InterfaceState::Authenticating => "🟡",
InterfaceState::Discovering => "🔎",
InterfaceState::Disconnecting => "🟠",
InterfaceState::AdHocNetworkFormed => "🔗",
InterfaceState::NotReady => "🔴",
InterfaceState::Unknown => "❔",
};
format!("{emoji} {state}")
}
fn lock_glyph(secured: bool, opts: &DisplayOpts) -> &'static str {
if !opts.emoji {
return "";
}
if secured {
"🔒 "
} else {
"🔓 "
}
}
pub fn print_interfaces(interfaces: &[Interface], opts: &DisplayOpts) {
for (i, iface) in interfaces.iter().enumerate() {
if i > 0 {
println!();
}
print_interface(iface, opts);
}
}
pub fn print_interface(iface: &Interface, opts: &DisplayOpts) {
let label = if opts.emoji {
format!("📶 {}", iface.name)
} else {
iface.name.clone()
};
println!("{}", paint(&label, palette::CYAN, opts));
println!(
" {} {}",
paint("Description", palette::GRAY, opts),
iface.description
);
println!(
" {} {}",
paint("State", palette::GRAY, opts),
state_glyph(iface.state, opts)
);
match &iface.connection {
Some(conn) => print_connection(conn, opts),
None => {
let msg = if opts.emoji { "💤 not connected" } else { "not connected" };
println!(" {} {}", paint("Connection", palette::GRAY, opts), msg);
}
}
}
fn print_connection(conn: &ConnectionInfo, opts: &DisplayOpts) {
let secured = !conn.authentication.eq_ignore_ascii_case("open");
println!(
" {} {}{}",
paint("SSID", palette::GRAY, opts),
lock_glyph(secured, opts),
paint(&conn.ssid, palette::WHITE, opts)
);
println!(" {} {}", paint("BSSID", palette::GRAY, opts), conn.bssid);
println!(
" {} {}",
paint("Network type", palette::GRAY, opts),
conn.network_type
);
println!(
" {} {}",
paint("Radio type", palette::GRAY, opts),
paint(&conn.radio_type, palette::BLUE, opts)
);
println!(
" {} {}",
paint("Authentication", palette::GRAY, opts),
paint(&conn.authentication, palette::MAGENTA, opts)
);
println!(" {} {}", paint("Cipher", palette::GRAY, opts), conn.cipher);
println!(
" {} {}",
paint("Connection mode", palette::GRAY, opts),
conn.connection_mode
);
if let (Some(ch), Some(band)) = (conn.channel, conn.band_ghz) {
println!(
" {} {} ({band:.1} GHz)",
paint("Channel", palette::GRAY, opts),
ch
);
}
if let (Some(rx), Some(tx)) = (conn.rx_rate_mbps, conn.tx_rate_mbps) {
println!(
" {} rx {rx} Mbps / tx {tx} Mbps",
paint("Link rate", palette::GRAY, opts)
);
}
if let Some(q) = conn.signal_quality {
println!(
" {} {}",
paint("Signal", palette::GRAY, opts),
signal_bar(q, opts)
);
}
if let Some(profile) = &conn.profile_name {
println!(" {} {}", paint("Profile", palette::GRAY, opts), profile);
}
}
pub fn print_networks(networks: &[NetworkInfo], opts: &DisplayOpts) {
if networks.is_empty() {
println!("{}", paint("No networks visible.", palette::GRAY, opts));
return;
}
let mut sorted: Vec<&NetworkInfo> = networks.iter().collect();
sorted.sort_by_key(|net| std::cmp::Reverse(net.signal_quality));
for net in sorted {
let secured = !net.authentication.eq_ignore_ascii_case("open");
let marker = if net.already_connected {
if opts.emoji { "➡️ " } else { "* " }
} else {
" "
};
println!(
"{marker}{}{}",
lock_glyph(secured, opts),
paint(&net.ssid, palette::WHITE, opts)
);
println!(
" {} {}",
paint("Signal", palette::GRAY, opts),
signal_bar(net.signal_quality, opts)
);
println!(
" {} {} / {}",
paint("Security", palette::GRAY, opts),
paint(&net.authentication, palette::MAGENTA, opts),
net.encryption
);
if !net.connectable {
println!(
" {} not connectable right now",
paint("Note", palette::YELLOW, opts)
);
}
if let Some(profile) = &net.profile_name {
println!(
" {} {profile}",
paint("Saved profile", palette::GRAY, opts)
);
}
println!();
}
}