use std::sync::Arc;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint, hosts_info::HostIndex},
protocol::v20::Hidpp20Error,
};
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct FnInversionCapabilities: u8 {
const MANUAL_FN_LOCK = 1 << 0;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum FnInversionState {
Off = 0,
On = 1,
}
impl From<bool> for FnInversionState {
fn from(value: bool) -> Self {
if value { Self::On } else { Self::Off }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct FnInversionInfo {
pub host_index: HostIndex,
pub state: FnInversionState,
pub default_state: FnInversionState,
pub capabilities: FnInversionCapabilities,
}
#[derive(Clone)]
pub struct FnInversionMultiHostFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for FnInversionMultiHostFeature {
const ID: u16 = 0x40a3;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for FnInversionMultiHostFeature {}
impl FnInversionMultiHostFeature {
pub async fn get_global_fn_inversion(
&self,
host: HostIndex,
) -> Result<FnInversionInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(0, [u8::from(host), 0, 0])
.await?
.extend_payload();
FnInversionInfo::from_payload(payload)
}
pub async fn set_global_fn_inversion(
&self,
host: HostIndex,
state: FnInversionState,
) -> Result<FnInversionInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(1, set_multi_host_fn_inversion_args(host, state))
.await?
.extend_payload();
FnInversionInfo::from_payload(payload)
}
}
fn set_multi_host_fn_inversion_args(host: HostIndex, state: FnInversionState) -> [u8; 3] {
[u8::from(host), u8::from(state), 0]
}
impl FnInversionInfo {
fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
Ok(Self {
host_index: HostIndex::from(payload[0]),
state: FnInversionState::try_from(payload[1])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
default_state: FnInversionState::try_from(payload[2])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
capabilities: FnInversionCapabilities::from_bits_retain(payload[3]),
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct GlobalFnInversion {
pub state: FnInversionState,
pub default_state: FnInversionState,
}
impl GlobalFnInversion {
fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
Ok(Self {
state: FnInversionState::try_from(payload[0])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
default_state: FnInversionState::try_from(payload[1])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
}
#[derive(Clone)]
pub struct FnInversionWithDefaultStateFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for FnInversionWithDefaultStateFeature {
const ID: u16 = 0x40a2;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for FnInversionWithDefaultStateFeature {}
impl FnInversionWithDefaultStateFeature {
pub async fn get_global_fn_inversion(&self) -> Result<GlobalFnInversion, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
GlobalFnInversion::from_payload(payload)
}
pub async fn set_global_fn_inversion(
&self,
state: FnInversionState,
) -> Result<GlobalFnInversion, Hidpp20Error> {
let payload = self
.endpoint
.call(1, [u8::from(state), 0, 0])
.await?
.extend_payload();
GlobalFnInversion::from_payload(payload)
}
}
#[cfg(test)]
mod tests {
use super::{
FnInversionInfo, FnInversionState, GlobalFnInversion, set_multi_host_fn_inversion_args,
};
use crate::feature::hosts_info::HostIndex;
#[test]
fn parses_fn_inversion_info() {
let mut payload = [0; 16];
payload[0] = 1;
payload[1] = 1;
payload[2] = 0;
payload[3] = 1;
let info = FnInversionInfo::from_payload(payload).unwrap();
assert_eq!(info.host_index, HostIndex::Slot(1));
assert_eq!(info.state, FnInversionState::On);
assert_eq!(info.default_state, FnInversionState::Off);
}
#[test]
fn parses_global_fn_inversion() {
let mut payload = [0; 16];
payload[0] = 1;
payload[1] = 0;
let global = GlobalFnInversion::from_payload(payload).unwrap();
assert_eq!(global.state, FnInversionState::On);
assert_eq!(global.default_state, FnInversionState::Off);
}
#[test]
fn encodes_multi_host_set_args_as_host_then_state() {
assert_eq!(
set_multi_host_fn_inversion_args(HostIndex::Slot(2), FnInversionState::On),
[2, 1, 0]
);
}
}