This commit is contained in:
Moritz Ruth 2024-01-31 01:23:56 +01:00
parent 1904e3e96a
commit b5a7ab3c6b
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
71 changed files with 921 additions and 1297 deletions

View file

@ -0,0 +1,17 @@
[package]
name = "pa_volume"
version = "0.1.0"
edition = "2021"
[dependencies]
deckster_mode = { path = "../../crates/deckster_mode" }
pa_volume_interface = { path = "../../crates/pa_volume_interface" }
clap = { version = "4.4.18", features = ["derive"] }
color-eyre = "0.6.2"
serde = { version = "1.0.196", features = ["derive"] }
serde_regex = "1.1.0"
parse-display = "0.8.2"
once_cell = "1.19.0"
env_logger = "0.11.1"
log = "0.4.20"
im = "15.1.0"

View file

@ -0,0 +1,159 @@
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);
}
}

View file

@ -0,0 +1,28 @@
use clap::Parser;
use color_eyre::Result;
use crate::handler::Handler;
mod handler;
#[derive(Debug, Parser)]
#[command(name = "pa_volume")]
enum CliCommand {
#[command(about = "Print all currently available entities")]
Entities,
#[command(name = "deckster-run", hide = true)]
Run,
}
fn main() -> Result<()> {
let command = CliCommand::parse();
match command {
CliCommand::Entities => todo!(),
CliCommand::Run => {
deckster_mode::run(Handler::new)?;
}
}
Ok(())
}