This commit is contained in:
Moritz Ruth 2024-01-15 17:00:44 +01:00
parent 6c7d749a2c
commit eb0983e79d
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
8 changed files with 98 additions and 62 deletions

View file

@ -1 +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>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="#ff0000" 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>

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 441 B

View file

@ -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]"

View file

@ -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]"
mode.audio_volume.style.inactive.icon = "@apps/spotify[scale=1.2|grayscale|alpha=0.6]"

View file

@ -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<PaVolumeInterface> = 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<Arc<PaEntityState>>) -> 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<Config>, 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<Arc<PaEntityState>>| {
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<Config>, 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<Config>, 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<Config>, 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)
}
}
_ => {}
}

View file

@ -12,5 +12,5 @@ pub enum IoWorkerCommand {
SetActivePages { key_page_id: String, knob_page_id: String },
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
SetKnobStyle { path: KnobPath, value: Option<KnobStyle> },
SetKnobValue { path: KnobPath, value: f32 },
SetKnobValue { path: KnobPath, value: Option<f32> },
}

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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<KnobStyle>,
pub value: f32,
pub value: Option<f32>,
}