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"
|
icon = "@ph/skip-back"
|
||||||
mode.playerctl__button.command = "previous"
|
mode.playerctl__button.command = "previous"
|
||||||
mode.playerctl__button.style.inactive.icon = "@ph/skip-back[alpha=0.4]"
|
mode.playerctl__button.style.inactive.icon = "@ph/skip-back[alpha=0.4]"
|
||||||
|
|
||||||
[keys.2x1]
|
[keys.2x2]
|
||||||
icon = "@ph/play-pause[alpha=0.4]"
|
icon = "@ph/play-pause[alpha=0.4]"
|
||||||
mode.playerctl__button.command = "play-pause"
|
mode.playerctl__button.command = "play-pause"
|
||||||
mode.playerctl__button.style.paused.icon = "@ph/play"
|
mode.playerctl__button.style.paused.icon = "@ph/play"
|
||||||
mode.playerctl__button.style.playing.icon = "@ph/pause"
|
mode.playerctl__button.style.playing.icon = "@ph/pause"
|
||||||
|
|
||||||
[keys.3x1]
|
[keys.3x2]
|
||||||
icon = "@ph/skip-forward"
|
icon = "@ph/skip-forward"
|
||||||
mode.playerctl__button.command = "next"
|
mode.playerctl__button.command = "next"
|
||||||
mode.playerctl__button.style.inactive.icon = "@ph/skip-forward[alpha=0.4]"
|
mode.playerctl__button.style.inactive.icon = "@ph/skip-forward[alpha=0.4]"
|
||||||
|
|
||||||
[keys.1x2]
|
[keys.1x3]
|
||||||
icon = "@fad/shuffle[alpha=0.4]"
|
icon = "@fad/shuffle[alpha=0.4]"
|
||||||
mode.playerctl__shuffle.style.on.icon = "@fad/shuffle[color=#58fc11]"
|
mode.playerctl__shuffle.style.on.icon = "@fad/shuffle[color=#58fc11]"
|
||||||
|
|
||||||
[keys.2x2]
|
[keys.2x3]
|
||||||
icon = "@fad/repeat[alpha=0.4]"
|
icon = "@fad/repeat[alpha=0.4]"
|
||||||
mode.playerctl__loop.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
mode.playerctl__loop.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
||||||
mode.playerctl__loop.style.all.icon = "@fad/repeat[color=#58fc11]"
|
mode.playerctl__loop.style.all.icon = "@fad/repeat[color=#58fc11]"
|
||||||
|
|
||||||
[keys.4x1]
|
[keys.3x3]
|
||||||
icon = "@ph/timer[color=#ff0000]"
|
icon = "@ph/timer[color=#ff0000]"
|
||||||
mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"]
|
mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"]
|
||||||
mode.timer.vibrate_when_finished = true
|
mode.timer.vibrate_when_finished = true
|
||||||
mode.timer.needy = 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]
|
[keys.4x3]
|
||||||
icon = "@ph/computer-tower"
|
icon = "@ph/computer-tower"
|
||||||
label = "Tower PC unnötig lang"
|
label = "Gaming PC"
|
||||||
mode.home_assistant__switch.name = "switch.mwin"
|
mode.home_assistant__switch.name = "switch.mwin"
|
||||||
mode.home_assistant__switch.icon.on = "@ph/computer-tower[color=#58fc11]"
|
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.delta = 0.05
|
||||||
mode.audio_volume.target.type = "input"
|
mode.audio_volume.target.type = "input"
|
||||||
mode.audio_volume.target.predicates = [{ property = "description", value = "SC425 USB Microphone Analog Stereo" }]
|
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 = "normal"
|
||||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
|
||||||
|
|
||||||
mode.audio_volume.style.active.label = "{percentage}%"
|
mode.audio_volume.style.active.label = "{percentage}%"
|
||||||
mode.audio_volume.style.muted.label = "Muted"
|
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]"
|
mode.audio_volume.style.inactive.icon = "@apps/discord[scale=0.25|grayscale|alpha=0.8]"
|
||||||
|
|
||||||
[knobs.left-middle]
|
[knobs.left-middle]
|
||||||
icon = "@apps/youtube[scale=1.3|color=#ff0000]"
|
icon = "@apps/youtube[scale=1.3]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
mode.audio_volume.delta = 0.05
|
||||||
|
mode.audio_volume.muted_turn_action = "unmute"
|
||||||
mode.audio_volume.target.type = "application"
|
mode.audio_volume.target.type = "application"
|
||||||
mode.audio_volume.target.predicates = [{ property = "binary-name", value = "librewolf" }, { property = "description", regex = "\\- Piped$" }]
|
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.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]
|
[knobs.left-bottom]
|
||||||
icon = "@apps/spotify[scale=1.2]"
|
icon = "@apps/spotify[scale=1.2]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
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.type = "application"
|
||||||
mode.audio_volume.target.predicates = [{ property = "application-name", value = "spotify" }]
|
mode.audio_volume.target.predicates = [{ property = "application-name", value = "spotify" }]
|
||||||
|
|
||||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
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 {
|
pub enum MutedTurnAction {
|
||||||
#[default]
|
#[default]
|
||||||
Ignore,
|
Ignore,
|
||||||
|
Normal,
|
||||||
UnmuteAtZero,
|
UnmuteAtZero,
|
||||||
UnmuteAndRestore,
|
Unmute,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
|
#[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()));
|
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()
|
*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 {
|
fn state_matches(target: &Target, state: &PaEntityState) -> bool {
|
||||||
if !match target.kind {
|
if !match target.kind {
|
||||||
TargetKind::Input => state.kind() == PaEntityKind::Source,
|
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 pa_volume_interface = &PA_VOLUME_INTERFACE;
|
||||||
let (initial_state, mut volume_states) = pa_volume_interface.subscribe_to_state();
|
let (initial_state, mut volume_states) = pa_volume_interface.subscribe_to_state();
|
||||||
|
|
||||||
if let Some(state) = initial_state {
|
let update_knob_value = {
|
||||||
entity_state = state
|
let commands = commands.clone();
|
||||||
.entities_by_id()
|
let config = Arc::clone(&config);
|
||||||
.values()
|
let path = path.clone();
|
||||||
.find(|entity| state_matches(&config.target, &entity))
|
|
||||||
.map(Arc::clone);
|
|
||||||
|
|
||||||
|
move |entity_state: &Option<Arc<PaEntityState>>| {
|
||||||
commands
|
commands
|
||||||
.send(IoWorkerCommand::SetKnobValue {
|
.send(IoWorkerCommand::SetKnobValue {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: get_volume_es(&entity_state),
|
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();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let update_style = {
|
let update_knob_style = {
|
||||||
let commands = commands.clone();
|
let commands = commands.clone();
|
||||||
let config = Arc::clone(&config);
|
let config = Arc::clone(&config);
|
||||||
let path = path.clone();
|
let path = path.clone();
|
||||||
|
@ -188,11 +191,15 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
||||||
let mut style = config.style.get(&state).cloned();
|
let mut style = config.style.get(&state).cloned();
|
||||||
|
|
||||||
if let Some(ref mut s) = &mut style {
|
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 {
|
if let Some(ref mut label) = &mut s.label {
|
||||||
|
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());
|
*label = label.replace("{percentage}", &((v * 100.0).round() as u32).to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.send(IoWorkerCommand::SetKnobStyle {
|
.send(IoWorkerCommand::SetKnobStyle {
|
||||||
|
@ -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 {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
Ok(volume_state) = volume_states.recv() => {
|
Ok(volume_state) = volume_states.recv() => {
|
||||||
entity_state = volume_state.entities_by_id().values().find(|entity| state_matches(&config.target, &entity)).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);
|
update_knob_style(&entity_state);
|
||||||
|
update_knob_value(&entity_state);
|
||||||
commands.send(IoWorkerCommand::SetKnobValue { path: path.clone(), value: get_volume_es(&entity_state) }).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((event_path, event)) = events.recv() => {
|
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 {
|
if let Some(entity_state) = &entity_state {
|
||||||
match event {
|
match event {
|
||||||
KnobEvent::Rotate { direction } => {
|
KnobEvent::Rotate { direction } => {
|
||||||
let factor = match direction {
|
let factor: f32 = match direction {
|
||||||
RotationDirection::Clockwise => 1.0,
|
RotationDirection::Clockwise => 1.0,
|
||||||
RotationDirection::Counterclockwise => -1.0,
|
RotationDirection::Counterclockwise => -1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_v = get_volume_cv(&entity_state.channel_volumes());
|
let mut current_v = get_volume_from_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()]);
|
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 => {
|
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 },
|
SetActivePages { key_page_id: String, knob_page_id: String },
|
||||||
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
||||||
SetKnobStyle { path: KnobPath, value: Option<KnobStyle> },
|
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 WIDTH: f32 = 5.0;
|
||||||
const PADDING_X: f32 = 5.0;
|
const PADDING_X: f32 = 5.0;
|
||||||
const PADDING_Y: f32 = 20.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 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() {
|
let x = if state.path.position.is_left() {
|
||||||
PADDING_X
|
PADDING_X
|
||||||
|
|
|
@ -346,9 +346,13 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke
|
||||||
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
IoWorkerCommand::SetKnobValue { path, value } => {
|
IoWorkerCommand::SetKnobValue { path, value } => {
|
||||||
if !(0.0..=1.0).contains(&value) {
|
if let Some(v) = value {
|
||||||
error!("Received SetKnobValue with an out-of-range value: {}", value)
|
if !(0.0..=1.0).contains(&v) {
|
||||||
} else {
|
error!("Received SetKnobValue with an out-of-range value: {}", v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.mutate_knob_for_command("SetKnobValue", &path, |k| {
|
state.mutate_knob_for_command("SetKnobValue", &path, |k| {
|
||||||
k.value = value;
|
k.value = value;
|
||||||
});
|
});
|
||||||
|
@ -357,7 +361,6 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// active -> config.active_button_color
|
// active -> config.active_button_color
|
||||||
// no actions defined -> #000000
|
// no actions defined -> #000000
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl State {
|
||||||
},
|
},
|
||||||
base_style: knob_config.base_style.clone(),
|
base_style: knob_config.base_style.clone(),
|
||||||
style: None,
|
style: None,
|
||||||
value: 0.0,
|
value: None,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -127,5 +127,5 @@ pub struct Knob {
|
||||||
pub path: KnobPath,
|
pub path: KnobPath,
|
||||||
pub base_style: KnobStyle,
|
pub base_style: KnobStyle,
|
||||||
pub style: Option<KnobStyle>,
|
pub style: Option<KnobStyle>,
|
||||||
pub value: f32,
|
pub value: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue