From fa8c9887056c176f2eef230f7adc98d2fc5355ea Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Tue, 20 Feb 2024 16:05:32 +0100 Subject: [PATCH] commit --- Cargo.lock | 12 +- Cargo.toml | 1 + crates/deckster_shared/Cargo.toml | 1 - .../src/handler_communication.rs | 6 +- examples/full/key-pages/default.toml | 30 ++--- src/handler_host/mod.rs | 111 ++++++++++-------- 6 files changed, 95 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e980bb..6778dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,7 @@ dependencies = [ "flume", "humantime-serde", "is_executable", + "itertools", "log", "loupedeck_serial", "once_cell", @@ -422,7 +423,6 @@ dependencies = [ "derive_more", "enum-map", "enum-ordinalize", - "im", "parse-display 0.8.2", "rgb", "serde", @@ -789,7 +789,6 @@ dependencies = [ "bitmaps", "rand_core", "rand_xoshiro", - "serde", "sized-chunks", "typenum", "version_check", @@ -848,6 +847,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" diff --git a/Cargo.toml b/Cargo.toml index 32085c8..befbe7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ walkdir = "2.4.0" once_cell = "1.19.0" is_executable = "1.0.1" rumqttc = "0.23.0" +itertools = "0.12.1" [workspace] members = [ diff --git a/crates/deckster_shared/Cargo.toml b/crates/deckster_shared/Cargo.toml index 84032f2..6aba6b2 100644 --- a/crates/deckster_shared/Cargo.toml +++ b/crates/deckster_shared/Cargo.toml @@ -11,5 +11,4 @@ derive_more = "0.99.17" rgb = "0.8.37" enum-ordinalize = "4.3.0" enum-map = "3.0.0-beta.2" -im = { version = "15.1.0", features = ["serde"] } parse-display = "0.8.2" diff --git a/crates/deckster_shared/src/handler_communication.rs b/crates/deckster_shared/src/handler_communication.rs index df64f7f..f83313a 100644 --- a/crates/deckster_shared/src/handler_communication.rs +++ b/crates/deckster_shared/src/handler_communication.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -55,8 +57,8 @@ pub enum HandlerCommand { #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct InitialHandlerMessage { - pub key_configs: im::HashMap, - pub knob_configs: im::HashMap, + pub key_configs: HashMap, + pub knob_configs: HashMap, } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] diff --git a/examples/full/key-pages/default.toml b/examples/full/key-pages/default.toml index 523f825..8f9ef3e 100644 --- a/examples/full/key-pages/default.toml +++ b/examples/full/key-pages/default.toml @@ -35,19 +35,19 @@ config.mode = "loop" config.style.single.icon = "@fad/repeat-one[color=#58fc11]" config.style.all.icon = "@fad/repeat[color=#58fc11]" -[keys.3x3] -icon = "@ph/timer[color=#ff0000]" +#[keys.3x3] +#icon = "@ph/timer[color=#ff0000]" +# +#handler = "timer" +#config.durations = ["60s", "5m", "10m", "15m", "30m"] +#config.vibrate_when_finished = true +#config.needy = true -handler = "timer" -config.durations = ["60s", "5m", "10m", "15m", "30m"] -config.vibrate_when_finished = true -config.needy = true - -[keys.4x3] -icon = "@ph/computer-tower" -label = "Gaming PC" - -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 +#[keys.4x3] +#icon = "@ph/computer-tower" +#label = "Gaming PC" +# +#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/src/handler_host/mod.rs b/src/handler_host/mod.rs index b589858..0a529eb 100644 --- a/src/handler_host/mod.rs +++ b/src/handler_host/mod.rs @@ -7,6 +7,8 @@ use std::sync::Arc; use color_eyre::eyre::{eyre, WrapErr}; use color_eyre::Result; use is_executable::IsExecutable; +use itertools::Itertools; +use log::warn; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::process::{ChildStdin, Command}; @@ -43,10 +45,46 @@ pub async fn start( let mut handler_stdin_by_name: HashMap, ChildStdin> = HashMap::with_capacity(handler_names.len()); - for handler_name in handler_names { - let handler_name = handler_name.into_string().map_err(|_| eyre!("Command names must be valid Unicode."))?; + let mut handler_name_by_key_path: HashMap> = HashMap::new(); + let mut handler_config_by_key_path_by_handler_name: HashMap, HashMap>> = HashMap::new(); - let mut command = Command::new(handlers_directory.join(&handler_name)) + for (path, config) in key_configs { + handler_name_by_key_path.insert(path.clone(), config.handler_name.clone()); + handler_config_by_key_path_by_handler_name + .entry(config.handler_name) + .or_default() + .insert(path, Arc::clone(&config.handler_config)); + } + + let mut handler_name_by_knob_path: HashMap> = HashMap::new(); + let mut handler_config_by_knob_path_by_handler_name: HashMap, HashMap>> = HashMap::new(); + + for (path, config) in knob_configs { + handler_name_by_knob_path.insert(path.clone(), config.handler_name.clone()); + handler_config_by_knob_path_by_handler_name + .entry(config.handler_name) + .or_default() + .insert(path, Arc::clone(&config.handler_config)); + } + + for handler_name in handler_names { + let handler_name = handler_name + .into_string() + .map_err(|_| eyre!("Command names must be valid Unicode."))? + .into_boxed_str(); + + let (key_configs, knob_configs) = match ( + handler_config_by_key_path_by_handler_name.remove(&handler_name), + handler_config_by_knob_path_by_handler_name.remove(&handler_name), + ) { + (None, None) => { + warn!("Handler '{handler_name}' is not used by any key or knob."); + continue; + } + (a, b) => (a.unwrap_or_default(), b.unwrap_or_default()), + }; + + let mut command = Command::new(handlers_directory.join(handler_name.to_string())) .arg("deckster-run") .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -56,28 +94,7 @@ pub async fn start( let mut stdout_lines = BufReader::new(command.stdout.take().unwrap()).lines(); let mut stdin = command.stdin.unwrap(); - let initial_handler_message = InitialHandlerMessage { - key_configs: key_configs - .iter() - .filter_map(|(path, c)| { - if *c.handler_name == handler_name { - Some((path.clone(), Arc::clone(&c.handler_config))) - } else { - None - } - }) - .collect(), - knob_configs: knob_configs - .iter() - .filter_map(|(path, c)| { - if *c.handler_name == handler_name { - Some((path.clone(), Arc::clone(&c.handler_config))) - } else { - None - } - }) - .collect(), - }; + let initial_handler_message = InitialHandlerMessage { key_configs, knob_configs }; let serialized_message = serde_json::to_string(&initial_handler_message).unwrap().into_boxed_str().into_boxed_bytes(); @@ -93,13 +110,13 @@ pub async fn start( if let HandlerInitializationError::InvalidConfig { supports_keys, supports_knobs, .. } = error { if !supports_keys && !initial_handler_message.key_configs.is_empty() { return Err(eyre!( - "The '{handler_name}' handler does not support keys, but these keys tried to use it: {:?}", - initial_handler_message.key_configs.keys().map(|k| k.to_string()).collect::() + "The '{handler_name}' handler does not support keys, but these keys tried to use it: {}", + initial_handler_message.key_configs.keys().map(|k| k.to_string()).join(", ") )); } else if !supports_knobs && !initial_handler_message.knob_configs.is_empty() { return Err(eyre!( - "The '{handler_name}' handler does not support knobs, but these knobs tried to use it: {:?}", - initial_handler_message.knob_configs.keys().map(|k| k.to_string()).collect::() + "The '{handler_name}' handler does not support knobs, but these knobs tried to use it: {}", + initial_handler_message.knob_configs.keys().map(|k| k.to_string()).join(", ") )); } }; @@ -121,29 +138,31 @@ pub async fn start( } }); - handler_stdin_by_name.insert(handler_name.into_boxed_str(), stdin); + handler_stdin_by_name.insert(handler_name, stdin); + } + + if let Some((handler_name, config_by_key_path)) = handler_config_by_key_path_by_handler_name.drain().next() { + return Err(eyre!( + "There is no executable file named '{handler_name}' in the handlers directory but these keys have it set as their handler: {}", + config_by_key_path.keys().join(", ") + )); + } + + if let Some((handler_name, config_by_knob_path)) = handler_config_by_knob_path_by_handler_name.drain().next() { + return Err(eyre!( + "There is no executable file named '{handler_name}' in the handlers directory but these knobs have it set as their handler: {}", + config_by_knob_path.keys().join(", ") + )); } tokio::spawn(async move { while let Ok(event) = events_receiver.recv().await { - let config = match &event { - HandlerEvent::Key { path, .. } => { - if let Some(config) = key_configs.get(path) { - config - } else { - continue; - } - } - HandlerEvent::Knob { path, .. } => { - if let Some(config) = knob_configs.get(path) { - config - } else { - continue; - } - } + let handler_name = match &event { + HandlerEvent::Key { path, .. } => handler_name_by_key_path.get(path).expect("every key must have a handler"), + HandlerEvent::Knob { path, .. } => handler_name_by_knob_path.get(path).expect("every knob must have a handler"), }; - let handler_stdin = handler_stdin_by_name.get_mut(&config.handler_name).expect("was already checked above"); + let handler_stdin = handler_stdin_by_name.get_mut(handler_name).expect("was already checked above"); let serialized_event = serde_json::to_string(&event).unwrap().into_boxed_str().into_boxed_bytes(); handler_stdin.write_all(&serialized_event).await.unwrap();