diff --git a/crates/deckster_shared/src/handler_communication.rs b/crates/deckster_shared/src/handler_communication.rs index 5dd416f..f4c1740 100644 --- a/crates/deckster_shared/src/handler_communication.rs +++ b/crates/deckster_shared/src/handler_communication.rs @@ -51,8 +51,8 @@ pub enum HandlerCommand { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct InitialHandlerMessage { - pub key_configs: im::HashMap, KeyConfig)>, - pub knob_configs: im::HashMap, KnobConfig)>, + pub key_configs: im::HashMap, + pub knob_configs: im::HashMap, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] @@ -64,8 +64,6 @@ pub enum HandlerInitializationResultMessage { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Error)] pub enum HandlerInitializationError { - #[error("The provided mode string is invalid: {message}")] - InvalidModeString { message: Box }, #[error("The provided handler config is invalid: {message}")] InvalidConfig { supports_keys: bool, diff --git a/crates/deckster_shared/src/image_filter.rs b/crates/deckster_shared/src/image_filter.rs index 2890cff..f40c3dd 100644 --- a/crates/deckster_shared/src/image_filter.rs +++ b/crates/deckster_shared/src/image_filter.rs @@ -243,15 +243,6 @@ impl Display for ImageFilter { is_first = false; } - if let Some(color) = self.destructive.color { - if !is_first { - f.write_str("|")? - } - - f.write_fmt(format_args!("color={}", color))?; - is_first = false; - } - if self.destructive.grayscale { if !is_first { f.write_str("|")? diff --git a/examples/full/key-pages/default.toml b/examples/full/key-pages/default.toml index 5b09c1f..523f825 100644 --- a/examples/full/key-pages/default.toml +++ b/examples/full/key-pages/default.toml @@ -1,33 +1,37 @@ [keys.1x2] icon = "@ph/skip-back" -handler = "playerctl previous" +handler = "playerctl" config.mode = "previous" config.style.inactive.icon = "@ph/skip-back[alpha=0.4]" [keys.2x2] icon = "@ph/play-pause[alpha=0.4]" -handler = "playerctl play-pause" +handler = "playerctl" +config.mode = "play-pause" config.style.paused.icon = "@ph/play" config.style.playing.icon = "@ph/pause" [keys.3x2] icon = "@ph/skip-forward" -handler = "playerctl next" +handler = "playerctl" +config.mode = "next" config.style.inactive.icon = "@ph/skip-forward[alpha=0.4]" [keys.1x3] icon = "@fad/shuffle[alpha=0.4]" -handler = "playerctl shuffle" +handler = "playerctl" +config.mode = "shuffle" config.style.on.icon = "@fad/shuffle[color=#58fc11]" [keys.2x3] icon = "@fad/repeat[alpha=0.4]" -handler = "playerctl loop" +handler = "playerctl" +config.mode = "loop" config.style.single.icon = "@fad/repeat-one[color=#58fc11]" config.style.all.icon = "@fad/repeat[color=#58fc11]" @@ -43,6 +47,7 @@ config.needy = true icon = "@ph/computer-tower" label = "Gaming PC" -handler = "home-assistant switch" +handler = "home-assistant" +config.mode = "switch" config.name = "switch.mwin" config.style.on.icon = "@ph/computer-tower[color=#58fc11]" \ No newline at end of file diff --git a/handlers/pa_volume/src/handler.rs b/handlers/pa_volume/src/handler.rs index 0505092..e1c148c 100644 --- a/handlers/pa_volume/src/handler.rs +++ b/handlers/pa_volume/src/handler.rs @@ -145,43 +145,6 @@ fn state_matches(target: &Target, state: &PaEntityState) -> bool { }); } -pub struct Handler { - events_sender: broadcast::Sender<(KnobPath, KnobEvent)>, - #[allow(unused)] - runtime: tokio::runtime::Runtime, -} - -impl Handler { - pub fn new(data: InitialHandlerMessage<(), KnobConfig>) -> Result { - let (events_sender, _) = broadcast::channel::<(KnobPath, KnobEvent)>(5); - let pa_volume_interface = Arc::new(PaVolumeInterface::spawn_thread("deckster handler".to_owned())); - - let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).build().unwrap(); - - for (path, (mode, config)) in data.knob_configs { - if !mode.is_empty() { - return Err(HandlerInitializationError::InvalidModeString { - message: "No mode string allowed.".into(), - }); - } - - let events_receiver = events_sender.subscribe(); - let a = Arc::clone(&pa_volume_interface); - runtime.spawn(manage_knob(path, config, events_receiver, a)); - } - - Ok(Handler { events_sender, runtime }) - } -} - -impl DecksterHandler for Handler { - fn handle(&mut self, event: HandlerEvent) { - if let HandlerEvent::Knob { path, event } = event { - self.events_sender.send((path, event)).unwrap(); - } - } -} - async fn manage_knob(path: KnobPath, config: KnobConfig, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, pa_volume_interface: Arc) { let mut entity_state: Option> = None; @@ -299,3 +262,33 @@ async fn manage_knob(path: KnobPath, config: KnobConfig, mut events: broadcast:: } } } + +pub struct Handler { + events_sender: broadcast::Sender<(KnobPath, KnobEvent)>, + #[allow(unused)] + runtime: tokio::runtime::Runtime, +} + +impl Handler { + pub fn new(data: InitialHandlerMessage<(), KnobConfig>) -> Result { + let (events_sender, _) = broadcast::channel::<(KnobPath, KnobEvent)>(5); + let pa_volume_interface = Arc::new(PaVolumeInterface::spawn_thread("deckster handler".to_owned())); + + let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).build().unwrap(); + + for (path, config) in data.knob_configs { + let events_receiver = events_sender.subscribe(); + runtime.spawn(manage_knob(path, config, events_receiver, Arc::clone(&pa_volume_interface))); + } + + Ok(Handler { events_sender, runtime }) + } +} + +impl DecksterHandler for Handler { + fn handle(&mut self, event: HandlerEvent) { + if let HandlerEvent::Knob { path, event } = event { + self.events_sender.send((path, event)).unwrap(); + } + } +} diff --git a/handlers/playerctl/src/handler.rs b/handlers/playerctl/src/handler.rs index 7729716..ab68620 100644 --- a/handlers/playerctl/src/handler.rs +++ b/handlers/playerctl/src/handler.rs @@ -9,24 +9,34 @@ use std::time::Duration; use log::{error, warn}; use once_cell::sync::Lazy; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tokio::sync::broadcast; use tokio::sync::broadcast::error::RecvError; -use deckster_mode::shared::handler_communication::{HandlerCommand, HandlerEvent, InitialHandlerMessage, KeyEvent}; +use deckster_mode::shared::handler_communication::{HandlerCommand, HandlerEvent, HandlerInitializationError, InitialHandlerMessage, KeyEvent}; use deckster_mode::shared::path::KeyPath; use deckster_mode::shared::state::KeyStyleByStateMap; use deckster_mode::{send_command, DecksterHandler}; -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "mode", rename_all = "kebab-case")] +pub enum KeyConfig { + Play(ButtonConfig), + Pause(ButtonConfig), + PlayPause(ButtonConfig), + Previous(ButtonConfig), + Next(ButtonConfig), + Shuffle(ShuffleConfig), + Loop(LoopConfig), +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ButtonConfig { #[serde(default)] pub style: KeyStyleByStateMap, - pub command: ButtonCommand, } -#[derive(Debug, Eq, PartialEq, Deserialize)] -#[serde(rename_all = "kebab-case")] +#[derive(Debug, Eq, PartialEq)] pub enum ButtonCommand { PlayPause, Play, @@ -35,7 +45,7 @@ pub enum ButtonCommand { Next, } -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ButtonState { Inactive, @@ -43,13 +53,13 @@ pub enum ButtonState { Paused, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ShuffleConfig { #[serde(default)] pub style: KeyStyleByStateMap, } -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ShuffleState { Inactive, @@ -57,13 +67,13 @@ pub enum ShuffleState { On, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct LoopConfig { #[serde(default)] pub style: KeyStyleByStateMap, } -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)] +#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LoopState { Inactive, @@ -163,11 +173,11 @@ static STATE_WATCHER_LOOP: Lazy> = Lazy::new(|| }) }); -pub async fn handle_button(path: KeyPath, config: Arc, mut events: broadcast::Receiver) { +pub async fn manage_button(path: KeyPath, config: ButtonConfig, mut events: broadcast::Receiver<(KeyPath, KeyEvent)>, command: ButtonCommand) { let mut is_active = false; let mut state = STATE_WATCHER_PLAYING.subscribe_to_state(); - let command = match config.command { + let command = match command { ButtonCommand::PlayPause => "play-pause", ButtonCommand::Play => "play", ButtonCommand::Pause => "pause", @@ -192,7 +202,11 @@ pub async fn handle_button(path: KeyPath, config: Arc, mut events: } } - Ok(event) = events.recv() => { + Ok((p, event)) = events.recv() => { + if p != path { + continue + } + if event == KeyEvent::Press && is_active { let status = std::process::Command::new("playerctl").arg(command).status().unwrap(); @@ -205,7 +219,7 @@ pub async fn handle_button(path: KeyPath, config: Arc, mut events: } } -pub async fn handle_shuffle(path: KeyPath, config: Arc, mut events: broadcast::Receiver) { +pub async fn manage_shuffle(path: KeyPath, config: ShuffleConfig, mut events: broadcast::Receiver<(KeyPath, KeyEvent)>) { let mut state = STATE_WATCHER_SHUFFLE.subscribe_to_state(); loop { @@ -223,7 +237,11 @@ pub async fn handle_shuffle(path: KeyPath, config: Arc, mut event } } - Ok(event) = events.recv() => { + Ok((p, event)) = events.recv() => { + if p != path { + continue + } + if event == KeyEvent::Press { let current = *STATE_WATCHER_SHUFFLE.state.read().unwrap(); let new = match current { @@ -242,7 +260,7 @@ pub async fn handle_shuffle(path: KeyPath, config: Arc, mut event } } -pub async fn handle_loop(path: KeyPath, config: Arc, mut events: broadcast::Receiver) { +pub async fn manage_loop(path: KeyPath, config: LoopConfig, mut events: broadcast::Receiver<(KeyPath, KeyEvent)>) { let mut state = STATE_WATCHER_LOOP.subscribe_to_state(); loop { @@ -260,7 +278,11 @@ pub async fn handle_loop(path: KeyPath, config: Arc, mut events: bro } } - Ok(event) = events.recv() => { + Ok((p, event)) = events.recv() => { + if p != path { + continue + } + if event == KeyEvent::Press { let current = *STATE_WATCHER_LOOP.state.read().unwrap(); let new = match current { @@ -287,22 +309,23 @@ pub struct Handler { } impl Handler { - pub fn new(data: InitialHandlerMessage<()>) -> Result { - let (events_sender, _) = broadcast::channel::<(KnobPath, KnobEvent)>(5); - let pa_volume_interface = Arc::new(PaVolumeInterface::spawn_thread("deckster handler".to_owned())); + pub fn new(data: InitialHandlerMessage) -> Result { + let (events_sender, _) = broadcast::channel::<(KeyPath, KeyEvent)>(5); let runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).build().unwrap(); - for (path, (mode, config)) in data.knob_configs { - if !mode.is_empty() { - return Err(HandlerInitializationError::InvalidModeString { - message: "No mode string allowed.".into(), - }); - } + for (path, config) in data.key_configs { + let events = events_sender.subscribe(); - let events_receiver = events_sender.subscribe(); - let a = Arc::clone(&pa_volume_interface); - runtime.spawn(manage_knob(path, config, events_receiver, a)); + match config { + KeyConfig::Loop(config) => runtime.spawn(manage_loop(path, config, events)), + KeyConfig::Shuffle(config) => runtime.spawn(manage_shuffle(path, config, events)), + KeyConfig::Previous(config) => runtime.spawn(manage_button(path, config, events, ButtonCommand::Previous)), + KeyConfig::Next(config) => runtime.spawn(manage_button(path, config, events, ButtonCommand::Next)), + KeyConfig::Pause(config) => runtime.spawn(manage_button(path, config, events, ButtonCommand::Pause)), + KeyConfig::Play(config) => runtime.spawn(manage_button(path, config, events, ButtonCommand::Play)), + KeyConfig::PlayPause(config) => runtime.spawn(manage_button(path, config, events, ButtonCommand::PlayPause)), + }; } Ok(Handler { events_sender, runtime }) @@ -311,7 +334,7 @@ impl Handler { impl DecksterHandler for Handler { fn handle(&mut self, event: HandlerEvent) { - if let HandlerEvent::Knob { path, event } = event { + if let HandlerEvent::Key { path, event } = event { self.events_sender.send((path, event)).unwrap(); } } diff --git a/handlers/playerctl/src/main.rs b/handlers/playerctl/src/main.rs index ffc9844..243ec04 100644 --- a/handlers/playerctl/src/main.rs +++ b/handlers/playerctl/src/main.rs @@ -1,6 +1,8 @@ use clap::Parser; use color_eyre::Result; +use crate::handler::Handler; + mod handler; #[derive(Debug, Parser)] diff --git a/src/handler_host/mod.rs b/src/handler_host/mod.rs index 57b592c..e57ae67 100644 --- a/src/handler_host/mod.rs +++ b/src/handler_host/mod.rs @@ -17,7 +17,6 @@ use deckster_shared::path::{KeyPath, KnobPath}; pub struct KeyOrKnobConfig { pub handler_name: Box, - pub mode_string: Box, pub handler_config: Arc, } @@ -62,7 +61,7 @@ pub async fn start( .iter() .filter_map(|(path, c)| { if *c.handler_name == handler_name { - Some((path.clone(), (c.mode_string.clone(), Arc::clone(&c.handler_config)))) + Some((path.clone(), Arc::clone(&c.handler_config))) } else { None } @@ -72,7 +71,7 @@ pub async fn start( .iter() .filter_map(|(path, c)| { if *c.handler_name == handler_name { - Some((path.clone(), (c.mode_string.clone(), Arc::clone(&c.handler_config)))) + Some((path.clone(), Arc::clone(&c.handler_config))) } else { None } diff --git a/src/runner/mod.rs b/src/runner/mod.rs index a2ad1cc..8805b13 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -54,20 +54,13 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> { .iter() .flat_map(|(page_id, p)| { p.keys.iter().map(|(position, k)| { - let (handler_name, mode_string) = k - .handler - .split_once(' ') - .map(|(a, b)| (a.into(), b.into())) - .unwrap_or_else(|| (k.handler.as_str().into(), "".into())); - ( KeyPath { page_id: page_id.clone(), position: *position, }, KeyOrKnobConfig { - handler_name, - mode_string, + handler_name: k.handler.as_str().into(), handler_config: Arc::clone(&k.config), }, ) @@ -84,20 +77,13 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> { return None; } - let (handler_name, mode_string) = k - .handler - .split_once(' ') - .map(|(a, b)| (a.into(), b.into())) - .unwrap_or_else(|| (k.handler.as_str().into(), "".into())); - Some(( KnobPath { page_id: page_id.clone(), position, }, KeyOrKnobConfig { - handler_name, - mode_string, + handler_name: k.handler.as_str().into(), handler_config: Arc::clone(&k.config), }, )) @@ -120,7 +106,7 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> { let device = available_device.connect().wrap_err("Connecting to the device failed.")?; info!("Connected."); - device.set_brightness(0.1); + device.set_brightness(0.5); device.vibrate(VibrationPattern::RiseFall); let io_worker_context = IoWorkerContext::create(config_directory, Arc::clone(&config), device, commands_sender.clone(), events_sender);