Skip to content

Commit

Permalink
feat: handle the config
Browse files Browse the repository at this point in the history
  • Loading branch information
orhun committed Sep 24, 2023
1 parent 3ac0e14 commit 6486f41
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 100 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rdev = "0.5.3"
rdev = { version = "0.5.3", features = ["serialize"] }
rodio = { version = "0.17.1", default-features = false, features = [
"symphonia-mp3",
] }
Expand Down
7 changes: 3 additions & 4 deletions config/typewriter.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
[[sound_preset]]
name = "default"
key_config = [
{ event = "key_press", keys = "enter", file = "ding.mp3", volume = 1.0, embed = true },
{ event = "key_release", keys = "enter", file = "keyup.mp3", volume = 1.0, embed = true },
{ event = "key_press", keys = "Return", file = "ding.mp3", volume = 1.0, embed = true },
{ event = "key_press", keys = ".*", file = "keydown.mp3", volume = 1.0, embed = true },
{ event = "key_release", keys = ".*", file = "keyup.mp3", volume = 1.0, embed = true },
]
Expand All @@ -11,10 +10,10 @@ disabled_keys = []
[[sound_preset]]
name = "simple"
key_config = [
{ event = "key_press", keys = "enter", file = "newline.mp3", embed = true },
{ event = "key_press", keys = "Return", file = "newline.mp3", embed = true },
{ event = "key_press", keys = ".*", file = "keystroke.mp3", embed = true },
]
disabled_keys = ["up", "down", "left", "right", "backspace"]
disabled_keys = ["UpArrow", "DownArrow", "LeftArrow", "RightArrow", "Backspace"]

[[sound_preset]]
name = "custom"
Expand Down
84 changes: 69 additions & 15 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::io::BufReader;

use crate::{
embed::{Sound, Sounds},
config::{KeyConfig, KeyEventConfig, SoundPreset},
embed::EmbeddedSound,
error::Result,
};
use rodio::{OutputStream, Sink};
use rdev::{Event, EventType};
use rodio::{Decoder, OutputStream, Sink};
use std::{fs::File, io::BufReader};

/// Application state controller.
pub struct App {
preset: SoundPreset,
_stream: OutputStream,
key_press_sink: Sink,
key_release_sink: Sink,
Expand All @@ -16,37 +18,89 @@ pub struct App {

impl App {
/// Initializes a new instance.
pub fn init() -> Result<Self> {
pub fn init(preset: SoundPreset) -> Result<Self> {
let (stream, handle) = OutputStream::try_default()?;
let key_press_sink = Sink::try_new(&handle)?;
let key_release_sink = Sink::try_new(&handle)?;
Ok(Self {
preset,
_stream: stream,
key_press_sink,
key_release_sink,
key_released: true,
})
}

/// Handle the key events.
pub fn handle_event(&mut self, event: Event) -> Result<()> {
match event.event_type {
EventType::KeyPress(key) | EventType::KeyRelease(key) => {
tracing::debug!("Event: {:?}", event);
if self
.preset
.disabled_keys
.as_ref()
.cloned()
.unwrap_or_default()
.contains(&key)
{
tracing::debug!("Skipping: {:?}", key);
return Ok(());
}
let event_type = match event.event_type {
EventType::KeyPress(_) => KeyEventConfig::KeyPress,
EventType::KeyRelease(_) => KeyEventConfig::KeyRelease,
_ => unreachable!(),
};
let key_config = self
.preset
.key_config
.clone()
.into_iter()
.find(|v| v.event == event_type && v.keys.is_match(&format!("{:?}", key)));
tracing::debug!("Key config: {:?}", key_config);
if event_type == KeyEventConfig::KeyPress {
self.handle_key_press(&key_config)?;
} else {
self.handle_key_release(&key_config)?;
}
}
_ => {}
};
Ok(())
}

/// Handle the key press event.
pub fn handle_key_press(&mut self) -> Result<()> {
pub fn handle_key_press(&mut self, key_config: &Option<KeyConfig>) -> Result<()> {
if self.key_released {
let sound = Sounds::get_sound(Sound::Keydown)?;
self.key_press_sink.stop();
self.key_press_sink
.append(rodio::Decoder::new(BufReader::new(sound))?);
if let Some(key_config) = key_config {
self.play_sound(key_config, &self.key_press_sink)?;
}
}
self.key_released = false;
Ok(())
}

/// Handle the key release event.
pub fn handle_key_release(&mut self) -> Result<()> {
let sound = Sounds::get_sound(Sound::Keyup)?;
self.key_release_sink.stop();
self.key_release_sink
.append(rodio::Decoder::new(BufReader::new(sound))?);
pub fn handle_key_release(&mut self, key_config: &Option<KeyConfig>) -> Result<()> {
if let Some(key_config) = key_config {
self.play_sound(key_config, &self.key_release_sink)?;
}
self.key_released = true;
Ok(())
}

/// Play the sound from embedded/file for the given sink.
fn play_sound(&self, key_config: &KeyConfig, sink: &Sink) -> Result<()> {
if key_config.embed.unwrap_or(false) {
let sound = BufReader::new(Box::new(EmbeddedSound::get_sound(&key_config.file)?));
sink.stop();
sink.append(Decoder::new(sound)?);
} else {
let sound = BufReader::new(Box::new(File::open(&key_config.file)?));
sink.stop();
sink.append(Decoder::new(sound)?);
};
Ok(())
}
}
34 changes: 5 additions & 29 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::error::{Error, Result};
use crate::error::Result;
use rdev::Key;
use regex::Regex;
use rust_embed::RustEmbed;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
Expand All @@ -10,30 +10,6 @@ use std::str;
/// Default configuration file.
pub const DEFAULT_CONFIG: &str = concat!(env!("CARGO_PKG_NAME"), ".toml");

/// Configuration file embedder/extractor.
///
/// Embeds `config/`[`DEFAULT_CONFIG`] into the binary.
#[derive(Debug, RustEmbed)]
#[folder = "config/"]
pub struct EmbeddedConfig;

impl EmbeddedConfig {
/// Extracts the embedded content.
pub fn get_config() -> Result<String> {
match Self::get(DEFAULT_CONFIG) {
Some(v) => Ok(str::from_utf8(&v.data)?.to_string()),
None => Err(Error::Embedded(String::from("embedded config not found"))),
}
}

/// Parses the extracted content into [`Config`].
///
/// [`Config`]: Config
pub fn parse() -> Result<Config> {
Ok(toml::from_str(&Self::get_config()?)?)
}
}

/// Configuration.
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
Expand Down Expand Up @@ -83,11 +59,11 @@ pub struct SoundPreset {
/// Key configuration.
pub key_config: Vec<KeyConfig>,
/// List of disabled keys.
pub disabled_keys: Option<Vec<String>>,
pub disabled_keys: Option<Vec<Key>>,
}

/// Key configuration.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyConfig {
/// Event.
pub event: KeyEventConfig,
Expand All @@ -103,7 +79,7 @@ pub struct KeyConfig {
}

/// Key configuration.
#[derive(Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum KeyEventConfig {
/// Key press.
Expand Down
69 changes: 35 additions & 34 deletions src/embed.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
use crate::error::{Error, Result};
use crate::{
config::{Config, DEFAULT_CONFIG},
error::{Error, Result},
};
use rust_embed::RustEmbed;
use std::{fmt, io::Cursor};

/// File extension of the audio files.
const FILE_EXTENSION: &str = "mp3";

/// A typewriter sound.
#[derive(Debug)]
#[allow(dead_code)]
pub enum Sound {
Ding,
Keydown,
Keystroke,
Keyup,
Newline,
}

impl fmt::Display for Sound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", format!("{:?}", self).to_lowercase())
}
}

impl Sound {
/// Returns the file name of the sound.
fn as_file_name(&self) -> String {
format!("{self}.{FILE_EXTENSION}")
}
}
use std::io::Cursor;
use std::str;

/// Embedded sound assets.
#[derive(RustEmbed)]
#[folder = "sounds"]
pub struct Sounds;
pub struct EmbeddedSound;

impl Sounds {
impl EmbeddedSound {
/// Returns the bytes of the sound.
pub fn get_sound(sound: Sound) -> Result<Cursor<Vec<u8>>> {
Self::get(&sound.as_file_name())
pub fn get_sound(name: &str) -> Result<Cursor<Vec<u8>>> {
Self::get(name)
.map(|v| Cursor::new(v.data.to_vec()))
.ok_or_else(|| Error::AssetNotFound(sound.to_string()))
.ok_or_else(|| Error::AssetNotFound(name.to_string()))
}
}

/// Configuration file embedder/extractor.
///
/// Embeds `config/`[`DEFAULT_CONFIG`] into the binary.
#[derive(Debug, RustEmbed)]
#[folder = "config/"]
pub struct EmbeddedConfig;

impl EmbeddedConfig {
/// Extracts the embedded content.
pub fn get_config() -> Result<String> {
match Self::get(DEFAULT_CONFIG) {
Some(v) => Ok(str::from_utf8(&v.data)?.to_string()),
None => Err(Error::Embedded(String::from("embedded config not found"))),
}
}

/// Parses the extracted content into [`Config`].
///
/// [`Config`]: Config
pub fn parse() -> Result<Config> {
Ok(toml::from_str(&Self::get_config()?)?)
}
}
21 changes: 6 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod error;
pub mod logger;

/// File embedder.
mod embed;
pub mod embed;

/// Command-line arguments.
pub mod args;
Expand All @@ -19,11 +19,11 @@ pub mod config;
use app::App;
use config::SoundPreset;
use error::Result;
use rdev::{listen, EventType};
use rdev::listen;
use std::thread;

/// Starts the typewriter.
pub async fn run(sound_preset: &SoundPreset) -> Result<()> {
pub async fn run(sound_preset: SoundPreset) -> Result<()> {
// Create a listener for the keyboard events.
let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel();
thread::spawn(move || {
Expand All @@ -36,22 +36,13 @@ pub async fn run(sound_preset: &SoundPreset) -> Result<()> {
});

// Create the application state.
let mut app = App::init()?;
tracing::debug!("{:#?}", sound_preset);
let mut app = App::init(sound_preset)?;

// Handle events loop.
// Handle events.
loop {
if let Some(event) = receiver.recv().await {
tracing::debug!("{:?}", event);
match event.event_type {
EventType::KeyPress(_) => {
app.handle_key_press()?;
}
EventType::KeyRelease(_) => {
app.handle_key_release()?;
}
_ => {}
};
app.handle_event(event)?;
}
}
}
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use std::{fs, process};
use tracing::Level;

use typewriter::args::Args;
use typewriter::config::{Config, EmbeddedConfig, DEFAULT_CONFIG};
use typewriter::config::{Config, DEFAULT_CONFIG};
use typewriter::embed::EmbeddedConfig;
use typewriter::error::{Error, Result};
use typewriter::logger;

Expand Down Expand Up @@ -36,7 +37,7 @@ async fn main() -> Result<()> {
let preset_name = args.preset.unwrap_or_else(|| String::from("default"));
let preset = config
.sound_presets
.iter()
.into_iter()
.find(|v| v.name == preset_name)
.ok_or_else(|| Error::PresetNotFound(preset_name))?;
match typewriter::run(preset).await {
Expand Down

0 comments on commit 6486f41

Please sign in to comment.