diff options
| -rw-r--r-- | Cargo.lock | 9 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | formatable-float/Cargo.toml | 9 | ||||
| -rw-r--r-- | formatable-float/src/lib.rs | 154 | ||||
| -rw-r--r-- | pulse/Cargo.toml | 1 | ||||
| -rw-r--r-- | pulse/src/config.rs | 175 | ||||
| -rw-r--r-- | pulse/src/runnable/mod.rs | 1 |
7 files changed, 188 insertions, 163 deletions
@@ -144,6 +144,14 @@ dependencies = [ ] [[package]] +name = "formatable-float" +version = "0.1.0" +dependencies = [ + "erased-serde", + "serde", +] + +[[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -539,6 +547,7 @@ name = "swaystatus-pulse" version = "0.1.0" dependencies = [ "erased-serde", + "formatable-float", "serde", "swaystatus-plugin", ] @@ -3,7 +3,9 @@ members = [ "swaystatus", "swaystatus-plugin", + "formatable-float", "alsa", "clock", "pulse" ] +resolver = "2" diff --git a/formatable-float/Cargo.toml b/formatable-float/Cargo.toml new file mode 100644 index 0000000..323bc07 --- /dev/null +++ b/formatable-float/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "formatable-float" +version = "0.1.0" +authors = ["Andreas Grois <andi@grois.info>"] +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +erased-serde = "0.3" diff --git a/formatable-float/src/lib.rs b/formatable-float/src/lib.rs new file mode 100644 index 0000000..7b1db21 --- /dev/null +++ b/formatable-float/src/lib.rs @@ -0,0 +1,154 @@ +use serde::{Serialize,Deserialize,Serializer,Deserializer}; +use serde::de::Error as DeError; +use serde::de::Unexpected as DeUnexpect; +use std::collections::BTreeMap; +use std::ops::{Add, Sub}; +use std::str::FromStr; +use std::num::{ParseIntError,IntErrorKind}; + +#[derive(Serialize, Deserialize)] +#[serde(tag = "Format")] +pub enum FormatableFloatValue<KeyTypeMetadata : KeyBackingTypeMetadata> { + Off, + Numeric { + #[serde(rename = "Label")] + label : String, + #[serde(rename = "DecimalDigits")] + digits : u8 + }, + Binned { + #[serde(rename = "Label")] + label: String, + #[serde(rename = "PercentToSymbolMap")] + bin_symbol_map : BTreeMap<FormatableFloatKey<KeyTypeMetadata>,String> + } +} + +#[derive(Debug)] +pub enum FormattingError { + EmptyMap { + numeric_fallback : String + } +} +impl std::fmt::Display for FormattingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FormattingError::EmptyMap{numeric_fallback} => { write!(f, "Formatting failed. Empty PercentToSymbolMap. Numeric value: {}", numeric_fallback) } + } + } +} +impl std::error::Error for FormattingError {} + +impl<KeyTypeMetadata : KeyBackingTypeMetadata> FormatableFloatValue<KeyTypeMetadata> { + pub fn format_float(&self, float : f32) -> Result<Option<String>, FormattingError> { + match self { + FormatableFloatValue::Numeric{ label, digits } => { Ok(Some(Self::format_float_numeric(float, label, *digits))) } + FormatableFloatValue::Binned{ label, bin_symbol_map } => { Some(Self::format_float_binned(float, label, bin_symbol_map)).transpose()} + FormatableFloatValue::Off => {Ok(None)} + } + } + pub fn format_float_binned(float : f32, label : &str, bin_symbol_map : &BTreeMap<FormatableFloatKey<KeyTypeMetadata>, String>) -> Result<String,FormattingError> { + let value_to_match = FormatableFloatKey::<KeyTypeMetadata>::match_float(float); + //first try to find the next lower value. + if let Some((_,msg)) = bin_symbol_map.range(..=value_to_match).next_back() { + Ok(format!("{}{}",label,msg)) + } + else if let Some((_,msg)) = bin_symbol_map.iter().next() { + Ok(format!("{}{}",label,msg)) + } + else { + Err(FormattingError::EmptyMap{numeric_fallback : Self::format_float_numeric(float, label, 0) }) + } + } + pub fn format_float_numeric(float : f32, label : &str, digits : u8) -> String { + let percentage = 100.0*float; + format!("{}{:.*}%", label, digits as usize, percentage) + } +} + +///Helper trait for conversion from float to integer backing type for binning keys. +///Needed because Rust seems not to offer a trait that indicates "can be rounded from float" +///in the standard library. There are thir-party crates that do this, but using a full crate +///for a few lines of code sounds a bit excessive... +pub trait BackingTypeFromFloat { + fn round_from_float(float : f32) -> Self; +} + +///Metadata description for Keys. Basically a workaround for Rust's lack of +///constant generics. Having Ord as supertrait is because of the BTreeMap's trait +///bounds. +pub trait KeyBackingTypeMetadata : Ord { + type BackingType + : Ord + + Add<Output = Self::BackingType> + + Sub<Output = Self::BackingType> + + Into<f32> + + BackingTypeFromFloat + + ToString //TOML needs map keys to be strings... + + FromStr<Err = ParseIntError> + + std::fmt::Display; + const MIN : Self::BackingType; + const MAX : Self::BackingType; + const FLOAT_MIN : f32; + const FLOAT_MAX : f32; +} + +#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)] +pub struct FormatableFloatKey<BackingType : KeyBackingTypeMetadata>(pub BackingType::BackingType); + +impl<BackingType : KeyBackingTypeMetadata> FormatableFloatKey<BackingType> { + fn match_float(float : f32) -> Self { + let x = (float - BackingType::FLOAT_MIN) / (BackingType::FLOAT_MAX - BackingType::FLOAT_MIN); + let cx = x.clamp(0.0,1.0); + let interval = BackingType::MAX.into() - BackingType::MIN.into(); + let offset = interval * cx; + let result = BackingType::BackingType::round_from_float(offset) + BackingType::MIN; + Self(result) + } + +} +/// Custom serializer, as TOML only supports string map keys. +impl<Metadata : KeyBackingTypeMetadata> Serialize for FormatableFloatKey<Metadata> { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where S : Serializer + { + let string = self.0.to_string(); + serializer.serialize_str(&string) + } +} +impl<'de, Metadata> Deserialize<'de> for FormatableFloatKey<Metadata> + where Metadata : KeyBackingTypeMetadata, +{ + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where D: Deserializer<'de> + { + let a = String::deserialize(deserializer)?; + match a.parse() { + Ok(x) => { + if x >= Metadata::MIN && x <= Metadata::MAX { + Ok(Self(x)) + } + else { + Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) + } + } + Err(e) => { + match e.kind() { + IntErrorKind::Empty => { Err(DeError::missing_field("Bin Map Key")) } + IntErrorKind::InvalidDigit => { Err(DeError::invalid_type(DeUnexpect::Str(&a), &"an integer value")) } + IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) } + IntErrorKind::Zero => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("a nonzero integer between {} and {}", Metadata::MIN, Metadata::MAX)))} + _ => { Err(DeError::custom("Value could not be parsed")) } + } + } + } + } +} +macro_rules! impl_key_backing_type_from_float_for { + ($( $t:ty ), *) => { + $( impl BackingTypeFromFloat for $t { + fn round_from_float(float : f32) -> $t { float.round() as $t } + } )* + } +} +impl_key_backing_type_from_float_for!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize, i128, u128); diff --git a/pulse/Cargo.toml b/pulse/Cargo.toml index c6cff4b..c3b4c12 100644 --- a/pulse/Cargo.toml +++ b/pulse/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" swaystatus-plugin = { path = '../swaystatus-plugin', version = '*'} serde = { version = "1.0", features = ["derive"] } erased-serde = "0.3" +formatable-float = { path = '../formatable-float', version = '*'} [lib] crate-type = ["cdylib"] diff --git a/pulse/src/config.rs b/pulse/src/config.rs index 0be7367..fb123e0 100644 --- a/pulse/src/config.rs +++ b/pulse/src/config.rs @@ -1,12 +1,8 @@ -use serde::{Serialize,Deserialize,Serializer,Deserializer}; -use serde::de::Error as DeError; -use serde::de::Unexpected as DeUnexpect; use std::collections::BTreeMap; -use swaystatus_plugin::*; -use std::ops::{Add, Sub}; -use std::str::FromStr; -use std::num::{ParseIntError,IntErrorKind}; +use serde::{Serialize,Deserialize}; +use swaystatus_plugin::*; +use formatable_float::{FormatableFloatValue, FormattingError, KeyBackingTypeMetadata, FormatableFloatKey}; #[derive(Serialize, Deserialize)] #[serde(tag = "Sink")] @@ -19,24 +15,6 @@ pub(crate) enum Sink { } #[derive(Serialize, Deserialize)] -#[serde(tag = "Format")] -enum FormatableVolume<KeyTypeMetadata : VolumeKeyBackingTypeMetadata> { - Off, - Numeric { - #[serde(rename = "Label")] - label : String, - #[serde(rename = "DecimalDigits")] - digits : u8 - }, - Binned { - #[serde(rename = "Label")] - label: String, - #[serde(rename = "PercentToSymbolMap")] - bin_symbol_map : BTreeMap<VolumeKey<KeyTypeMetadata>,String> - } -} - -#[derive(Serialize, Deserialize)] enum FieldSorting { MuteVolumeBalance, MuteBalanceVolume, @@ -65,8 +43,8 @@ enum FormatableMute { pub struct PulseVolumeConfig { sorting : FieldSorting, pub(crate) sink : Sink, - volume : FormatableVolume<VolumeKeyVolume>, - balance : FormatableVolume<VolumeKeyBalance>, + volume : FormatableFloatValue<VolumeKeyVolume>, + balance : FormatableFloatValue<VolumeKeyBalance>, mute : FormatableMute, } @@ -109,14 +87,14 @@ impl Default for PulseVolumeConfig { fn default() -> Self { PulseVolumeConfig { sink : Sink::Default, - volume : FormatableVolume::Numeric { label : String::from(""), digits : 0 }, - balance : FormatableVolume::Binned { + volume : FormatableFloatValue::Numeric { label : String::from(""), digits : 0 }, + balance : FormatableFloatValue::Binned { label : String::from(" "), bin_symbol_map : { let mut a = BTreeMap::new(); - a.insert(VolumeKey(-100),String::from("|..")); - a.insert(VolumeKey(-10), String::from(".|.")); - a.insert(VolumeKey(10), String::from("..|")); + a.insert(FormatableFloatKey(-100),String::from("|..")); + a.insert(FormatableFloatKey(-10), String::from(".|.")); + a.insert(FormatableFloatKey(10), String::from("..|")); a } }, @@ -133,48 +111,6 @@ impl SwayStatusModuleInstance for PulseVolumeConfig { } } -#[derive(Debug)] -pub(crate) enum FormattingError { - EmptyMap { - numeric_fallback : String - } -} -impl std::fmt::Display for FormattingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FormattingError::EmptyMap{numeric_fallback} => { write!(f, "Formatting failed. Empty PercentToSymbolMap. Numeric value: {}", numeric_fallback) } - } - } -} -impl std::error::Error for FormattingError {} - -impl<KeyTypeMetadata : VolumeKeyBackingTypeMetadata> FormatableVolume<KeyTypeMetadata> { - fn format_float(&self, float : f32) -> Result<Option<String>, FormattingError> { - match self { - FormatableVolume::Numeric{ label, digits } => { Ok(Some(Self::format_float_numeric(float, label, *digits))) } - FormatableVolume::Binned{ label, bin_symbol_map } => { Some(Self::format_float_binned(float, label, bin_symbol_map)).transpose()} - FormatableVolume::Off => {Ok(None)} - } - } - fn format_float_binned(float : f32, label : &str, bin_symbol_map : &BTreeMap<VolumeKey<KeyTypeMetadata>, String>) -> Result<String,FormattingError> { - let value_to_match = VolumeKey::<KeyTypeMetadata>::match_float(float); - //first try to find the next lower value. - if let Some((_,msg)) = bin_symbol_map.range(..=value_to_match).next_back() { - Ok(format!("{}{}",label,msg)) - } - else if let Some((_,msg)) = bin_symbol_map.iter().next() { - Ok(format!("{}{}",label,msg)) - } - else { - Err(FormattingError::EmptyMap{numeric_fallback : Self::format_float_numeric(float, label, 0) }) - } - } - fn format_float_numeric(float : f32, label : &str, digits : u8) -> String { - let percentage = 100.0*float; - format!("{}{:.*}%", label, digits as usize, percentage) - } -} - impl FormatableMute { fn format_mute(&self, mute : bool) -> Option<String> { match self { @@ -183,45 +119,12 @@ impl FormatableMute { } } } -///Helper trait for conversion from float to integer backing type for volume binning keys. -///Needed because Rust seems not to offer a trait that indicates "can be rounded from float" -///in the standard library. There are thir-party crates that do this, but using a full crate -///for a few lines of code sounds a bit excessive... -trait VolumeKeyBackingTypeFromFloat { - fn round_from_float(float : f32) -> Self; -} -macro_rules! impl_volume_key_backing_type_from_float_for { - ($( $t:ty ), *) => { - $( impl VolumeKeyBackingTypeFromFloat for $t { - fn round_from_float(float : f32) -> $t { float.round() as $t } - } )* - } -} - -///Metadata description for VolumeKeys. Basically a workaround for Rust's lack of -///constant generics. Having Ord as supertrait is because of the BTreeMap's trait -///bounds. -trait VolumeKeyBackingTypeMetadata : Ord { - type BackingType - : Ord - + Add<Output = Self::BackingType> - + Sub<Output = Self::BackingType> - + Into<f32> - + VolumeKeyBackingTypeFromFloat - + ToString //TOML needs map keys to be strings... - + FromStr<Err = ParseIntError> - + std::fmt::Display; - const MIN : Self::BackingType; - const MAX : Self::BackingType; - const FLOAT_MIN : f32; - const FLOAT_MAX : f32; -} #[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] struct VolumeKeyVolume; #[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] struct VolumeKeyBalance; -impl VolumeKeyBackingTypeMetadata for VolumeKeyVolume{ +impl KeyBackingTypeMetadata for VolumeKeyVolume{ type BackingType = u8; const MIN : Self::BackingType = 0; const MAX : Self::BackingType = 100; @@ -229,64 +132,10 @@ impl VolumeKeyBackingTypeMetadata for VolumeKeyVolume{ const FLOAT_MAX : f32 = 1.0; } -impl VolumeKeyBackingTypeMetadata for VolumeKeyBalance{ +impl KeyBackingTypeMetadata for VolumeKeyBalance{ type BackingType = i8; const MIN : Self::BackingType = -100; const MAX : Self::BackingType = 100; const FLOAT_MIN : f32 = -1.0; const FLOAT_MAX : f32 = 1.0; } - -impl_volume_key_backing_type_from_float_for!(i8, u8); - -#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)] -struct VolumeKey<BackingType : VolumeKeyBackingTypeMetadata>(BackingType::BackingType); - -impl<BackingType : VolumeKeyBackingTypeMetadata> VolumeKey<BackingType> { - fn match_float(float : f32) -> Self { - let x = (float - BackingType::FLOAT_MIN) / (BackingType::FLOAT_MAX - BackingType::FLOAT_MIN); - let cx = x.clamp(0.0,1.0); - let interval = BackingType::MAX.into() - BackingType::MIN.into(); - let offset = interval * cx; - let result = BackingType::BackingType::round_from_float(offset) + BackingType::MIN; - Self(result) - } - -} -/// Custom serializer, as TOML only supports string map keys. -impl<Metadata : VolumeKeyBackingTypeMetadata> Serialize for VolumeKey<Metadata> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where S : Serializer - { - let string = self.0.to_string(); - serializer.serialize_str(&string) - } -} -impl<'de, Metadata> Deserialize<'de> for VolumeKey<Metadata> - where Metadata : VolumeKeyBackingTypeMetadata, -{ - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where D: Deserializer<'de> - { - let a = String::deserialize(deserializer)?; - match a.parse() { - Ok(x) => { - if x >= Metadata::MIN && x <= Metadata::MAX { - Ok(Self(x)) - } - else { - Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) - } - } - Err(e) => { - match e.kind() { - IntErrorKind::Empty => { Err(DeError::missing_field("Bin Map Key")) } - IntErrorKind::InvalidDigit => { Err(DeError::invalid_type(DeUnexpect::Str(&a), &"an integer value")) } - IntErrorKind::NegOverflow | IntErrorKind::PosOverflow => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("an integer equal or larger {} and equal or smaller {}", Metadata::MIN, Metadata::MAX))) } - IntErrorKind::Zero => { Err(DeError::invalid_value(DeUnexpect::Str(&a), &&*format!("a nonzero integer between {} and {}", Metadata::MIN, Metadata::MAX)))} - _ => { Err(DeError::custom("Value could not be parsed")) } - } - } - } - } -} diff --git a/pulse/src/runnable/mod.rs b/pulse/src/runnable/mod.rs index b207874..4a89c5c 100644 --- a/pulse/src/runnable/mod.rs +++ b/pulse/src/runnable/mod.rs @@ -3,6 +3,7 @@ use crate::config::*; use swaystatus_plugin::*; use crate::communication::*; use std::convert::TryFrom; +use formatable_float::FormattingError; pub mod pulse; use pulse::{Pulse,MainLoopCreationError, PulseContext, PaContextState, SinkHandle, PulseOperation}; |
