use std::ops::RangeInclusive;
use std::sync::Arc;
use hidpp::{
channel::HidppChannel,
device::Device,
feature::{
CreatableFeature as _,
extended_report_rate::{
ConnectionType, ExtendedReportRate, ExtendedReportRateFeature, ExtendedReportRateList,
},
report_rate::{ReportRateFeature, ReportRateList},
},
};
use tracing::debug;
use crate::SharedChannel;
use crate::backend::HidBackend;
use crate::channel::route::{DIRECT_DEVICE_INDEX, DeviceRoute};
use openlogi_core::hid::{ReportRate, ReportRateInfo};
use super::{HidppOperation, WriteError, classify_hidpp_error, open_feature, with_route};
fn legacy_intervals_ms() -> RangeInclusive<u8> {
1..=8
}
fn legacy_rate(interval_ms: u8) -> ReportRate {
let interval_ms = u16::from(interval_ms.max(1));
ReportRate::new((1000 + interval_ms / 2) / interval_ms)
}
fn legacy_interval(rate: ReportRate) -> Option<u8> {
legacy_intervals_ms().find(|&interval_ms| legacy_rate(interval_ms) == rate)
}
fn legacy_rates(list: ReportRateList) -> Vec<ReportRate> {
legacy_intervals_ms()
.rev()
.filter(|interval_ms| list.bits() & (1 << (interval_ms - 1)) != 0)
.map(legacy_rate)
.collect()
}
const EXTENDED_RATES: [ExtendedReportRate; 7] = [
ExtendedReportRate::Hz125,
ExtendedReportRate::Hz250,
ExtendedReportRate::Hz500,
ExtendedReportRate::Hz1000,
ExtendedReportRate::Hz2000,
ExtendedReportRate::Hz4000,
ExtendedReportRate::Hz8000,
];
fn extended_rate(setting: ExtendedReportRate) -> ReportRate {
ReportRate::new(125 << u8::from(setting))
}
fn extended_setting(rate: ReportRate) -> Option<ExtendedReportRate> {
EXTENDED_RATES
.into_iter()
.find(|&setting| extended_rate(setting) == rate)
}
fn extended_rates(list: ExtendedReportRateList) -> Vec<ReportRate> {
EXTENDED_RATES
.into_iter()
.filter(|&setting| list.bits() & (1 << u8::from(setting)) != 0)
.map(extended_rate)
.collect()
}
fn connection_for(index: u8) -> ConnectionType {
if index == DIRECT_DEVICE_INDEX {
ConnectionType::Wired
} else {
ConnectionType::GamingWireless
}
}
enum ReportRateControl {
Extended {
feature: Arc<ExtendedReportRateFeature>,
connection: ConnectionType,
},
Legacy(Arc<ReportRateFeature>),
}
impl ReportRateControl {
async fn open(device: &mut Device, index: u8) -> Result<Self, WriteError> {
match open_feature::<ExtendedReportRateFeature>(device).await {
Ok(feature) => Ok(Self::Extended {
feature,
connection: connection_for(index),
}),
Err(WriteError::FeatureUnsupported { feature_hex })
if feature_hex == ExtendedReportRateFeature::ID =>
{
Ok(Self::Legacy(
open_feature::<ReportRateFeature>(device).await?,
))
}
Err(error) => Err(error),
}
}
fn feature_hex(&self) -> u16 {
match self {
Self::Extended { .. } => ExtendedReportRateFeature::ID,
Self::Legacy(_) => ReportRateFeature::ID,
}
}
async fn current(&self, operation: HidppOperation) -> Result<ReportRate, WriteError> {
let classify = |e| classify_hidpp_error(e, operation, self.feature_hex());
match self {
Self::Extended {
feature,
connection,
} => Ok(extended_rate(
feature
.get_report_rate(*connection)
.await
.map_err(classify)?,
)),
Self::Legacy(feature) => Ok(legacy_rate(
feature.get_report_rate().await.map_err(classify)?,
)),
}
}
async fn info(&self, operation: HidppOperation) -> Result<ReportRateInfo, WriteError> {
let classify = |e| classify_hidpp_error(e, operation, self.feature_hex());
let supported = match self {
Self::Extended { feature, .. } => extended_rates(
feature
.get_actual_report_rate_list()
.await
.map_err(classify)?,
),
Self::Legacy(feature) => {
legacy_rates(feature.get_report_rate_list().await.map_err(classify)?)
}
};
if supported.is_empty() {
return Err(WriteError::UnsupportedResponse {
operation,
feature_hex: self.feature_hex(),
});
}
let current = self.current(operation).await?;
Ok(ReportRateInfo { current, supported })
}
async fn write(&self, rate: ReportRate) -> Result<(), WriteError> {
let unsupported = WriteError::ReportRateUnsupported { requested: rate };
let classify =
|e| classify_hidpp_error(e, HidppOperation::WriteReportRate, self.feature_hex());
match self {
Self::Extended { feature, .. } => feature
.set_report_rate(extended_setting(rate).ok_or(unsupported)?)
.await
.map_err(classify),
Self::Legacy(feature) => feature
.set_report_rate(legacy_interval(rate).ok_or(unsupported)?)
.await
.map_err(classify),
}
}
}
async fn open_control(
channel: &Arc<HidppChannel>,
index: u8,
) -> Result<ReportRateControl, WriteError> {
let mut device = Device::new(Arc::clone(channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
ReportRateControl::open(&mut device, index).await
}
pub async fn get_report_rate_info(
backend: &dyn HidBackend,
route: &DeviceRoute,
) -> Result<ReportRateInfo, WriteError> {
let index = route.device_index();
with_route(backend, route, move |channel| async move {
get_report_rate_info_on_channel(&channel, index).await
})
.await
}
pub async fn get_report_rate_info_on(shared: &SharedChannel) -> Result<ReportRateInfo, WriteError> {
get_report_rate_info_on_channel(shared.channel(), shared.device_index()).await
}
async fn get_report_rate_info_on_channel(
channel: &Arc<HidppChannel>,
index: u8,
) -> Result<ReportRateInfo, WriteError> {
open_control(channel, index)
.await?
.info(HidppOperation::ReadReportRate)
.await
}
pub async fn set_report_rate(
backend: &dyn HidBackend,
route: &DeviceRoute,
rate: ReportRate,
) -> Result<ReportRateInfo, WriteError> {
let index = route.device_index();
with_route(backend, route, move |channel| async move {
set_report_rate_on_channel(&channel, index, rate).await
})
.await
}
pub async fn set_report_rate_on(
shared: &SharedChannel,
rate: ReportRate,
) -> Result<ReportRateInfo, WriteError> {
set_report_rate_on_channel(shared.channel(), shared.device_index(), rate).await
}
async fn set_report_rate_on_channel(
channel: &Arc<HidppChannel>,
index: u8,
rate: ReportRate,
) -> Result<ReportRateInfo, WriteError> {
let control = open_control(channel, index).await?;
let before = control.info(HidppOperation::WriteReportRate).await?;
if !before.supports(rate) {
return Err(WriteError::ReportRateUnsupported { requested: rate });
}
if before.current == rate {
debug!(index, %rate, "report rate already set");
return Ok(before);
}
control.write(rate).await?;
match control.current(HidppOperation::WriteReportRate).await {
Ok(actual) if actual == rate => {
debug!(index, %rate, "report rate written");
Ok(ReportRateInfo {
current: actual,
..before
})
}
Ok(actual) => Err(WriteError::ReportRateNotApplied {
requested: rate,
actual,
}),
Err(error) => {
debug!(index, %rate, %error, "report rate written (read-back skipped)");
Ok(ReportRateInfo {
current: rate,
..before
})
}
}
}
#[cfg(test)]
mod tests;