commit
This commit is contained in:
parent
1904e3e96a
commit
b5a7ab3c6b
71 changed files with 921 additions and 1297 deletions
17
handlers/pa_volume/Cargo.toml
Normal file
17
handlers/pa_volume/Cargo.toml
Normal 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"
|
159
handlers/pa_volume/src/handler.rs
Normal file
159
handlers/pa_volume/src/handler.rs
Normal 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);
|
||||
}
|
||||
}
|
28
handlers/pa_volume/src/main.rs
Normal file
28
handlers/pa_volume/src/main.rs
Normal 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(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue