use crate::error::{Error, Result};
use rdev::Key;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::str;
pub const DEFAULT_CONFIG: &str = "daktilo.toml";
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
#[serde(rename = "sound_preset")]
pub sound_presets: Vec<SoundPreset>,
#[serde(rename = "no_surprises", default)]
pub disable_easter_eggs: bool,
}
impl Config {
pub fn get_default_location() -> Option<PathBuf> {
if let Some(config_dirs) = dirs::config_dir().map(|config_dir| {
vec![
config_dir.join(DEFAULT_CONFIG),
config_dir.join(env!("CARGO_PKG_NAME")).join(DEFAULT_CONFIG),
config_dir.join(env!("CARGO_PKG_NAME")).join("config"),
]
}) {
for config_dir in config_dirs {
if config_dir.exists() {
return Some(config_dir);
}
}
}
None
}
pub fn parse(file: &Path) -> Result<Config> {
let contents = fs::read_to_string(file)?;
let config: Config = toml::from_str(&contents)?;
Ok(config)
}
pub fn select_preset(&self, name: &str) -> Result<SoundPreset> {
if !self.disable_easter_eggs && fastrand::usize(0..1000) == 42 || name == "ak47" {
return Ok(SoundPreset {
name: String::new(),
key_config: vec![
KeyConfig {
event: KeyEvent::KeyPress,
keys: Regex::new("Return")?,
files: vec![
AudioFile {
path: String::from("mbox10.mp3"),
volume: None,
},
AudioFile {
path: String::from("mbox11.mp3"),
volume: None,
},
],
strategy: Some(PlaybackStrategy::Random),
variation: None,
},
KeyConfig {
event: KeyEvent::KeyPress,
keys: Regex::new(".*")?,
files: vec![AudioFile {
path: String::from("mbox9.mp3"),
volume: None,
}],
strategy: None,
variation: Some(SoundVariation {
volume: Some((0.1, 0.1)),
tempo: Some((0.075, 0.075)),
}),
},
],
disabled_keys: None,
variation: None,
});
}
self.sound_presets
.clone()
.into_iter()
.find(|v| v.name == name)
.ok_or_else(|| Error::PresetNotFound(name.to_string()))
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SoundPreset {
pub name: String,
pub key_config: Vec<KeyConfig>,
pub disabled_keys: Option<Vec<Key>>,
pub variation: Option<SoundVariation>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyConfig {
pub event: KeyEvent,
#[serde(with = "serde_regex")]
pub keys: Regex,
pub files: Vec<AudioFile>,
pub strategy: Option<PlaybackStrategy>,
pub variation: Option<SoundVariation>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum KeyEvent {
#[serde(rename = "press")]
KeyPress,
#[serde(rename = "release")]
KeyRelease,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AudioFile {
pub path: String,
pub volume: Option<f32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum PlaybackStrategy {
Random,
Sequential,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct SoundVariation {
pub volume: Option<(f32, f32)>,
pub tempo: Option<(f32, f32)>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_parse_config() -> Result<()> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../")
.join("config")
.join(DEFAULT_CONFIG);
if let Some(global_path) = Config::get_default_location() {
path = global_path;
}
Config::parse(&path)?;
Ok(())
}
}