From eb0983e79d11594955f9df09d24a14d3bc3a25a5 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Mon, 15 Jan 2024 17:00:44 +0100 Subject: [PATCH] commit --- deckster/examples/full/icons/apps/youtube.svg | 2 +- deckster/examples/full/key-pages/default.toml | 21 ++--- .../examples/full/knob-pages/default.toml | 11 ++- deckster/src/modes/knob/audio_volume.rs | 94 +++++++++++++------ deckster/src/runner/command.rs | 2 +- deckster/src/runner/graphics.rs | 7 +- deckster/src/runner/mod.rs | 19 ++-- deckster/src/runner/state.rs | 4 +- 8 files changed, 98 insertions(+), 62 deletions(-) diff --git a/deckster/examples/full/icons/apps/youtube.svg b/deckster/examples/full/icons/apps/youtube.svg index 01652bc..8d62a5d 100644 --- a/deckster/examples/full/icons/apps/youtube.svg +++ b/deckster/examples/full/icons/apps/youtube.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/deckster/examples/full/key-pages/default.toml b/deckster/examples/full/key-pages/default.toml index 7677da0..03c9c78 100644 --- a/deckster/examples/full/key-pages/default.toml +++ b/deckster/examples/full/key-pages/default.toml @@ -1,43 +1,36 @@ -[keys.1x1] +[keys.1x2] icon = "@ph/skip-back" mode.playerctl__button.command = "previous" mode.playerctl__button.style.inactive.icon = "@ph/skip-back[alpha=0.4]" -[keys.2x1] +[keys.2x2] icon = "@ph/play-pause[alpha=0.4]" mode.playerctl__button.command = "play-pause" mode.playerctl__button.style.paused.icon = "@ph/play" mode.playerctl__button.style.playing.icon = "@ph/pause" -[keys.3x1] +[keys.3x2] icon = "@ph/skip-forward" mode.playerctl__button.command = "next" mode.playerctl__button.style.inactive.icon = "@ph/skip-forward[alpha=0.4]" -[keys.1x2] +[keys.1x3] icon = "@fad/shuffle[alpha=0.4]" mode.playerctl__shuffle.style.on.icon = "@fad/shuffle[color=#58fc11]" -[keys.2x2] +[keys.2x3] icon = "@fad/repeat[alpha=0.4]" mode.playerctl__loop.style.single.icon = "@fad/repeat-one[color=#58fc11]" mode.playerctl__loop.style.all.icon = "@fad/repeat[color=#58fc11]" -[keys.4x1] +[keys.3x3] icon = "@ph/timer[color=#ff0000]" mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"] mode.timer.vibrate_when_finished = true mode.timer.needy = true -[keys.3x3] -icon = "@fad/thunderbolt" -label = "Dock" -border= "#00ff00" -mode.home_assistant__switch.name = "switch.moritz_thunderbolt_dock" -mode.home_assistant__switch.icon.on = "@fad/thunderbolt[color=#58fc11]" - [keys.4x3] icon = "@ph/computer-tower" -label = "Tower PC unnötig lang" +label = "Gaming PC" mode.home_assistant__switch.name = "switch.mwin" mode.home_assistant__switch.icon.on = "@ph/computer-tower[color=#58fc11]" \ No newline at end of file diff --git a/deckster/examples/full/knob-pages/default.toml b/deckster/examples/full/knob-pages/default.toml index 38c33a2..bbcbbf1 100644 --- a/deckster/examples/full/knob-pages/default.toml +++ b/deckster/examples/full/knob-pages/default.toml @@ -5,8 +5,7 @@ indicators.bar.color = "#ffffff50" 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.muted_turn_action = "normal" mode.audio_volume.style.active.label = "{percentage}%" mode.audio_volume.style.muted.label = "Muted" @@ -27,23 +26,25 @@ 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]" +icon = "@apps/youtube[scale=1.3]" indicators.bar.color = "#ffffff50" mode.audio_volume.delta = 0.05 +mode.audio_volume.muted_turn_action = "unmute" 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]" +mode.audio_volume.style.inactive.icon = "@apps/youtube[scale=1.3|grayscale]" [knobs.left-bottom] icon = "@apps/spotify[scale=1.2]" indicators.bar.color = "#ffffff50" mode.audio_volume.delta = 0.05 +mode.audio_volume.muted_turn_action = "unmute-at-zero" 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]" \ No newline at end of file +mode.audio_volume.style.inactive.icon = "@apps/spotify[scale=1.2|grayscale|alpha=0.6]" \ No newline at end of file diff --git a/deckster/src/modes/knob/audio_volume.rs b/deckster/src/modes/knob/audio_volume.rs index f41f831..b58f2d9 100644 --- a/deckster/src/modes/knob/audio_volume.rs +++ b/deckster/src/modes/knob/audio_volume.rs @@ -80,8 +80,9 @@ pub enum TargetPredicateProperty { pub enum MutedTurnAction { #[default] Ignore, + Normal, UnmuteAtZero, - UnmuteAndRestore, + Unmute, } #[derive(Debug, Default, Eq, PartialEq, Deserialize)] @@ -102,14 +103,10 @@ pub enum State { static PA_VOLUME_INTERFACE: Lazy = Lazy::new(|| PaVolumeInterface::spawn_thread("deckster".to_owned())); -fn get_volume_cv(channel_volumes: &[f32]) -> f32 { +fn get_volume_from_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>) -> f32 { - 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, @@ -158,22 +155,28 @@ pub async fn handle(path: KnobPath, config: Arc, mut events: broadcast:: let pa_volume_interface = &PA_VOLUME_INTERFACE; let (initial_state, mut volume_states) = pa_volume_interface.subscribe_to_state(); - if let Some(state) = initial_state { - entity_state = state - .entities_by_id() - .values() - .find(|entity| state_matches(&config.target, &entity)) - .map(Arc::clone); + let update_knob_value = { + let commands = commands.clone(); + let config = Arc::clone(&config); + let path = path.clone(); - commands - .send(IoWorkerCommand::SetKnobValue { - path: path.clone(), - value: get_volume_es(&entity_state), - }) - .unwrap(); - } + move |entity_state: &Option>| { + commands + .send(IoWorkerCommand::SetKnobValue { + path: path.clone(), + value: entity_state.as_ref().map(|s| { + if s.is_muted() && config.muted_turn_action == MutedTurnAction::UnmuteAtZero { + 0.0 + } else { + get_volume_from_cv(&s.channel_volumes()) + } + }), + }) + .unwrap(); + } + }; - let update_style = { + let update_knob_style = { let commands = commands.clone(); let config = Arc::clone(&config); let path = path.clone(); @@ -188,9 +191,13 @@ pub async fn handle(path: KnobPath, config: Arc, mut events: broadcast:: let mut style = config.style.get(&state).cloned(); if let Some(ref mut s) = &mut style { - let v = get_volume_es(entity_state); + let v = entity_state.as_ref().map(|s| get_volume_from_cv(&s.channel_volumes())); + if let Some(ref mut label) = &mut s.label { - *label = label.replace("{percentage}", &((v * 100.0).round() as u32).to_string()); + if let Some(v) = v { + // v is only None when state is State::Inactive + *label = label.replace("{percentage}", &((v * 100.0).round() as u32).to_string()); + } } } @@ -203,13 +210,20 @@ pub async fn handle(path: KnobPath, config: Arc, mut events: broadcast:: } }; + if let Some(state) = initial_state { + entity_state = state + .entities_by_id() + .values() + .find(|entity| state_matches(&config.target, &entity)) + .map(Arc::clone); + } + loop { select! { Ok(volume_state) = volume_states.recv() => { 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() + update_knob_style(&entity_state); + update_knob_value(&entity_state); } Ok((event_path, event)) = events.recv() => { @@ -220,17 +234,37 @@ pub async fn handle(path: KnobPath, config: Arc, mut events: broadcast:: if let Some(entity_state) = &entity_state { match event { KnobEvent::Rotate { direction } => { - let factor = match direction { + let factor: f32 = match direction { RotationDirection::Clockwise => 1.0, RotationDirection::Counterclockwise => -1.0, }; - let current_v = get_volume_cv(&entity_state.channel_volumes()); - 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()]); + let mut current_v = get_volume_from_cv(&entity_state.channel_volumes()); + + if entity_state.is_muted() { + match config.muted_turn_action { + MutedTurnAction::Ignore => continue, + MutedTurnAction::UnmuteAtZero => { + current_v = 0.0 + } + MutedTurnAction::Unmute => {}, + MutedTurnAction::Normal => {} + } + } + + let new_v = (current_v + (factor * config.delta.unwrap_or(0.01))).clamp(0.0, 1.0); + if new_v > 0.0 && matches!(config.muted_turn_action, MutedTurnAction::Unmute | MutedTurnAction::UnmuteAtZero) { + pa_volume_interface.set_is_muted(*entity_state.id(), false); + } + + pa_volume_interface.set_channel_volumes(*entity_state.id(), vec![new_v; entity_state.channel_volumes().len()]); } KnobEvent::Press => { - pa_volume_interface.set_is_muted(*entity_state.id(), !entity_state.is_muted()) + let is_muted = entity_state.is_muted(); + + if (is_muted && !config.disable_press_to_unmute) || (!is_muted && !config.disable_press_to_mute) { + pa_volume_interface.set_is_muted(*entity_state.id(), !is_muted) + } } _ => {} } diff --git a/deckster/src/runner/command.rs b/deckster/src/runner/command.rs index 0e4bd61..7e6b045 100644 --- a/deckster/src/runner/command.rs +++ b/deckster/src/runner/command.rs @@ -12,5 +12,5 @@ pub enum IoWorkerCommand { SetActivePages { key_page_id: String, knob_page_id: String }, SetKeyStyle { path: KeyPath, value: Option }, SetKnobStyle { path: KnobPath, value: Option }, - SetKnobValue { path: KnobPath, value: f32 }, + SetKnobValue { path: KnobPath, value: Option }, } diff --git a/deckster/src/runner/graphics.rs b/deckster/src/runner/graphics.rs index 49594e1..4593627 100644 --- a/deckster/src/runner/graphics.rs +++ b/deckster/src/runner/graphics.rs @@ -84,9 +84,14 @@ pub fn render_knob(context: &GraphicsContext, screen_size: IntSize, state: Optio const WIDTH: f32 = 5.0; const PADDING_X: f32 = 5.0; const PADDING_Y: f32 = 20.0; + const HEIGHT_AT_ZERO: f32 = 2.0; let max_height: f32 = pixmap.height() as f32 - PADDING_Y * 2.0; - let height = state.value * max_height; + let height = if let Some(value) = &state.value { + HEIGHT_AT_ZERO + value * (max_height - HEIGHT_AT_ZERO) + } else { + 0.0 + }; let x = if state.path.position.is_left() { PADDING_X diff --git a/deckster/src/runner/mod.rs b/deckster/src/runner/mod.rs index a379db9..41f7937 100644 --- a/deckster/src/runner/mod.rs +++ b/deckster/src/runner/mod.rs @@ -346,15 +346,18 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap(); } IoWorkerCommand::SetKnobValue { path, value } => { - if !(0.0..=1.0).contains(&value) { - error!("Received SetKnobValue with an out-of-range value: {}", value) - } else { - state.mutate_knob_for_command("SetKnobValue", &path, |k| { - k.value = value; - }); - - draw_knob_at_path_if_visible(context, state, path); + if let Some(v) = value { + if !(0.0..=1.0).contains(&v) { + error!("Received SetKnobValue with an out-of-range value: {}", v); + return; + } } + + state.mutate_knob_for_command("SetKnobValue", &path, |k| { + k.value = value; + }); + + draw_knob_at_path_if_visible(context, state, path); } } } diff --git a/deckster/src/runner/state.rs b/deckster/src/runner/state.rs index 4fb2fc4..880ad50 100644 --- a/deckster/src/runner/state.rs +++ b/deckster/src/runner/state.rs @@ -57,7 +57,7 @@ impl State { }, base_style: knob_config.base_style.clone(), style: None, - value: 0.0, + value: None, } }), }) @@ -127,5 +127,5 @@ pub struct Knob { pub path: KnobPath, pub base_style: KnobStyle, pub style: Option, - pub value: f32, + pub value: Option, }