commit
This commit is contained in:
parent
aee74cb528
commit
6c7d749a2c
8 changed files with 274 additions and 78 deletions
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -376,6 +376,7 @@ dependencies = [
|
|||
"loupedeck_serial",
|
||||
"once_cell",
|
||||
"pa-volume-interface",
|
||||
"parse-display",
|
||||
"regex",
|
||||
"resvg",
|
||||
"rgb",
|
||||
|
@ -1061,6 +1062,32 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-display"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6509d08722b53e8dafe97f2027b22ccbe3a5db83cb352931e9716b0aa44bc5c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"parse-display-derive",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-display-derive"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68517892c8daf78da08c0db777fcc17e07f2f63ef70041718f8a7630ad84f341"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"regex-syntax 0.7.5",
|
||||
"structmeta",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
|
@ -1161,7 +1188,7 @@ dependencies = [
|
|||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1172,9 +1199,15 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
|||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.2"
|
||||
|
@ -1499,6 +1532,29 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "structmeta"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "structmeta-derive"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.13.0"
|
||||
|
|
|
@ -29,4 +29,5 @@ tiny-skia = "0.11.3"
|
|||
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "rt-multi-thread", "sync"]}
|
||||
toml = "0.8.8"
|
||||
walkdir = "2.4.0"
|
||||
once_cell = "1.19.0"
|
||||
once_cell = "1.19.0"
|
||||
parse-display = "0.8.2"
|
1
deckster/examples/full/icons/apps/youtube.svg
Normal file
1
deckster/examples/full/icons/apps/youtube.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>
|
After Width: | Height: | Size: 426 B |
|
@ -1,30 +1,49 @@
|
|||
#[knobs.right-top]
|
||||
#icon = "@ph/microphone-light[scale=0.9]"
|
||||
#indicators.bar.color = "#ffffff50"
|
||||
#
|
||||
#mode.audio_volume.direction = "input"
|
||||
#mode.audio_volume.regex = "^(SC425 USB Microphone Analog Stereo)$"
|
||||
#mode.audio_volume.disable_press_to_unmute = true
|
||||
#mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
||||
#mode.audio_volume.style.muted.label = "Muted"
|
||||
#mode.audio_volume.style.inactive.icon = "@ph/microphone-slash-light[alpha=0.9|color=#fc4646]"
|
||||
#
|
||||
#[knobs.right-middle]
|
||||
#icon = "@apps/discord[scale=0.25]"
|
||||
#indicators.bar.color = "#ffffff50"
|
||||
#
|
||||
#mode.audio_volume.regex = "Discord"
|
||||
#mode.audio_volume.style.active.label = "{percentage}%"
|
||||
#mode.audio_volume.style.muted.label = "Muted"
|
||||
#mode.audio_volume.style.inactive.label = ""
|
||||
#mode.audio_volume.style.inactive.icon = "@apps/discord[grayscale|alpha=0.9]"
|
||||
|
||||
[knobs.right-bottom]
|
||||
icon = "@apps/spotify[scale=1.1]"
|
||||
[knobs.right-top]
|
||||
icon = "@ph/microphone-light[scale=0.9]"
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.regex = "Spotify"
|
||||
mode.audio_volume.delta = 0.05
|
||||
mode.audio_volume.target.type = "input"
|
||||
mode.audio_volume.target.predicates = [{ property = "description", value = "SC425 USB Microphone Analog Stereo" }]
|
||||
mode.audio_volume.disable_press_to_unmute = true
|
||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
||||
|
||||
mode.audio_volume.style.active.label = "{percentage}%"
|
||||
mode.audio_volume.style.muted.label = "Muted"
|
||||
mode.audio_volume.style.inactive.label = ""
|
||||
mode.audio_volume.style.inactive.icon = "@apps/spotify[grayscale|alpha=0.9]"
|
||||
mode.audio_volume.style.muted.icon = "@ph/microphone-slash-light[scale=0.9|color=#fc4646]"
|
||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
||||
mode.audio_volume.style.inactive.label = "N/A"
|
||||
mode.audio_volume.style.inactive.icon = "@ph/microphone-slash-light[scale=0.9|alpha=0.8|color=#fc4646]"
|
||||
|
||||
[knobs.left-top]
|
||||
icon = "@apps/discord[scale=0.25]"
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.delta = 0.05
|
||||
mode.audio_volume.target.type = "application"
|
||||
mode.audio_volume.target.predicates = [{ property = "binary-name", value = "Discord" }, { property = "description", value = "playStream" }]
|
||||
|
||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
||||
mode.audio_volume.style.inactive.icon = "@apps/discord[scale=0.25|grayscale|alpha=0.8]"
|
||||
|
||||
[knobs.left-middle]
|
||||
icon = "@apps/youtube[scale=1.3|color=#ff0000]"
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.delta = 0.05
|
||||
mode.audio_volume.target.type = "application"
|
||||
mode.audio_volume.target.predicates = [{ property = "binary-name", value = "librewolf" }, { property = "description", regex = "\\- Piped$" }]
|
||||
|
||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
||||
mode.audio_volume.style.inactive.icon = "@apps/youtube[scale=1.3|grayscale|alpha=0.8]"
|
||||
|
||||
[knobs.left-bottom]
|
||||
icon = "@apps/spotify[scale=1.2]"
|
||||
indicators.bar.color = "#ffffff50"
|
||||
|
||||
mode.audio_volume.delta = 0.05
|
||||
mode.audio_volume.target.type = "application"
|
||||
mode.audio_volume.target.predicates = [{ property = "application-name", value = "spotify" }]
|
||||
|
||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
||||
mode.audio_volume.style.inactive.icon = "@apps/spotify[scale=1.2|grayscale|alpha=0.8]"
|
|
@ -67,6 +67,12 @@ pub enum KnobPosition {
|
|||
RightBottom,
|
||||
}
|
||||
|
||||
impl KnobPosition {
|
||||
pub fn is_left(&self) -> bool {
|
||||
matches!(self, KnobPosition::LeftBottom | KnobPosition::LeftMiddle | KnobPosition::LeftTop)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoupedeckKnob> for KnobPosition {
|
||||
fn from(value: LoupedeckKnob) -> Self {
|
||||
match value {
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use std::borrow::ToOwned;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use parse_display::Display;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use tokio::select;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use pa_volume_interface::{PaEntityState, PaVolumeInterface};
|
||||
use pa_volume_interface::{PaEntityKind, PaEntityMetadata, PaEntityState, PaVolumeInterface};
|
||||
|
||||
use crate::model::knob_page::StyleByStateMap;
|
||||
use crate::model::position::KnobPath;
|
||||
|
@ -17,12 +18,8 @@ use crate::runner::command::IoWorkerCommand;
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "default_delta")]
|
||||
pub delta: f32,
|
||||
#[serde(with = "serde_regex")]
|
||||
pub regex: Regex,
|
||||
#[serde(default)]
|
||||
pub direction: Direction,
|
||||
pub target: Target,
|
||||
pub delta: Option<f32>,
|
||||
#[serde(default)]
|
||||
pub disable_press_to_mute: bool,
|
||||
#[serde(default)]
|
||||
|
@ -30,13 +27,52 @@ pub struct Config {
|
|||
#[serde(default)]
|
||||
pub muted_turn_action: MutedTurnAction,
|
||||
#[serde(default)]
|
||||
pub label: HashMap<State, String>,
|
||||
#[serde(default)]
|
||||
pub style: StyleByStateMap<State>,
|
||||
}
|
||||
|
||||
fn default_delta() -> f32 {
|
||||
0.05
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Display)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[display(style = "kebab-case")]
|
||||
pub enum TargetKind {
|
||||
Input,
|
||||
Output,
|
||||
Application,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Target {
|
||||
#[serde(rename = "type")]
|
||||
kind: TargetKind,
|
||||
predicates: Vec<TargetPredicate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TargetPredicate {
|
||||
property: TargetPredicateProperty,
|
||||
#[serde(flatten)]
|
||||
pattern: TargetPredicatePattern,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum TargetPredicatePattern {
|
||||
Static {
|
||||
value: String,
|
||||
},
|
||||
Regex {
|
||||
#[serde(with = "serde_regex")]
|
||||
regex: Regex,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Deserialize, Display)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[display(style = "kebab-case")]
|
||||
pub enum TargetPredicateProperty {
|
||||
Description,
|
||||
InternalName,
|
||||
ApplicationName,
|
||||
BinaryName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
|
||||
|
@ -66,12 +102,54 @@ pub enum State {
|
|||
|
||||
static PA_VOLUME_INTERFACE: Lazy<PaVolumeInterface> = Lazy::new(|| PaVolumeInterface::spawn_thread("deckster".to_owned()));
|
||||
|
||||
fn get_volume_cv(channel_volumes: &Vec<f32>) -> f32 {
|
||||
*channel_volumes.first().unwrap()
|
||||
fn get_volume_cv(channel_volumes: &[f32]) -> f32 {
|
||||
*channel_volumes.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn get_volume_es(entity_state: &Option<Arc<PaEntityState>>) -> f32 {
|
||||
entity_state.as_ref().map(|s| *s.channel_volumes().first().unwrap()).unwrap_or(0.0)
|
||||
entity_state.as_ref().map(|s| get_volume_cv(&s.channel_volumes())).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
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 async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, commands: flume::Sender<IoWorkerCommand>) {
|
||||
|
@ -84,8 +162,9 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
|||
entity_state = state
|
||||
.entities_by_id()
|
||||
.values()
|
||||
.find(|entity| config.regex.is_match(entity.display_name()))
|
||||
.find(|entity| state_matches(&config.target, &entity))
|
||||
.map(Arc::clone);
|
||||
|
||||
commands
|
||||
.send(IoWorkerCommand::SetKnobValue {
|
||||
path: path.clone(),
|
||||
|
@ -127,7 +206,7 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
|||
loop {
|
||||
select! {
|
||||
Ok(volume_state) = volume_states.recv() => {
|
||||
entity_state = volume_state.entities_by_id().values().find(|entity| config.regex.is_match(entity.display_name())).map(Arc::clone);
|
||||
entity_state = volume_state.entities_by_id().values().find(|entity| state_matches(&config.target, &entity)).map(Arc::clone);
|
||||
update_style(&entity_state);
|
||||
|
||||
commands.send(IoWorkerCommand::SetKnobValue { path: path.clone(), value: get_volume_es(&entity_state) }).unwrap()
|
||||
|
@ -147,7 +226,7 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
|||
};
|
||||
|
||||
let current_v = get_volume_cv(&entity_state.channel_volumes());
|
||||
let v = (current_v + (factor * config.delta)).clamp(0.0, 1.0);
|
||||
let v = (current_v + (factor * config.delta.unwrap_or(0.01))).clamp(0.0, 1.0);
|
||||
pa_volume_interface.set_channel_volumes(*entity_state.id(), vec![v; entity_state.channel_volumes().len()]);
|
||||
}
|
||||
KnobEvent::Press => {
|
||||
|
|
|
@ -81,13 +81,23 @@ pub fn render_knob(context: &GraphicsContext, screen_size: IntSize, state: Optio
|
|||
if let Some(indicators) = &style.indicators {
|
||||
if let Some(bar) = indicators.bar.as_ref() {
|
||||
let color = bar.color.unwrap_or(RGBA::new(0xff, 0xff, 0xff, 0x50).into());
|
||||
const PADDING: f32 = 20.0;
|
||||
let max_height: f32 = pixmap.height() as f32 - PADDING * 2.0;
|
||||
const WIDTH: f32 = 5.0;
|
||||
const PADDING_X: f32 = 5.0;
|
||||
const PADDING_Y: f32 = 20.0;
|
||||
|
||||
let max_height: f32 = pixmap.height() as f32 - PADDING_Y * 2.0;
|
||||
let height = state.value * max_height;
|
||||
let y = pixmap.height() as f32 - PADDING - height;
|
||||
|
||||
let x = if state.path.position.is_left() {
|
||||
PADDING_X
|
||||
} else {
|
||||
pixmap.width() as f32 - PADDING_X - WIDTH
|
||||
};
|
||||
|
||||
let y = pixmap.height() as f32 - PADDING_Y - height;
|
||||
|
||||
pixmap.fill_rect(
|
||||
Rect::from_xywh(pixmap.width() as f32 - 10.0, y, 5.0, height).unwrap(),
|
||||
Rect::from_xywh(x, y, WIDTH, height).unwrap(),
|
||||
&Paint {
|
||||
shader: Shader::SolidColor(Color::from_rgba8(color.r, color.g, color.b, color.a)),
|
||||
..Paint::default()
|
||||
|
|
|
@ -10,7 +10,6 @@ use libpulse_binding::context::introspect::{Introspector, SinkInfo, SinkInputInf
|
|||
use libpulse_binding::context::subscribe::{Facility, InterestMaskSet};
|
||||
use libpulse_binding::context::{subscribe, Context, FlagSet};
|
||||
use libpulse_binding::def::Retval;
|
||||
use libpulse_binding::mainloop::api::Mainloop as MainloopTrait;
|
||||
use libpulse_binding::mainloop::standard::{IterateResult, Mainloop};
|
||||
use libpulse_binding::operation::Operation;
|
||||
use libpulse_binding::volume::{ChannelVolumes, Volume};
|
||||
|
@ -23,17 +22,32 @@ pub type PaEntityId = u32;
|
|||
pub enum PaEntityKind {
|
||||
Source,
|
||||
Sink,
|
||||
Application,
|
||||
SinkInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum PaEntityMetadata {
|
||||
Source {
|
||||
name: String,
|
||||
description: String,
|
||||
},
|
||||
Sink {
|
||||
name: String,
|
||||
description: String,
|
||||
},
|
||||
SinkInput {
|
||||
description: String,
|
||||
binary_name: Option<String>,
|
||||
application_name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaEntityState {
|
||||
id: PaEntityId,
|
||||
kind: PaEntityKind,
|
||||
name: String,
|
||||
display_name: String,
|
||||
channel_volumes: ChannelVolumes,
|
||||
is_muted: bool,
|
||||
metadata: PaEntityMetadata,
|
||||
}
|
||||
|
||||
impl PaEntityState {
|
||||
|
@ -41,16 +55,16 @@ impl PaEntityState {
|
|||
&self.id
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &PaEntityKind {
|
||||
&self.kind
|
||||
pub fn kind(&self) -> PaEntityKind {
|
||||
match &self.metadata {
|
||||
PaEntityMetadata::Source { .. } => PaEntityKind::Source,
|
||||
PaEntityMetadata::Sink { .. } => PaEntityKind::Sink,
|
||||
PaEntityMetadata::SinkInput { .. } => PaEntityKind::SinkInput,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &String {
|
||||
&self.display_name
|
||||
pub fn metadata(&self) -> &PaEntityMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
pub fn channel_volumes(&self) -> Vec<f32> {
|
||||
|
@ -67,10 +81,11 @@ impl From<&SourceInfo<'_>> for PaEntityState {
|
|||
PaEntityState {
|
||||
id: value.index as PaEntityId,
|
||||
is_muted: value.mute,
|
||||
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
display_name: value.description.clone().unwrap_or_default().into_owned(),
|
||||
kind: PaEntityKind::Source,
|
||||
channel_volumes: value.volume,
|
||||
metadata: PaEntityMetadata::Source {
|
||||
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
description: value.description.clone().unwrap_or_default().into_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,10 +95,11 @@ impl From<&SinkInfo<'_>> for PaEntityState {
|
|||
PaEntityState {
|
||||
id: value.index as PaEntityId,
|
||||
is_muted: value.mute,
|
||||
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
display_name: value.description.clone().unwrap_or_default().into_owned(),
|
||||
kind: PaEntityKind::Sink,
|
||||
channel_volumes: value.volume,
|
||||
metadata: PaEntityMetadata::Sink {
|
||||
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
description: value.description.clone().unwrap_or_default().into_owned(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,10 +109,18 @@ impl From<&SinkInputInfo<'_>> for PaEntityState {
|
|||
PaEntityState {
|
||||
id: value.index as PaEntityId,
|
||||
is_muted: value.mute,
|
||||
name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
display_name: value.name.clone().unwrap_or_default().into_owned(),
|
||||
kind: PaEntityKind::Application,
|
||||
channel_volumes: value.volume,
|
||||
metadata: PaEntityMetadata::SinkInput {
|
||||
description: value.name.clone().unwrap_or_default().into_owned(),
|
||||
application_name: value
|
||||
.proplist
|
||||
.get("application.name")
|
||||
.map(|v| String::from_utf8_lossy(v).trim_end_matches(char::from(0)).to_owned()),
|
||||
binary_name: value
|
||||
.proplist
|
||||
.get("application.process.binary")
|
||||
.map(|v| String::from_utf8_lossy(v).trim_end_matches(char::from(0)).to_owned()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -353,14 +377,14 @@ impl PaThread {
|
|||
loop {
|
||||
self.run_single_mainloop_iteration(false);
|
||||
|
||||
if let Ok(command) = self.commands_rx.try_recv() {
|
||||
while let Ok(command) = self.commands_rx.try_recv() {
|
||||
match command {
|
||||
PaCommand::SetIsMuted { id, value } => {
|
||||
if let Some(state) = PaThread::unwrap_state(¤t_state).entities_by_id.get(&id) {
|
||||
match state.kind {
|
||||
match state.kind() {
|
||||
PaEntityKind::Sink => self.introspector.borrow_mut().set_sink_mute_by_index(id, value, None),
|
||||
PaEntityKind::Source => self.introspector.borrow_mut().set_source_mute_by_index(id, value, None),
|
||||
PaEntityKind::Application => self.introspector.borrow_mut().set_sink_input_mute(id, value, None),
|
||||
PaEntityKind::SinkInput => self.introspector.borrow_mut().set_sink_input_mute(id, value, None),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -371,10 +395,10 @@ impl PaThread {
|
|||
value.set(i as u8, Volume((Volume::NORMAL.0 as f32 * v).floor() as u32));
|
||||
}
|
||||
|
||||
match state.kind {
|
||||
match state.kind() {
|
||||
PaEntityKind::Sink => self.introspector.borrow_mut().set_sink_volume_by_index(id, &value, None),
|
||||
PaEntityKind::Source => self.introspector.borrow_mut().set_source_volume_by_index(id, &value, None),
|
||||
PaEntityKind::Application => self.introspector.borrow_mut().set_sink_input_volume(id, &value, None),
|
||||
PaEntityKind::SinkInput => self.introspector.borrow_mut().set_sink_input_volume(id, &value, None),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue