use crate::DriverError;
use crate::result::DriverResult;
use serde::{Deserialize, Serialize};
use std::process::Command;
use tracing::{debug, info, warn};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BluetoothDevice {
pub name: String,
pub mac_address: String,
pub device_type: String,
pub paired: bool,
pub connected: bool,
pub rssi: Option<i32>,
pub battery_level: Option<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BluetoothAdapterStatus {
pub powered_on: bool,
pub discoverable: bool,
pub pairable: bool,
pub name: String,
pub mac_address: String,
pub discoverable_timeout: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BluetoothService {
pub uuid: String,
pub name: String,
pub primary: bool,
pub characteristics: Vec<BluetoothCharacteristic>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BluetoothCharacteristic {
pub uuid: String,
pub name: String,
pub properties: Vec<String>,
pub value: Option<Vec<u8>>,
}
#[cfg(target_os = "windows")]
pub fn get_adapter_status() -> DriverResult<BluetoothAdapterStatus> {
debug!("Getting adapter status on Windows");
let output = crate::common::hidden_cmd("powershell")
.args(["-Command", "Get-PnpDevice -Class Bluetooth | Select-Object Status, FriendlyName"])
.output()
.map_err(|e| {
let err_msg = format!("Failed to execute PowerShell: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut powered_on = false;
let mut name = "Unknown".to_string();
for line in stdout.lines() {
if line.contains("OK") || line.contains("正在运行") {
powered_on = true;
debug!("Bluetooth adapter is powered on");
}
if line.contains("Bluetooth") && !line.contains("Status") {
name = line.trim().to_string();
debug!("Adapter name: {}", name);
}
}
let mac_address = get_mac_address()?;
debug!("Adapter MAC address: {}", mac_address);
return Ok(BluetoothAdapterStatus { powered_on, discoverable: false, pairable: true, name, mac_address, discoverable_timeout: 120 });
}
#[cfg(target_os = "linux")]
pub fn get_adapter_status() -> DriverResult<BluetoothAdapterStatus> {
debug!("Getting adapter status on Linux");
let output = crate::common::hidden_cmd("bluetoothctl").args(["show"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut powered_on = false;
let mut discoverable = false;
let mut pairable = true;
let mut name = "Unknown".to_string();
let mut mac_address = "Unknown".to_string();
let mut discoverable_timeout = 0;
for line in stdout.lines() {
if line.contains("Powered:") && line.contains("yes") {
powered_on = true;
debug!("Adapter is powered on");
}
if line.contains("Discoverable:") && line.contains("yes") {
discoverable = true;
debug!("Adapter is discoverable");
}
if line.contains("Name:") {
if let Some(n) = line.split(':').nth(1) {
name = n.trim().to_string();
debug!("Adapter name: {}", name);
}
}
if line.contains("Address:") {
if let Some(addr) = line.split(':').nth(1) {
mac_address = addr.trim().to_string();
debug!("Adapter MAC address: {}", mac_address);
}
}
if line.contains("DiscoverableTimeout:") {
if let Some(t) = line.split(':').nth(1) {
discoverable_timeout = t.trim().parse().unwrap_or(0);
debug!("Discoverable timeout: {}s", discoverable_timeout);
}
}
}
return Ok(BluetoothAdapterStatus { powered_on, discoverable, pairable, name, mac_address, discoverable_timeout });
}
#[cfg(target_os = "macos")]
pub fn get_adapter_status() -> DriverResult<BluetoothAdapterStatus> {
debug!("Getting adapter status on macOS");
let output = crate::common::hidden_cmd("system_profiler").args(["SPBluetoothDataType"]).output().map_err(|e| {
let err_msg = format!("Failed to execute system_profiler: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut powered_on = false;
let mut name = "Unknown".to_string();
let mut mac_address = "Unknown".to_string();
for line in stdout.lines() {
if line.contains("Bluetooth Power: On") {
powered_on = true;
debug!("Adapter is powered on");
}
if line.contains("Name:") {
if let Some(n) = line.split(':').nth(1) {
name = n.trim().to_string();
debug!("Adapter name: {}", name);
}
}
if line.contains("Address:") {
if let Some(addr) = line.split(':').nth(1) {
mac_address = addr.trim().to_string();
debug!("Adapter MAC address: {}", mac_address);
}
}
}
return Ok(BluetoothAdapterStatus { powered_on, discoverable: true, pairable: true, name, mac_address, discoverable_timeout: 120 });
}
pub fn get_mac_address() -> DriverResult<String> {
debug!("Getting Bluetooth MAC address");
#[cfg(target_os = "linux")]
{
let output = crate::common::hidden_cmd("bluetoothctl").args(["show"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("Address:") {
if let Some(addr) = line.split(':').nth(1) {
let mac = addr.trim().to_string();
debug!("MAC address found: {}", mac);
return Ok(mac);
}
}
}
}
#[cfg(target_os = "windows")]
{
let output = crate::common::hidden_cmd("getmac").output().map_err(|e| {
let err_msg = format!("Failed to execute getmac: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("Bluetooth") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 1 {
let mac = parts[0].to_string();
debug!("MAC address found: {}", mac);
return Ok(mac);
}
}
}
}
#[cfg(target_os = "macos")]
{
let output = crate::common::hidden_cmd("system_profiler").args(["SPBluetoothDataType"]).output().map_err(|e| {
let err_msg = format!("Failed to execute system_profiler: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if line.contains("Address:") {
if let Some(addr) = line.split(':').nth(1) {
let mac = addr.trim().to_string();
debug!("MAC address found: {}", mac);
return Ok(mac);
}
}
}
}
warn!("MAC address not found, returning 'Unknown'");
return Ok("Unknown".to_string());
}
#[cfg(target_os = "linux")]
pub fn bluetooth_on() -> DriverResult<()> {
debug!("Turning Bluetooth on (Linux)");
crate::common::hidden_cmd("bluetoothctl").args(["power", "on"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Bluetooth turned on");
return Ok(());
}
#[cfg(target_os = "windows")]
pub fn bluetooth_on() -> DriverResult<()> {
debug!("Turning Bluetooth on (Windows)");
crate::common::hidden_cmd("powershell").args(["-Command", "Enable-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue"]).output().map_err(|e| {
let err_msg = format!("Failed to execute PowerShell: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Bluetooth turned on");
return Ok(());
}
#[cfg(target_os = "macos")]
pub fn bluetooth_on() -> DriverResult<()> {
debug!("Turning Bluetooth on (macOS)");
let output = crate::common::hidden_cmd("blueutil").args(["--power", "1"]).output();
if output.is_err() {
let err_msg = "blueutil not installed. Run: brew install blueutil".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
info!("Bluetooth turned on");
return Ok(());
}
#[cfg(target_os = "linux")]
pub fn bluetooth_off() -> DriverResult<()> {
debug!("Turning Bluetooth off (Linux)");
crate::common::hidden_cmd("bluetoothctl").args(["power", "off"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Bluetooth turned off");
return Ok(());
}
#[cfg(target_os = "windows")]
pub fn bluetooth_off() -> DriverResult<()> {
debug!("Turning Bluetooth off (Windows)");
crate::common::hidden_cmd("powershell").args(["-Command", "Disable-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue"]).output().map_err(|e| {
let err_msg = format!("Failed to execute PowerShell: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Bluetooth turned off");
return Ok(());
}
#[cfg(target_os = "macos")]
pub fn bluetooth_off() -> DriverResult<()> {
debug!("Turning Bluetooth off (macOS)");
let output = crate::common::hidden_cmd("blueutil").args(["--power", "0"]).output();
if output.is_err() {
let err_msg = "blueutil not installed. Run: brew install blueutil".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
info!("Bluetooth turned off");
return Ok(());
}
#[cfg(target_os = "linux")]
pub fn scan_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Starting Bluetooth scan (Linux)");
debug!("Starting discovery scan");
let _ = crate::common::hidden_cmd("bluetoothctl").args(["scan", "on"]).output();
debug!("Waiting 5 seconds for scan results");
std::thread::sleep(std::time::Duration::from_secs(5));
let output = crate::common::hidden_cmd("bluetoothctl").args(["devices"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines() {
if line.starts_with("Device") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let device = BluetoothDevice {
mac_address: parts[1].to_string(),
name: parts[2..].join(" "),
device_type: "Unknown".to_string(),
paired: false,
connected: false,
rssi: None,
battery_level: None,
};
debug!("Found device: {} ({})", device.name, device.mac_address);
devices.push(device);
}
}
}
debug!("Stopping discovery scan");
let _ = crate::common::hidden_cmd("bluetoothctl").args(["scan", "off"]).output();
info!("Scan complete, found {} devices", devices.len());
return Ok(devices);
}
#[cfg(target_os = "windows")]
pub fn scan_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Starting Bluetooth scan (Windows)");
let output = crate::common::hidden_cmd("powershell")
.args(["-Command", "Get-PnpDevice -Class Bluetooth | Select-Object FriendlyName, InstanceId"])
.output()
.map_err(|e| {
let err_msg = format!("Failed to execute PowerShell: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 1 && !line.contains("Bluetooth") {
let device = BluetoothDevice {
name: parts[0].to_string(),
mac_address: "Unknown".to_string(),
device_type: "Unknown".to_string(),
paired: false,
connected: false,
rssi: None,
battery_level: None,
};
debug!("Found device: {}", device.name);
devices.push(device);
}
}
info!("Scan complete, found {} devices", devices.len());
return Ok(devices);
}
#[cfg(target_os = "macos")]
pub fn scan_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Starting Bluetooth scan (macOS)");
let output = crate::common::hidden_cmd("system_profiler").args(["SPBluetoothDataType"]).output().map_err(|e| {
let err_msg = format!("Failed to execute system_profiler: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
let mut current_device: Option<BluetoothDevice> = None;
for line in stdout.lines() {
if line.contains("Fully Qualified") {
if let Some(device) = current_device.take() {
devices.push(device);
}
if let Some(name) = line.split(':').nth(1) {
debug!("Found device: {}", name.trim());
current_device = Some(BluetoothDevice {
name: name.trim().to_string(),
mac_address: "Unknown".to_string(),
device_type: "Unknown".to_string(),
paired: false,
connected: false,
rssi: None,
battery_level: None,
});
}
}
if let Some(ref mut device) = current_device {
if line.contains("Address:") {
if let Some(addr) = line.split(':').nth(1) {
device.mac_address = addr.trim().to_string();
debug!("Device MAC: {}", device.mac_address);
}
}
if line.contains("Connected: Yes") {
device.connected = true;
debug!("Device is connected");
}
if line.contains("Paired: Yes") {
device.paired = true;
debug!("Device is paired");
}
}
}
if let Some(device) = current_device {
devices.push(device);
}
info!("Scan complete, found {} devices", devices.len());
return Ok(devices);
}
#[cfg(target_os = "linux")]
pub fn pair_device(mac_address: &str) -> DriverResult<()> {
debug!("Pairing with device: {}", mac_address);
crate::common::hidden_cmd("bluetoothctl").args(["pair", mac_address]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Paired with device: {}", mac_address);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn pair_device(mac_address: &str) -> DriverResult<()> {
debug!("Pairing not supported on this platform: {}", mac_address);
let err_msg = "Pairing on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(target_os = "linux")]
pub fn unpair_device(mac_address: &str) -> DriverResult<()> {
debug!("Unpairing device: {}", mac_address);
crate::common::hidden_cmd("bluetoothctl").args(["remove", mac_address]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Unpaired device: {}", mac_address);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn unpair_device(mac_address: &str) -> DriverResult<()> {
debug!("Unpairing not supported on this platform: {}", mac_address);
let err_msg = "Unpairing on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(target_os = "linux")]
pub fn list_paired_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Listing paired devices (Linux)");
let output = crate::common::hidden_cmd("bluetoothctl").args(["paired-devices"]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines() {
if line.starts_with("Device") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let device = BluetoothDevice {
mac_address: parts[1].to_string(),
name: parts[2..].join(" "),
device_type: "Unknown".to_string(),
paired: true,
connected: false,
rssi: None,
battery_level: None,
};
debug!("Paired device: {} ({})", device.name, device.mac_address);
devices.push(device);
}
}
}
info!("Found {} paired devices", devices.len());
return Ok(devices);
}
#[cfg(target_os = "windows")]
pub fn list_paired_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Listing paired devices (Windows)");
let output = crate::common::hidden_cmd("powershell")
.args(["-Command", "Get-PnpDevice -Class Bluetooth | Where-Object {$_.FriendlyName -notlike '*Radio*'} | Select-Object FriendlyName, Status"])
.output()
.map_err(|e| {
let err_msg = format!("Failed to execute PowerShell: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines().skip(1) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 1 {
let connected = parts.contains(&"OK");
let device = BluetoothDevice {
name: parts[0].to_string(),
mac_address: "Unknown".to_string(),
device_type: "Unknown".to_string(),
paired: true,
connected,
rssi: None,
battery_level: None,
};
debug!("Paired device: {} (connected: {})", device.name, connected);
devices.push(device);
}
}
info!("Found {} paired devices", devices.len());
return Ok(devices);
}
#[cfg(target_os = "macos")]
pub fn list_paired_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Listing paired devices (macOS) - using scan_devices");
return scan_devices();
}
#[cfg(target_os = "linux")]
pub fn connect_device(mac_address: &str) -> DriverResult<()> {
debug!("Connecting to device: {}", mac_address);
crate::common::hidden_cmd("bluetoothctl").args(["connect", mac_address]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Connected to device: {}", mac_address);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn connect_device(mac_address: &str) -> DriverResult<()> {
debug!("Connecting not supported on this platform: {}", mac_address);
let err_msg = "Connecting on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(target_os = "linux")]
pub fn disconnect_device(mac_address: &str) -> DriverResult<()> {
debug!("Disconnecting device: {}", mac_address);
crate::common::hidden_cmd("bluetoothctl").args(["disconnect", mac_address]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Disconnected from device: {}", mac_address);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn disconnect_device(mac_address: &str) -> DriverResult<()> {
debug!("Disconnecting not supported on this platform: {}", mac_address);
let err_msg = "Disconnecting on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(target_os = "linux")]
pub fn set_discoverable(discoverable: bool, timeout: Option<u32>) -> DriverResult<()> {
debug!("Setting discoverable mode: enabled={}, timeout={:?}", discoverable, timeout);
if discoverable {
if let Some(t) = timeout {
debug!("Setting discoverable timeout to {}s", t);
crate::common::hidden_cmd("bluetoothctl").args(["discoverable-timeout", &t.to_string()]).output().map_err(|e| {
let err_msg = format!("Failed to set discoverable timeout: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
}
debug!("Turning discoverable on");
crate::common::hidden_cmd("bluetoothctl").args(["discoverable", "on"]).output().map_err(|e| {
let err_msg = format!("Failed to enable discoverable: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
} else {
debug!("Turning discoverable off");
crate::common::hidden_cmd("bluetoothctl").args(["discoverable", "off"]).output().map_err(|e| {
let err_msg = format!("Failed to disable discoverable: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
}
info!("Discoverable mode set: enabled={}", discoverable);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn set_discoverable(discoverable: bool, timeout: Option<u32>) -> DriverResult<()> {
let _ = (discoverable, timeout);
debug!("Setting discoverable not supported on this platform");
let err_msg = "Discoverable mode on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(target_os = "linux")]
pub fn set_device_name(name: &str) -> DriverResult<()> {
debug!("Setting device name to: {}", name);
crate::common::hidden_cmd("bluetoothctl").args(["name", name]).output().map_err(|e| {
let err_msg = format!("Failed to execute bluetoothctl: {}", e);
warn!("{}", err_msg);
return DriverError::execution(err_msg);
})?;
info!("Device name set to: {}", name);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn set_device_name(name: &str) -> DriverResult<()> {
let _ = name;
debug!("Setting device name not supported on this platform");
let err_msg = "Setting device name on this platform requires system preferences".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
pub fn get_connected_devices() -> DriverResult<Vec<BluetoothDevice>> {
debug!("Getting connected devices");
let all_devices = list_paired_devices()?;
let connected: Vec<BluetoothDevice> = all_devices.into_iter().filter(|d| d.connected).collect();
info!("Found {} connected devices", connected.len());
return Ok(connected);
}
#[cfg(target_os = "linux")]
pub fn send_file(mac_address: &str, file_path: &str) -> DriverResult<()> {
debug!("Sending file {} to {}", file_path, mac_address);
let output = crate::common::hidden_cmd("obexftp").args(["-b", mac_address, "-p", file_path]).output();
if output.is_err() {
let err_msg = "obexftp not installed. Please install obexftp package".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
info!("File sent successfully to {}", mac_address);
return Ok(());
}
#[cfg(not(target_os = "linux"))]
pub fn send_file(mac_address: &str, file_path: &str) -> DriverResult<()> {
let _ = (mac_address, file_path);
debug!("File transfer not supported on this platform");
let err_msg = "File transfer on this platform requires GUI interaction".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
pub fn bluetooth_on() -> DriverResult<()> {
debug!("Bluetooth not implemented on this platform");
let err_msg = "Bluetooth not implemented on this platform".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
pub fn bluetooth_off() -> DriverResult<()> {
debug!("Bluetooth not implemented on this platform");
let err_msg = "Bluetooth not implemented on this platform".to_string();
warn!("{}", err_msg);
return Err(DriverError::execution(err_msg));
}