commit
This commit is contained in:
parent
6c7d749a2c
commit
eb0983e79d
8 changed files with 98 additions and 62 deletions
|
@ -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 |
|
@ -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]"
|
|
@ -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]"
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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> },
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue