use std::num::NonZeroU8;
use std::sync::Arc;
use hidpp::{
channel::HidppChannel,
device::Device,
feature::{
CreatableFeature,
smartshift::{SmartShiftFeature, WheelMode},
smartshift_enhanced::{SmartShiftEnhancedFeature, SmartShiftEnhancedStatusChange},
},
};
use tracing::debug;
use crate::route::DeviceRoute;
use crate::smartshift::{SmartShiftMode, SmartShiftStatus};
use super::{HidppOperation, WriteError, classify_hidpp_error, open_feature, with_route};
pub(super) fn is_missing_enhanced(err: &WriteError) -> bool {
matches!(
err,
WriteError::FeatureUnsupported { feature_hex } if *feature_hex == 0x2111
)
}
pub(super) fn wheel_mode_to_smartshift(wheel: WheelMode) -> SmartShiftMode {
if matches!(wheel, WheelMode::Freespin) {
SmartShiftMode::Free
} else {
SmartShiftMode::Ratchet
}
}
pub(super) fn smartshift_to_wheel(mode: SmartShiftMode) -> WheelMode {
match mode {
SmartShiftMode::Free => WheelMode::Freespin,
SmartShiftMode::Ratchet => WheelMode::Ratchet,
}
}
enum SmartShift {
Enhanced(Arc<SmartShiftEnhancedFeature>),
Legacy(Arc<SmartShiftFeature>),
}
impl SmartShift {
async fn open(device: &mut Device) -> Result<Self, WriteError> {
match open_feature::<SmartShiftEnhancedFeature>(device).await {
Ok(feature) => Ok(Self::Enhanced(feature)),
Err(err) if is_missing_enhanced(&err) => {
let feature = open_feature::<SmartShiftFeature>(device).await?;
Ok(Self::Legacy(feature))
}
Err(err) => Err(err),
}
}
async fn status(&self) -> Result<SmartShiftStatus, WriteError> {
match self {
Self::Enhanced(feature) => {
let status = feature.get_ratchet_control_mode().await.map_err(|e| {
classify_hidpp_error(
e,
HidppOperation::ReadSmartShift,
SmartShiftEnhancedFeature::ID,
)
})?;
Ok(SmartShiftStatus {
mode: wheel_mode_to_smartshift(status.wheel_mode),
auto_disengage: status.auto_disengage,
tunable_torque: status.current_tunable_torque,
})
}
Self::Legacy(feature) => {
let rcm = feature.get_ratchet_control_mode().await.map_err(|e| {
classify_hidpp_error(e, HidppOperation::ReadSmartShift, SmartShiftFeature::ID)
})?;
Ok(SmartShiftStatus {
mode: wheel_mode_to_smartshift(rcm.wheel_mode),
auto_disengage: rcm.auto_disengage,
tunable_torque: 0,
})
}
}
}
async fn set_status(&self, status: SmartShiftStatus) -> Result<(), WriteError> {
let SmartShiftStatus {
mode,
auto_disengage,
tunable_torque,
} = status;
match self {
Self::Enhanced(feature) => feature
.set_ratchet_control_mode(SmartShiftEnhancedStatusChange {
wheel_mode: Some(smartshift_to_wheel(mode)),
auto_disengage: NonZeroU8::new(auto_disengage),
tunable_torque: NonZeroU8::new(tunable_torque),
})
.await
.map(|_| ())
.map_err(|e| {
classify_hidpp_error(
e,
HidppOperation::WriteSmartShift,
SmartShiftEnhancedFeature::ID,
)
}),
Self::Legacy(feature) => feature
.set_ratchet_control_mode(
Some(smartshift_to_wheel(mode)),
Some(auto_disengage),
None,
)
.await
.map_err(|e| {
classify_hidpp_error(e, HidppOperation::WriteSmartShift, SmartShiftFeature::ID)
}),
}
}
async fn set_sensitivity(&self, value: NonZeroU8) -> Result<(), WriteError> {
let current = self.status().await?;
match self {
Self::Enhanced(feature) => feature
.set_ratchet_control_mode(SmartShiftEnhancedStatusChange {
wheel_mode: Some(smartshift_to_wheel(current.mode)),
auto_disengage: Some(value),
tunable_torque: NonZeroU8::new(current.tunable_torque),
})
.await
.map(|_| ())
.map_err(|e| {
classify_hidpp_error(
e,
HidppOperation::WriteSmartShift,
SmartShiftEnhancedFeature::ID,
)
}),
Self::Legacy(_) => {
self.set_status(SmartShiftStatus {
auto_disengage: value.get(),
..current
})
.await
}
}
}
}
pub async fn get_smartshift_status(route: &DeviceRoute) -> Result<SmartShiftStatus, WriteError> {
let index = route.device_index();
with_route(route, move |channel| async move {
let mut device = Device::new(Arc::clone(&channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
let smartshift = SmartShift::open(&mut device).await?;
smartshift.status().await
})
.await
}
pub async fn set_smartshift_sensitivity(
route: &DeviceRoute,
value: NonZeroU8,
) -> Result<SmartShiftStatus, WriteError> {
let index = route.device_index();
with_route(route, move |channel| async move {
let mut device = Device::new(Arc::clone(&channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
let smartshift = SmartShift::open(&mut device).await?;
smartshift.set_sensitivity(value).await?;
smartshift.status().await
})
.await
}
pub async fn toggle_smartshift(route: &DeviceRoute) -> Result<SmartShiftMode, WriteError> {
let index = route.device_index();
with_route(route, move |channel| async move {
toggle_smartshift_on_channel(&channel, index).await
})
.await
}
pub(super) async fn toggle_smartshift_on_channel(
channel: &Arc<HidppChannel>,
index: u8,
) -> Result<SmartShiftMode, WriteError> {
let mut device = Device::new(Arc::clone(channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
let smartshift = SmartShift::open(&mut device).await?;
let status = smartshift.status().await?;
let next = status.mode.flipped();
smartshift
.set_status(SmartShiftStatus {
mode: next,
..status
})
.await?;
debug!(index, ?next, "wrote SmartShift mode");
Ok(next)
}
pub async fn set_smartshift(
route: &DeviceRoute,
mode: SmartShiftMode,
auto_disengage: u8,
tunable_torque: u8,
) -> Result<(), WriteError> {
let index = route.device_index();
with_route(route, move |channel| async move {
set_smartshift_on_channel(&channel, index, mode, auto_disengage, tunable_torque).await
})
.await
}
pub(super) async fn set_smartshift_on_channel(
channel: &Arc<HidppChannel>,
index: u8,
mode: SmartShiftMode,
auto_disengage: u8,
tunable_torque: u8,
) -> Result<(), WriteError> {
let mut device = Device::new(Arc::clone(channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
let smartshift = SmartShift::open(&mut device).await?;
smartshift
.set_status(SmartShiftStatus {
mode,
auto_disengage,
tunable_torque,
})
.await?;
debug!(
index,
?mode,
auto_disengage,
tunable_torque,
"wrote SmartShift config"
);
Ok(())
}