commit
This commit is contained in:
parent
2719b7afb8
commit
efb5385971
21 changed files with 200 additions and 84 deletions
|
@ -20,7 +20,7 @@ loupedeck_serial = { path = "../loupedeck_serial" }
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
resvg = "0.37.0"
|
resvg = "0.37.0"
|
||||||
rgb = "0.8.37"
|
rgb = "0.8.37"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive", "rc"] }
|
||||||
serde_regex = "1.1.0"
|
serde_regex = "1.1.0"
|
||||||
serde_with = "3.4.0"
|
serde_with = "3.4.0"
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.52"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[keys.1x1]
|
[keys.1x1]
|
||||||
icon = "@apps/spotify[scale=2.0|invert]"
|
icon = "@ph/play[alpha=0.4]"
|
||||||
mode.vibrate.pattern = "low"
|
mode.vibrate.pattern = "low"
|
||||||
mode.media__play_pause.icon.paused = "@ph/play"
|
mode.media__play_pause.style.paused.icon = "@ph/play"
|
||||||
mode.media__play_pause.icon.playing = "@ph/pause"
|
mode.media__play_pause.style.playing.icon = "@ph/pause"
|
||||||
|
|
||||||
[keys.1x2]
|
[keys.1x2]
|
||||||
icon = "@fad/shuffle[alpha=0.6]"
|
icon = "@fad/shuffle[alpha=0.6]"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use color_eyre::eyre::WrapErr;
|
use color_eyre::eyre::WrapErr;
|
||||||
|
@ -11,6 +12,7 @@ use crate::model::config::WithFallbackId;
|
||||||
|
|
||||||
mod icons;
|
mod icons;
|
||||||
mod model;
|
mod model;
|
||||||
|
mod modes;
|
||||||
mod runner;
|
mod runner;
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -44,7 +46,7 @@ pub async fn main() -> Result<()> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| model::key_page::Page {
|
.map(|p| model::key_page::Page {
|
||||||
id: p.inner.id.clone().unwrap_or(p.fallback_id),
|
id: p.inner.id.clone().unwrap_or(p.fallback_id),
|
||||||
keys: p.inner.keys,
|
keys: p.inner.keys.into_iter().map(|(p, k)| (p, Arc::new(k))).collect(),
|
||||||
scrolling: p.inner.scrolling,
|
scrolling: p.inner.scrolling,
|
||||||
})
|
})
|
||||||
.map(|p| (p.id.clone(), p))
|
.map(|p| (p.id.clone(), p))
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::model::key_page::StyleByStateMap;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct PlayPauseConfig {
|
|
||||||
#[serde(default)]
|
|
||||||
pub style: StyleByStateMap<PlayPauseState>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub action: PlayPauseAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum PlayPauseAction {
|
|
||||||
#[default]
|
|
||||||
Toggle,
|
|
||||||
Play,
|
|
||||||
Pause,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct PreviousAndNextConfig {
|
|
||||||
#[serde(default)]
|
|
||||||
pub style: StyleByStateMap<PreviousAndNextState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum PlayPauseState {
|
|
||||||
Inactive,
|
|
||||||
Playing,
|
|
||||||
Paused,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum PreviousAndNextState {
|
|
||||||
Inactive,
|
|
||||||
Active,
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod home_assistant;
|
|
||||||
pub mod media;
|
|
||||||
pub mod spotify;
|
|
||||||
pub mod timer;
|
|
||||||
pub mod vibrate;
|
|
|
@ -1,12 +1,13 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::model::geometry::UIntVec2;
|
use crate::model::geometry::UIntVec2;
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
use crate::model::icon_descriptor::IconDescriptor;
|
||||||
use crate::model::key_modes;
|
|
||||||
use crate::model::position::{KeyPosition, KnobPosition};
|
use crate::model::position::{KeyPosition, KnobPosition};
|
||||||
use crate::model::rgb::RGB8WithOptionalA;
|
use crate::model::rgb::RGB8WithOptionalA;
|
||||||
|
use crate::modes;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
|
@ -19,7 +20,7 @@ pub struct File {
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub scrolling: Option<ScrollingConfig>,
|
pub scrolling: Option<ScrollingConfig>,
|
||||||
pub keys: HashMap<KeyPosition, KeyConfig>,
|
pub keys: HashMap<KeyPosition, Arc<KeyConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -74,14 +75,14 @@ pub struct KeyConfig {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct KeyModes {
|
pub struct KeyModes {
|
||||||
pub vibrate: Option<key_modes::vibrate::Config>,
|
pub vibrate: Option<Arc<modes::key::vibrate::Config>>,
|
||||||
pub media__play_pause: Option<key_modes::media::PlayPauseConfig>,
|
pub media__play_pause: Option<Arc<modes::key::media::PlayPauseConfig>>,
|
||||||
pub media__previous: Option<key_modes::media::PreviousAndNextConfig>,
|
pub media__previous: Option<Arc<modes::key::media::PreviousAndNextConfig>>,
|
||||||
pub media__next: Option<key_modes::media::PreviousAndNextConfig>,
|
pub media__next: Option<Arc<modes::key::media::PreviousAndNextConfig>>,
|
||||||
pub spotify__shuffle: Option<key_modes::spotify::ShuffleConfig>,
|
pub spotify__shuffle: Option<Arc<modes::key::spotify::ShuffleConfig>>,
|
||||||
pub spotify__repeat: Option<key_modes::spotify::RepeatConfig>,
|
pub spotify__repeat: Option<Arc<modes::key::spotify::RepeatConfig>>,
|
||||||
pub home_assistant__switch: Option<key_modes::home_assistant::SwitchConfig>,
|
pub home_assistant__switch: Option<Arc<modes::key::home_assistant::SwitchConfig>>,
|
||||||
pub home_assistant__button: Option<key_modes::home_assistant::ButtonConfig>,
|
pub home_assistant__button: Option<Arc<modes::key::home_assistant::ButtonConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StyleByStateMap<State> = HashMap<State, KeyStyle>;
|
pub type StyleByStateMap<State> = HashMap<State, KeyStyle>;
|
||||||
|
|
|
@ -4,9 +4,9 @@ use enum_map::EnumMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
use crate::model::icon_descriptor::IconDescriptor;
|
||||||
use crate::model::knob_modes;
|
|
||||||
use crate::model::position::KnobPosition;
|
use crate::model::position::KnobPosition;
|
||||||
use crate::model::rgb::RGB8WithOptionalA;
|
use crate::model::rgb::RGB8WithOptionalA;
|
||||||
|
use crate::modes;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
|
@ -55,7 +55,7 @@ pub struct KnobStyle {
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct KnobModes {
|
pub struct KnobModes {
|
||||||
pub audio_volume: Option<knob_modes::audio_volume::Config>,
|
pub audio_volume: Option<modes::knob::audio_volume::Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StyleByStateMap<State> = HashMap<State, KnobStyle>;
|
pub type StyleByStateMap<State> = HashMap<State, KnobStyle>;
|
||||||
|
|
|
@ -2,9 +2,7 @@ pub mod config;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod icon_descriptor;
|
pub mod icon_descriptor;
|
||||||
pub mod image_filter;
|
pub mod image_filter;
|
||||||
pub mod key_modes;
|
|
||||||
pub mod key_page;
|
pub mod key_page;
|
||||||
pub mod knob_modes;
|
|
||||||
pub mod knob_page;
|
pub mod knob_page;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
pub mod rgb;
|
pub mod rgb;
|
||||||
|
|
|
@ -49,6 +49,12 @@ pub struct KeyPath {
|
||||||
pub position: KeyPosition,
|
pub position: KeyPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for KeyPath {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}/{}", &self.page_id, &self.position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum KnobPosition {
|
pub enum KnobPosition {
|
||||||
|
|
68
deckster/src/modes/key/media.rs
Normal file
68
deckster/src/modes/key/media.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use flume::Sender;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
|
use crate::model::key_page::StyleByStateMap;
|
||||||
|
use crate::model::position::KeyPath;
|
||||||
|
use crate::modes::key::KeyEvent;
|
||||||
|
use crate::runner::state::StateChangeCommand;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PlayPauseConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub style: StyleByStateMap<PlayPauseState>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub action: PlayPauseAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Eq, PartialEq, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum PlayPauseAction {
|
||||||
|
#[default]
|
||||||
|
Toggle,
|
||||||
|
Play,
|
||||||
|
Pause,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PreviousAndNextConfig {
|
||||||
|
#[serde(default)]
|
||||||
|
pub style: StyleByStateMap<PreviousAndNextState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum PlayPauseState {
|
||||||
|
Inactive,
|
||||||
|
Playing,
|
||||||
|
Paused,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum PreviousAndNextState {
|
||||||
|
Inactive,
|
||||||
|
Active,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle(path: KeyPath, config: Arc<PlayPauseConfig>, mut events: Receiver<KeyEvent>, commands: Sender<StateChangeCommand>) {
|
||||||
|
let mut state = PlayPauseState::Inactive;
|
||||||
|
|
||||||
|
while let Ok(event) = events.recv().await {
|
||||||
|
if event == KeyEvent::Press {
|
||||||
|
state = match state {
|
||||||
|
PlayPauseState::Playing => PlayPauseState::Paused,
|
||||||
|
_ => PlayPauseState::Playing,
|
||||||
|
};
|
||||||
|
|
||||||
|
commands
|
||||||
|
.send(StateChangeCommand::SetKeyStyle {
|
||||||
|
path: path.clone(),
|
||||||
|
value: config.style.get(&state).cloned(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
deckster/src/modes/key/mod.rs
Normal file
46
deckster/src/modes/key/mod.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use crate::model;
|
||||||
|
use crate::model::position::KeyPath;
|
||||||
|
use crate::runner::state::StateChangeCommand;
|
||||||
|
|
||||||
|
pub mod home_assistant;
|
||||||
|
pub mod media;
|
||||||
|
pub mod spotify;
|
||||||
|
pub mod timer;
|
||||||
|
pub mod vibrate;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum KeyEvent {
|
||||||
|
Press,
|
||||||
|
VisibilityChange { is_visible: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_handlers(
|
||||||
|
keys: impl Iterator<Item = (KeyPath, Arc<model::key_page::KeyConfig>)>,
|
||||||
|
events: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
|
commands: flume::Sender<StateChangeCommand>,
|
||||||
|
) {
|
||||||
|
for (path, config) in keys {
|
||||||
|
let mut events = events.subscribe();
|
||||||
|
let own_events = broadcast::Sender::new(5);
|
||||||
|
|
||||||
|
if let Some(c) = &config.mode.media__play_pause {
|
||||||
|
tokio::spawn(media::handle(path.clone(), Arc::clone(c), own_events.subscribe(), commands.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Ok((p, e)) = events.recv().await {
|
||||||
|
#[allow(clippy::collapsible_if)]
|
||||||
|
if p == path {
|
||||||
|
if own_events.send(e).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
2
deckster/src/modes/mod.rs
Normal file
2
deckster/src/modes/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod key;
|
||||||
|
pub mod knob;
|
|
@ -26,9 +26,10 @@ pub fn render_key(context: &GraphicsContext, key_size: IntSize, state: Option<&K
|
||||||
let mut pixmap = Pixmap::new(key_size.width(), key_size.height()).unwrap();
|
let mut pixmap = Pixmap::new(key_size.width(), key_size.height()).unwrap();
|
||||||
|
|
||||||
if let Some(state) = state {
|
if let Some(state) = state {
|
||||||
let style = state.style.merge_over(&state.base_style);
|
let style = state.style.as_ref().map(|s| s.merge_over(&state.base_style));
|
||||||
|
let style = style.as_ref().unwrap_or(&state.base_style);
|
||||||
|
|
||||||
if let Some(icon) = style.icon {
|
if let Some(icon) = &style.icon {
|
||||||
let filter = if let Some(global_filter) = icon.source.pack_id().and_then(|i| context.global_icon_filter_by_pack_id.get(i)) {
|
let filter = if let Some(global_filter) = icon.source.pack_id().and_then(|i| context.global_icon_filter_by_pack_id.get(i)) {
|
||||||
icon.filter.merge_over(global_filter)
|
icon.filter.merge_over(global_filter)
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,7 +63,7 @@ pub fn render_key(context: &GraphicsContext, key_size: IntSize, state: Option<&K
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(label) = style.label {
|
if let Some(label) = &style.label {
|
||||||
if !label.is_empty() {
|
if !label.is_empty() {
|
||||||
context.label_renderer.borrow_mut().render(&mut pixmap, &label);
|
context.label_renderer.borrow_mut().render(&mut pixmap, &label);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ use color_eyre::eyre::{ContextCompat, WrapErr};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use enum_ordinalize::Ordinalize;
|
use enum_ordinalize::Ordinalize;
|
||||||
use flume::{Receiver, Sender};
|
|
||||||
use log::{debug, info, trace};
|
use log::{debug, info, trace};
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use tiny_skia::IntSize;
|
use tiny_skia::IntSize;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
|
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics};
|
||||||
use loupedeck_serial::commands::VibrationPattern;
|
use loupedeck_serial::commands::VibrationPattern;
|
||||||
|
@ -21,15 +21,15 @@ use loupedeck_serial::events::LoupedeckEvent;
|
||||||
|
|
||||||
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIconsMap};
|
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIconsMap};
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::key_page::KeyStyle;
|
|
||||||
use crate::model::knob_page::KnobStyle;
|
use crate::model::knob_page::KnobStyle;
|
||||||
use crate::model::position::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
|
use crate::model::position::{ButtonPosition, KeyPath, KeyPosition, KnobPath};
|
||||||
|
use crate::modes::key::{start_handlers, KeyEvent};
|
||||||
use crate::runner::graphics::labels::LabelRenderer;
|
use crate::runner::graphics::labels::LabelRenderer;
|
||||||
use crate::runner::graphics::{render_key, GraphicsContext};
|
use crate::runner::graphics::{render_key, GraphicsContext};
|
||||||
use crate::runner::state::{Key, State, StateChangeCommand};
|
use crate::runner::state::{Key, State, StateChangeCommand};
|
||||||
|
|
||||||
mod graphics;
|
mod graphics;
|
||||||
mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub async fn start(config_directory: &Path, config: model::config::Config) -> Result<()> {
|
pub async fn start(config_directory: &Path, config: model::config::Config) -> Result<()> {
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
|
@ -40,11 +40,9 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
|
|
||||||
info!("Connecting to the device…");
|
info!("Connecting to the device…");
|
||||||
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
|
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
|
||||||
|
|
||||||
info!("Connected");
|
info!("Connected");
|
||||||
|
|
||||||
let key_grid = &device.characteristics().key_grid;
|
let key_grid = &device.characteristics().key_grid;
|
||||||
|
|
||||||
let used_icon_descriptors = get_used_icon_descriptors(&config);
|
let used_icon_descriptors = get_used_icon_descriptors(&config);
|
||||||
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
@ -55,8 +53,8 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
device.set_brightness(0.5);
|
device.set_brightness(0.5);
|
||||||
device.vibrate(VibrationPattern::RiseFall);
|
device.vibrate(VibrationPattern::RiseFall);
|
||||||
|
|
||||||
let events_receiver = device.events();
|
|
||||||
let (commands_sender, commands_receiver) = flume::bounded::<StateChangeCommand>(20);
|
let (commands_sender, commands_receiver) = flume::bounded::<StateChangeCommand>(20);
|
||||||
|
let key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)> = broadcast::Sender::new(20);
|
||||||
|
|
||||||
commands_sender
|
commands_sender
|
||||||
.send(StateChangeCommand::SetActivePages {
|
.send(StateChangeCommand::SetActivePages {
|
||||||
|
@ -67,11 +65,37 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
|
|
||||||
let cloned_config = Arc::clone(&config);
|
let cloned_config = Arc::clone(&config);
|
||||||
let cloned_commands_sender = commands_sender.clone();
|
let cloned_commands_sender = commands_sender.clone();
|
||||||
|
let cloned_key_events_sender = key_events_sender.clone();
|
||||||
let io_worker_thread = thread::Builder::new()
|
let io_worker_thread = thread::Builder::new()
|
||||||
.name("deckster IO worker".to_owned())
|
.name("deckster IO worker".to_owned())
|
||||||
.spawn(move || do_io_work(cloned_config, icons, device, events_receiver, cloned_commands_sender, commands_receiver))
|
.spawn(move || {
|
||||||
|
do_io_work(
|
||||||
|
cloned_config,
|
||||||
|
icons,
|
||||||
|
device,
|
||||||
|
cloned_key_events_sender,
|
||||||
|
cloned_commands_sender,
|
||||||
|
commands_receiver,
|
||||||
|
)
|
||||||
|
})
|
||||||
.wrap_err("Could not spawn the worker thread")?;
|
.wrap_err("Could not spawn the worker thread")?;
|
||||||
|
|
||||||
|
start_handlers(
|
||||||
|
config.key_pages_by_id.iter().flat_map(|(page_id, page)| {
|
||||||
|
page.keys.iter().map(|(position, key)| {
|
||||||
|
(
|
||||||
|
KeyPath {
|
||||||
|
page_id: page_id.clone(),
|
||||||
|
position: *position,
|
||||||
|
},
|
||||||
|
Arc::clone(key),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
key_events_sender,
|
||||||
|
commands_sender,
|
||||||
|
);
|
||||||
|
|
||||||
info!("Ready.");
|
info!("Ready.");
|
||||||
io_worker_thread.join().unwrap();
|
io_worker_thread.join().unwrap();
|
||||||
|
|
||||||
|
@ -93,7 +117,7 @@ fn create_state(config: &model::config::Config) -> State {
|
||||||
position: *position,
|
position: *position,
|
||||||
},
|
},
|
||||||
base_style: k.base_style.clone(),
|
base_style: k.base_style.clone(),
|
||||||
style: KeyStyle::default(),
|
style: None,
|
||||||
})
|
})
|
||||||
.map(|k| (k.path.position, k))
|
.map(|k| (k.path.position, k))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -147,9 +171,9 @@ fn do_io_work(
|
||||||
config: Arc<model::config::Config>,
|
config: Arc<model::config::Config>,
|
||||||
icons: LoadedIconsMap,
|
icons: LoadedIconsMap,
|
||||||
device: LoupedeckDevice,
|
device: LoupedeckDevice,
|
||||||
events_receiver: Receiver<LoupedeckEvent>,
|
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
commands_sender: Sender<StateChangeCommand>,
|
commands_sender: flume::Sender<StateChangeCommand>,
|
||||||
commands_receiver: Receiver<StateChangeCommand>,
|
commands_receiver: flume::Receiver<StateChangeCommand>,
|
||||||
) {
|
) {
|
||||||
let state = create_state(&config);
|
let state = create_state(&config);
|
||||||
let buffer_endianness = device.characteristics().key_grid.display.endianness;
|
let buffer_endianness = device.characteristics().key_grid.display.endianness;
|
||||||
|
@ -161,6 +185,8 @@ fn do_io_work(
|
||||||
|
|
||||||
let label_renderer = RefCell::new(LabelRenderer::new(config.label_font_family.as_ref()));
|
let label_renderer = RefCell::new(LabelRenderer::new(config.label_font_family.as_ref()));
|
||||||
|
|
||||||
|
let device_events_receiver = device.events();
|
||||||
|
|
||||||
let mut context = IoWorkerContext {
|
let mut context = IoWorkerContext {
|
||||||
config,
|
config,
|
||||||
device,
|
device,
|
||||||
|
@ -175,13 +201,13 @@ fn do_io_work(
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let a = flume::Selector::new()
|
let a = flume::Selector::new()
|
||||||
.recv(&events_receiver, |e| IoWork::Event(e.unwrap()))
|
.recv(&device_events_receiver, |e| IoWork::Event(e.unwrap()))
|
||||||
.recv(&commands_receiver, |c| IoWork::Command(c.unwrap()))
|
.recv(&commands_receiver, |c| IoWork::Command(c.unwrap()))
|
||||||
.wait();
|
.wait();
|
||||||
|
|
||||||
match a {
|
match a {
|
||||||
IoWork::Event(event) => {
|
IoWork::Event(event) => {
|
||||||
if !handle_event(&context, &commands_sender, event) {
|
if !handle_event(&context, &commands_sender, &key_events_sender, event) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,9 +216,19 @@ fn do_io_work(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_event(context: &IoWorkerContext, commands_sender: &Sender<StateChangeCommand>, event: LoupedeckEvent) -> bool {
|
fn handle_event(
|
||||||
|
context: &IoWorkerContext,
|
||||||
|
commands_sender: &flume::Sender<StateChangeCommand>,
|
||||||
|
key_events_sender: &broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
|
event: LoupedeckEvent,
|
||||||
|
) -> bool {
|
||||||
trace!("Handling event: {:?}", &event);
|
trace!("Handling event: {:?}", &event);
|
||||||
|
|
||||||
|
let send_key_event = |path: KeyPath, event: KeyEvent| {
|
||||||
|
trace!("Sending key event ({}): {:?}", &path, &event);
|
||||||
|
key_events_sender.send((path, event)).unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
LoupedeckEvent::Disconnected => return false,
|
LoupedeckEvent::Disconnected => return false,
|
||||||
LoupedeckEvent::ButtonDown { button } => {
|
LoupedeckEvent::ButtonDown { button } => {
|
||||||
|
@ -224,6 +260,8 @@ fn handle_event(context: &IoWorkerContext, commands_sender: &Sender<StateChangeC
|
||||||
page_id: context.state.active_key_page_id.clone(),
|
page_id: context.state.active_key_page_id.clone(),
|
||||||
position,
|
position,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
send_key_event(path, KeyEvent::Press);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ pub struct KnobPage {
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
pub path: KeyPath,
|
pub path: KeyPath,
|
||||||
pub base_style: KeyStyle,
|
pub base_style: KeyStyle,
|
||||||
pub style: KeyStyle,
|
pub style: Option<KeyStyle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -73,5 +73,5 @@ pub struct Knob {
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum StateChangeCommand {
|
pub enum StateChangeCommand {
|
||||||
SetActivePages { key_page_id: String, knob_page_id: String },
|
SetActivePages { key_page_id: String, knob_page_id: String },
|
||||||
SetKeyStyle { path: KeyPath, value: KeyStyle },
|
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue