deckster/handlers/pa_volume/src/handler.rs
2024-01-31 01:23:56 +01:00

159 lines
5.1 KiB
Rust

use log::warn;
use once_cell::sync::Lazy;
use parse_display::helpers::regex::Regex;
use parse_display::Display;
use serde::Deserialize;
use deckster_mode::{DecksterHandler, HandlerEvent, HandlerInitializationError, InitialHandlerMessage, KnobPath, KnobStyleByStateMap};
use pa_volume_interface::{PaEntityKind, PaEntityMetadata, PaEntityState, PaVolumeInterface};
#[derive(Debug, Clone, Deserialize)]
pub struct KnobConfig {
pub target: Target,
pub delta: Option<f32>,
#[serde(default)]
pub disable_press_to_mute: bool,
#[serde(default)]
pub disable_press_to_unmute: bool,
#[serde(default)]
pub muted_turn_action: MutedTurnAction,
#[serde(default)]
pub style: KnobStyleByStateMap<State>,
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Display)]
#[serde(rename_all = "kebab-case")]
#[display(style = "kebab-case")]
pub enum TargetKind {
Input,
Output,
Application,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Target {
#[serde(rename = "type")]
kind: TargetKind,
predicates: Vec<TargetPredicate>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TargetPredicate {
property: TargetPredicateProperty,
#[serde(flatten)]
pattern: TargetPredicatePattern,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum TargetPredicatePattern {
Static {
value: String,
},
Regex {
#[serde(with = "serde_regex")]
regex: Regex,
},
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Display)]
#[serde(rename_all = "kebab-case")]
#[display(style = "kebab-case")]
pub enum TargetPredicateProperty {
Description,
InternalName,
ApplicationName,
BinaryName,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum MutedTurnAction {
#[default]
Ignore,
Normal,
UnmuteAtZero,
Unmute,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Direction {
#[default]
Output,
Input,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum State {
Inactive,
Active,
Muted,
}
static PA_VOLUME_INTERFACE: Lazy<PaVolumeInterface> = Lazy::new(|| PaVolumeInterface::spawn_thread("deckster".to_owned()));
fn get_volume_from_cv(channel_volumes: &[f32]) -> f32 {
*channel_volumes.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
}
fn state_matches(target: &Target, state: &PaEntityState) -> bool {
if !match target.kind {
TargetKind::Input => state.kind() == PaEntityKind::Source,
TargetKind::Output => state.kind() == PaEntityKind::Sink,
TargetKind::Application => state.kind() == PaEntityKind::SinkInput,
} {
return false;
}
static EMPTY_STRING: String = String::new();
return target.predicates.iter().all(|p| {
let v = match (&p.property, state.metadata()) {
(TargetPredicateProperty::InternalName, PaEntityMetadata::Sink { name, .. }) => Some(name),
(TargetPredicateProperty::InternalName, PaEntityMetadata::Source { name, .. }) => Some(name),
(TargetPredicateProperty::InternalName, PaEntityMetadata::SinkInput { .. }) => None,
(TargetPredicateProperty::Description, PaEntityMetadata::Sink { description, .. }) => Some(description),
(TargetPredicateProperty::Description, PaEntityMetadata::Source { description, .. }) => Some(description),
(TargetPredicateProperty::Description, PaEntityMetadata::SinkInput { description, .. }) => Some(description),
(TargetPredicateProperty::ApplicationName, PaEntityMetadata::Sink { .. }) => None,
(TargetPredicateProperty::ApplicationName, PaEntityMetadata::Source { .. }) => None,
(TargetPredicateProperty::ApplicationName, PaEntityMetadata::SinkInput { application_name, .. }) => {
Some(application_name.as_ref().unwrap_or(&EMPTY_STRING))
}
(TargetPredicateProperty::BinaryName, PaEntityMetadata::Sink { .. }) => None,
(TargetPredicateProperty::BinaryName, PaEntityMetadata::Source { .. }) => None,
(TargetPredicateProperty::BinaryName, PaEntityMetadata::SinkInput { binary_name, .. }) => Some(binary_name.as_ref().unwrap_or(&EMPTY_STRING)),
};
if let Some(v) = v {
match &p.pattern {
TargetPredicatePattern::Static { value } => value == v,
TargetPredicatePattern::Regex { regex } => regex.is_match(v),
}
} else {
warn!("Property \"{}\" is not available for targets of type \"{}\"", &p.property, &target.kind);
false
}
});
}
pub struct Handler {
knobs: im::HashMap<KnobPath, KnobConfig>,
}
impl Handler {
pub fn new(data: InitialHandlerMessage<(), KnobConfig>) -> Result<Self, HandlerInitializationError> {
Ok(Handler {
knobs: data.knob_configs.into_iter().map(|(p, (_, c))| (p, c)).collect(),
})
}
}
impl DecksterHandler for Handler {
fn handle(&mut self, event: HandlerEvent) {
dbg!(&self.knobs, event);
}
}