159 lines
5.1 KiB
Rust
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);
|
|
}
|
|
}
|