use super::common::list_saved_networks;
use crate::DriverCallback;
use crate::DriverContext;
use crate::{
DriverCategory,
types::{Driver, DriverParameter},
};
use anyhow::Result;
use serde_json::{Value, json};
use std::collections::HashMap;
use std::process::Command;
#[derive(Debug)]
pub struct WifiPrioritySetDriver;
#[async_trait::async_trait]
impl Driver for WifiPrioritySetDriver {
fn name(&self) -> &str {
"wifi_priority_set"
}
fn description(&self) -> &str {
"Set connection priority order for saved WiFi networks"
}
fn usage_hint(&self) -> &str {
"Use this skill to control which WiFi network your device connects to first when multiple known networks are in range. Higher priority networks are preferred."
}
fn parameters(&self) -> Vec<DriverParameter> {
vec![DriverParameter {
name: "priority_list".to_string(),
param_type: "array".to_string(),
description: "List of SSIDs in order of priority (first = highest priority)"
.to_string(),
required: true,
default: None,
example: Some(json!(["MyWiFi", "GuestWiFi", "OfficeNet"])),
enum_values: None,
}]
}
fn example_call(&self) -> Value {
json!({
"action": "wifi_priority_set",
"parameters": {
"priority_list": ["MyWiFi", "GuestWiFi", "OfficeNet"]
}
})
}
fn example_output(&self) -> String {
"WiFi priority set: MyWiFi (highest) > GuestWiFi > OfficeNet".to_string()
}
fn category(&self) -> DriverCategory {
DriverCategory::Wifi
}
async fn execute(
&self,
parameters: &HashMap<String, Value>,
callback: Option<&dyn DriverCallback>,
context: Option<&DriverContext>,
) -> Result<String> {
let priority_list = parameters
.get("priority_list")
.and_then(|v| v.as_array())
.ok_or_else(|| anyhow::anyhow!("Missing 'priority_list' parameter"))?;
let ssids: Vec<String> = priority_list
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
if ssids.is_empty() {
anyhow::bail!("priority_list must contain at least one SSID");
}
set_network_priority(&ssids)?;
let priority_display: Vec<String> = ssids
.iter()
.enumerate()
.map(|(i, ssid)| {
if i == 0 {
format!("{} (highest)", ssid)
} else {
ssid.clone()
}
})
.collect();
Ok(format!(
"WiFi priority set: {}",
priority_display.join(" > ")
))
}
}
#[cfg(target_os = "windows")]
fn set_network_priority(ssids: &[String]) -> Result<()> {
let output = Command::new("netsh")
.args(["wlan", "show", "profiles"])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut current_profiles: Vec<String> = Vec::new();
for line in stdout.lines() {
if line.contains(":") && !line.contains("All User Profile") {
if let Some(profile) = line.split(':').nth(1) {
let profile = profile.trim();
if !profile.is_empty() && ssids.contains(&profile.to_string()) {
current_profiles.push(profile.to_string());
}
}
}
}
for (priority, ssid) in ssids.iter().enumerate() {
if current_profiles.contains(ssid) {
Command::new("netsh")
.args([
"wlan",
"set",
"profile",
"order",
"name=",
ssid,
"priority=",
&priority.to_string(),
])
.output()?;
}
}
Ok(())
}
#[cfg(target_os = "linux")]
fn set_network_priority(ssids: &[String]) -> Result<()> {
for (priority, ssid) in ssids.iter().enumerate() {
let priority_value = (ssids.len() - priority) * 10;
let output = Command::new("nmcli")
.args(["-t", "-f", "NAME,TYPE", "connection", "show"])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let parts: Vec<&str> = line.split(':').collect();
if parts.len() >= 2 && parts[1] == "802-11-wireless" {
let conn_name = parts[0];
if conn_name.contains(ssid) || ssid.contains(conn_name) {
Command::new("nmcli")
.args([
"connection",
"modify",
conn_name,
"connection.autoconnect-priority",
&priority_value.to_string(),
])
.output()?;
break;
}
}
}
}
Ok(())
}
#[cfg(target_os = "macos")]
fn set_network_priority(ssids: &[String]) -> Result<()> {
let service_name = get_wifi_service_name()?;
let output = Command::new("networksetup")
.args(["-listpreferredwirelessnetworks", &service_name])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut new_order = Vec::new();
for ssid in ssids {
new_order.push(ssid.as_str());
}
for line in stdout.lines().skip(1) {
let line_ssid = line.trim_start_matches('*').trim();
if !ssids.contains(&line_ssid.to_string()) && !new_order.contains(&line_ssid) {
new_order.push(line_ssid);
}
}
let output = Command::new("networksetup")
.args(["-listpreferredwirelessnetworks", &service_name])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines().skip(1) {
let ssid = line.trim_start_matches('*').trim();
let _ = Command::new("networksetup")
.args(["-removepreferredwirelessnetwork", &service_name, ssid])
.output();
}
for ssid in new_order {
Command::new("networksetup")
.args(["-addpreferredwirelessnetwork", &service_name, ssid, "0"])
.output()?;
}
Ok(())
}
#[cfg(target_os = "macos")]
fn get_wifi_service_name() -> Result<String> {
let output = Command::new("networksetup")
.args(["-listallhardwareports"])
.output()?;
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.contains("Hardware Port: Wi-Fi") || line.contains("Hardware Port: AirPort") {
if i + 1 < lines.len() && lines[i + 1].contains("Device:") {
if let Some(device) = lines[i + 1].split(':').nth(1) {
return Ok(device.trim().to_string());
}
}
}
}
Ok("Wi-Fi".to_string())
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
fn set_network_priority(_ssids: &[String]) -> Result<()> {
anyhow::bail!("Priority setting not implemented on this platform")
}