pub(crate) mod controls;
pub(crate) mod monitoring;
pub mod types;
use std::{collections::HashMap, sync::Arc};
use controls::DeviceWifiControls;
use futures::{Stream, StreamExt};
use tracing::warn;
use types::{BitrateKbps, BootTimeMs, WifiProperties, WirelessCapabilities};
pub(crate) use types::{DeviceWifiParams, LiveDeviceWifiParams};
use wayle_core::{Property, unwrap_dbus, unwrap_dbus_or};
use wayle_traits::{ModelMonitoring, Reactive};
use zbus::{Connection, zvariant::OwnedObjectPath};
use super::{Device, LiveDeviceParams};
use crate::{
error::Error,
proxy::devices::{DeviceProxy, wireless::DeviceWirelessProxy},
types::{device::NMDeviceType, wifi::NM80211Mode},
};
#[derive(Debug, Clone)]
pub struct DeviceWifi {
pub core: Device,
pub perm_hw_address: Property<String>,
pub mode: Property<NM80211Mode>,
pub bitrate: Property<BitrateKbps>,
pub access_points: Property<Vec<OwnedObjectPath>>,
pub active_access_point: Property<OwnedObjectPath>,
pub wireless_capabilities: Property<WirelessCapabilities>,
pub last_scan: Property<BootTimeMs>,
}
impl Reactive for DeviceWifi {
type Context<'a> = DeviceWifiParams<'a>;
type LiveContext<'a> = LiveDeviceWifiParams<'a>;
type Error = Error;
async fn get(params: Self::Context<'_>) -> Result<Self, Self::Error> {
Self::from_path(params.connection, params.device_path).await
}
async fn get_live(params: Self::LiveContext<'_>) -> Result<Arc<Self>, Self::Error> {
Self::verify_is_wifi_device(params.connection, ¶ms.device_path).await?;
let base_arc = Device::get_live(LiveDeviceParams {
connection: params.connection,
object_path: params.device_path.clone(),
cancellation_token: params.cancellation_token,
})
.await?;
let base = Device::clone(&base_arc);
let wifi_props =
Self::fetch_wifi_properties(params.connection, ¶ms.device_path).await?;
let device = Arc::new(Self::from_props(base, wifi_props));
device.clone().start_monitoring().await?;
Ok(device)
}
}
impl DeviceWifi {
pub async fn request_scan(&self) -> Result<(), Error> {
DeviceWifiControls::request_scan(
&self.core.connection,
&self.core.object_path,
HashMap::new(),
)
.await
}
pub async fn get_all_access_points(&self) -> Result<Vec<OwnedObjectPath>, Error> {
DeviceWifiControls::get_all_access_points(&self.core.connection, &self.core.object_path)
.await
}
async fn verify_is_wifi_device(
connection: &Connection,
object_path: &OwnedObjectPath,
) -> Result<(), Error> {
let device_proxy = DeviceProxy::new(connection, object_path)
.await
.map_err(Error::DbusError)?;
let device_type = device_proxy.device_type().await.map_err(Error::DbusError)?;
if device_type != NMDeviceType::Wifi as u32 {
return Err(Error::WrongObjectType {
object_path: object_path.clone(),
expected: String::from("WiFi device"),
actual: format!("device type {device_type}"),
});
}
Ok(())
}
async fn fetch_wifi_properties(
connection: &Connection,
device_path: &OwnedObjectPath,
) -> Result<WifiProperties, Error> {
let wifi_proxy = DeviceWirelessProxy::new(connection, device_path)
.await
.map_err(Error::DbusError)?;
let (
perm_hw_address,
mode,
bitrate,
access_points,
active_access_point,
wireless_capabilities,
last_scan,
) = tokio::join!(
wifi_proxy.perm_hw_address(),
wifi_proxy.mode(),
wifi_proxy.bitrate(),
wifi_proxy.access_points(),
wifi_proxy.active_access_point(),
wifi_proxy.wireless_capabilities(),
wifi_proxy.last_scan(),
);
Ok(WifiProperties {
perm_hw_address: unwrap_dbus!(perm_hw_address, device_path),
mode: unwrap_dbus!(mode, device_path),
bitrate: unwrap_dbus!(bitrate, device_path),
access_points: unwrap_dbus!(access_points, device_path),
active_access_point: unwrap_dbus_or!(
active_access_point,
device_path,
OwnedObjectPath::default()
),
wireless_capabilities: unwrap_dbus!(wireless_capabilities, device_path),
last_scan: unwrap_dbus_or!(last_scan, device_path, -1),
})
}
fn from_props(core: Device, props: WifiProperties) -> Self {
Self {
core,
perm_hw_address: Property::new(props.perm_hw_address),
mode: Property::new(NM80211Mode::from_u32(props.mode)),
bitrate: Property::new(props.bitrate),
access_points: Property::new(props.access_points),
active_access_point: Property::new(props.active_access_point),
wireless_capabilities: Property::new(props.wireless_capabilities),
last_scan: Property::new(props.last_scan),
}
}
async fn from_path(
connection: &Connection,
object_path: OwnedObjectPath,
) -> Result<Self, Error> {
let device_proxy = DeviceProxy::new(connection, &object_path).await?;
let device_type = device_proxy.device_type().await?;
if device_type != NMDeviceType::Wifi as u32 {
warn!(
"Device at {object_path} is not a wifi device, got type: {} ({:?})",
device_type,
NMDeviceType::from_u32(device_type)
);
return Err(Error::WrongObjectType {
object_path: object_path.clone(),
expected: String::from("WiFi device"),
actual: format!("{:?}", NMDeviceType::from_u32(device_type)),
});
}
let wifi_proxy = DeviceWirelessProxy::new(connection, &object_path).await?;
let base = match Device::from_path(connection, object_path.clone(), None).await {
Ok(base) => base,
Err(e) => {
warn!(object_path = %object_path, "cannot create base device");
return Err(Error::ObjectCreationFailed {
object_type: String::from("Device"),
object_path: object_path.clone(),
source: e.into(),
});
}
};
let (
perm_hw_address,
mode,
bitrate,
access_points,
active_access_point,
wireless_capabilities,
last_scan,
) = tokio::join!(
wifi_proxy.perm_hw_address(),
wifi_proxy.mode(),
wifi_proxy.bitrate(),
wifi_proxy.access_points(),
wifi_proxy.active_access_point(),
wifi_proxy.wireless_capabilities(),
wifi_proxy.last_scan(),
);
let device = Self {
core: base,
perm_hw_address: Property::new(unwrap_dbus!(perm_hw_address)),
mode: Property::new(NM80211Mode::from_u32(unwrap_dbus!(mode))),
bitrate: Property::new(unwrap_dbus!(bitrate)),
access_points: Property::new(unwrap_dbus!(access_points)),
active_access_point: Property::new(unwrap_dbus_or!(
active_access_point,
OwnedObjectPath::default()
)),
wireless_capabilities: Property::new(unwrap_dbus!(wireless_capabilities)),
last_scan: Property::new(unwrap_dbus_or!(last_scan, -1)),
};
Ok(device)
}
pub async fn access_point_added_signal(
&self,
) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
let proxy = DeviceWirelessProxy::new(&self.core.connection, &self.core.object_path).await?;
let stream = proxy.receive_access_point_added().await?;
Ok(stream
.filter_map(|signal| async move { signal.args().ok().map(|args| args.access_point) }))
}
pub async fn access_point_removed_signal(
&self,
) -> Result<impl Stream<Item = OwnedObjectPath>, Error> {
let proxy = DeviceWirelessProxy::new(&self.core.connection, &self.core.object_path).await?;
let stream = proxy.receive_access_point_removed().await?;
Ok(stream
.filter_map(|signal| async move { signal.args().ok().map(|args| args.access_point) }))
}
}