commit
This commit is contained in:
parent
6b4ea3f4ae
commit
fa8c988705
6 changed files with 95 additions and 66 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -384,6 +384,7 @@ dependencies = [
|
||||||
"flume",
|
"flume",
|
||||||
"humantime-serde",
|
"humantime-serde",
|
||||||
"is_executable",
|
"is_executable",
|
||||||
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"loupedeck_serial",
|
"loupedeck_serial",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -422,7 +423,6 @@ dependencies = [
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
"enum-ordinalize",
|
"enum-ordinalize",
|
||||||
"im",
|
|
||||||
"parse-display 0.8.2",
|
"parse-display 0.8.2",
|
||||||
"rgb",
|
"rgb",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -789,7 +789,6 @@ dependencies = [
|
||||||
"bitmaps",
|
"bitmaps",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"rand_xoshiro",
|
"rand_xoshiro",
|
||||||
"serde",
|
|
||||||
"sized-chunks",
|
"sized-chunks",
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
@ -848,6 +847,15 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
|
|
@ -34,6 +34,7 @@ walkdir = "2.4.0"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
is_executable = "1.0.1"
|
is_executable = "1.0.1"
|
||||||
rumqttc = "0.23.0"
|
rumqttc = "0.23.0"
|
||||||
|
itertools = "0.12.1"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|
|
@ -11,5 +11,4 @@ derive_more = "0.99.17"
|
||||||
rgb = "0.8.37"
|
rgb = "0.8.37"
|
||||||
enum-ordinalize = "4.3.0"
|
enum-ordinalize = "4.3.0"
|
||||||
enum-map = "3.0.0-beta.2"
|
enum-map = "3.0.0-beta.2"
|
||||||
im = { version = "15.1.0", features = ["serde"] }
|
|
||||||
parse-display = "0.8.2"
|
parse-display = "0.8.2"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -55,8 +57,8 @@ pub enum HandlerCommand {
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct InitialHandlerMessage<KeyConfig: Clone, KnobConfig: Clone> {
|
pub struct InitialHandlerMessage<KeyConfig: Clone, KnobConfig: Clone> {
|
||||||
pub key_configs: im::HashMap<KeyPath, KeyConfig>,
|
pub key_configs: HashMap<KeyPath, KeyConfig>,
|
||||||
pub knob_configs: im::HashMap<KnobPath, KnobConfig>,
|
pub knob_configs: HashMap<KnobPath, KnobConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
@ -35,19 +35,19 @@ config.mode = "loop"
|
||||||
config.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
config.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
||||||
config.style.all.icon = "@fad/repeat[color=#58fc11]"
|
config.style.all.icon = "@fad/repeat[color=#58fc11]"
|
||||||
|
|
||||||
[keys.3x3]
|
#[keys.3x3]
|
||||||
icon = "@ph/timer[color=#ff0000]"
|
#icon = "@ph/timer[color=#ff0000]"
|
||||||
|
#
|
||||||
|
#handler = "timer"
|
||||||
|
#config.durations = ["60s", "5m", "10m", "15m", "30m"]
|
||||||
|
#config.vibrate_when_finished = true
|
||||||
|
#config.needy = true
|
||||||
|
|
||||||
handler = "timer"
|
#[keys.4x3]
|
||||||
config.durations = ["60s", "5m", "10m", "15m", "30m"]
|
#icon = "@ph/computer-tower"
|
||||||
config.vibrate_when_finished = true
|
#label = "Gaming PC"
|
||||||
config.needy = true
|
#
|
||||||
|
#handler = "home-assistant"
|
||||||
[keys.4x3]
|
#config.mode = "switch"
|
||||||
icon = "@ph/computer-tower"
|
#config.name = "switch.mwin"
|
||||||
label = "Gaming PC"
|
#config.style.on.icon = "@ph/computer-tower[color=#58fc11]"
|
||||||
|
|
||||||
handler = "home-assistant"
|
|
||||||
config.mode = "switch"
|
|
||||||
config.name = "switch.mwin"
|
|
||||||
config.style.on.icon = "@ph/computer-tower[color=#58fc11]"
|
|
|
@ -7,6 +7,8 @@ use std::sync::Arc;
|
||||||
use color_eyre::eyre::{eyre, WrapErr};
|
use color_eyre::eyre::{eyre, WrapErr};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use is_executable::IsExecutable;
|
use is_executable::IsExecutable;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::warn;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::process::{ChildStdin, Command};
|
use tokio::process::{ChildStdin, Command};
|
||||||
|
|
||||||
|
@ -43,10 +45,46 @@ pub async fn start(
|
||||||
|
|
||||||
let mut handler_stdin_by_name: HashMap<Box<str>, ChildStdin> = HashMap::with_capacity(handler_names.len());
|
let mut handler_stdin_by_name: HashMap<Box<str>, ChildStdin> = HashMap::with_capacity(handler_names.len());
|
||||||
|
|
||||||
for handler_name in handler_names {
|
let mut handler_name_by_key_path: HashMap<KeyPath, Box<str>> = HashMap::new();
|
||||||
let handler_name = handler_name.into_string().map_err(|_| eyre!("Command names must be valid Unicode."))?;
|
let mut handler_config_by_key_path_by_handler_name: HashMap<Box<str>, HashMap<KeyPath, Arc<toml::Table>>> = 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<KnobPath, Box<str>> = HashMap::new();
|
||||||
|
let mut handler_config_by_knob_path_by_handler_name: HashMap<Box<str>, HashMap<KnobPath, Arc<toml::Table>>> = 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")
|
.arg("deckster-run")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(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 stdout_lines = BufReader::new(command.stdout.take().unwrap()).lines();
|
||||||
let mut stdin = command.stdin.unwrap();
|
let mut stdin = command.stdin.unwrap();
|
||||||
|
|
||||||
let initial_handler_message = InitialHandlerMessage {
|
let initial_handler_message = InitialHandlerMessage { key_configs, knob_configs };
|
||||||
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 serialized_message = serde_json::to_string(&initial_handler_message).unwrap().into_boxed_str().into_boxed_bytes();
|
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 let HandlerInitializationError::InvalidConfig { supports_keys, supports_knobs, .. } = error {
|
||||||
if !supports_keys && !initial_handler_message.key_configs.is_empty() {
|
if !supports_keys && !initial_handler_message.key_configs.is_empty() {
|
||||||
return Err(eyre!(
|
return Err(eyre!(
|
||||||
"The '{handler_name}' handler does not support keys, but these keys tried to use it: {:?}",
|
"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::<String>()
|
initial_handler_message.key_configs.keys().map(|k| k.to_string()).join(", ")
|
||||||
));
|
));
|
||||||
} else if !supports_knobs && !initial_handler_message.knob_configs.is_empty() {
|
} else if !supports_knobs && !initial_handler_message.knob_configs.is_empty() {
|
||||||
return Err(eyre!(
|
return Err(eyre!(
|
||||||
"The '{handler_name}' handler does not support knobs, but these knobs tried to use it: {:?}",
|
"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::<String>()
|
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 {
|
tokio::spawn(async move {
|
||||||
while let Ok(event) = events_receiver.recv().await {
|
while let Ok(event) = events_receiver.recv().await {
|
||||||
let config = match &event {
|
let handler_name = match &event {
|
||||||
HandlerEvent::Key { path, .. } => {
|
HandlerEvent::Key { path, .. } => handler_name_by_key_path.get(path).expect("every key must have a handler"),
|
||||||
if let Some(config) = key_configs.get(path) {
|
HandlerEvent::Knob { path, .. } => handler_name_by_knob_path.get(path).expect("every knob must have a handler"),
|
||||||
config
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandlerEvent::Knob { path, .. } => {
|
|
||||||
if let Some(config) = knob_configs.get(path) {
|
|
||||||
config
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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();
|
let serialized_event = serde_json::to_string(&event).unwrap().into_boxed_str().into_boxed_bytes();
|
||||||
|
|
||||||
handler_stdin.write_all(&serialized_event).await.unwrap();
|
handler_stdin.write_all(&serialized_event).await.unwrap();
|
||||||
|
|
Loading…
Add table
Reference in a new issue