From 4252fb7b129121867ef5c8bdbddf2130d10245ed Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:07:39 -0500 Subject: [PATCH 1/8] feat: use capl to play short tone when timer changes phase --- Cargo.lock | 2 +- src/app.rs | 12 +++++++ src/args.rs | 23 +++++++++++--- src/main.rs | 4 ++- src/sound.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/sound.rs diff --git a/Cargo.lock b/Cargo.lock index 4b9a3a2..e9c1855 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2120,7 +2120,7 @@ dependencies = [ [[package]] name = "termato" -version = "0.1.0" +version = "0.1.1" dependencies = [ "cpal", "crossterm", diff --git a/src/app.rs b/src/app.rs index 39ce384..81226c9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,7 @@ use crate::constants::SECONDS_PER_MIN; use crate::msg::Message; use crate::notification::send_desktop_notification; +use crate::sound::play_notification_sound; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TimerMode { @@ -17,6 +18,7 @@ pub struct Termato { pub time_remaining_in_sec: u32, pub show_help: bool, pub enable_notifications: bool, + pub enable_sound: bool, should_quit: bool, } @@ -31,6 +33,7 @@ impl Termato { time_remaining_in_sec: work_mins * SECONDS_PER_MIN, show_help: false, enable_notifications: false, + enable_sound: false, should_quit: false, } } @@ -44,6 +47,11 @@ impl Termato { self } + pub fn with_sound(mut self, enable: bool) -> Self { + self.enable_sound = enable; + self + } + pub fn update(&mut self, msg: Message) { match msg { Message::Tick => self.tick(), @@ -83,6 +91,10 @@ impl Termato { } } + if self.enable_sound { + play_notification_sound(); + } + self.toggle_mode(); } } diff --git a/src/args.rs b/src/args.rs index 41977ac..a600876 100644 --- a/src/args.rs +++ b/src/args.rs @@ -8,6 +8,8 @@ pub const FLAG_VERSION: &str = "-v"; pub const FLAG_VERSION_LONG: &str = "--version"; pub const FLAG_NOTIFY: &str = "-n"; pub const FLAG_NOTIFY_LONG: &str = "--notify"; +pub const FLAG_SOUND: &str = "-s"; +pub const FLAG_SOUND_LONG: &str = "--sound"; pub const FLAG_VISUALIZER: &str = "-z"; pub const FLAG_VISUALIZER_LONG: &str = "--visualizer"; @@ -16,6 +18,7 @@ pub struct CliConfig { pub work_mins: u32, pub break_mins: u32, pub enable_notifications: bool, + pub enable_sound: bool, pub enable_visualizer: bool, } @@ -37,6 +40,7 @@ impl Cli { pub fn parse_args(args: Vec) -> CliAction { let mut enable_notifications = false; let mut enable_visualizer = false; + let mut enable_sound = false; let mut positional = Vec::new(); for arg in args.into_iter().skip(1) { @@ -44,6 +48,7 @@ impl Cli { FLAG_HELP | FLAG_HELP_LONG => return CliAction::PrintHelp, FLAG_VERSION | FLAG_VERSION_LONG => return CliAction::PrintVersion, FLAG_NOTIFY | FLAG_NOTIFY_LONG => enable_notifications = true, + FLAG_SOUND | FLAG_SOUND_LONG => enable_sound = true, FLAG_VISUALIZER | FLAG_VISUALIZER_LONG => enable_visualizer = true, "--" => {} s if s.starts_with('-') => { @@ -69,6 +74,7 @@ impl Cli { work_mins, break_mins, enable_notifications, + enable_sound, enable_visualizer, }) } @@ -82,22 +88,27 @@ impl Cli { println!( " {:<19} Enable desktop notifications", - format_args!("{}, {}", FLAG_NOTIFY, FLAG_NOTIFY_LONG) + format!("{}, {}", FLAG_NOTIFY, FLAG_NOTIFY_LONG) + ); + + println!( + " {:<19} Enable sound notifications", + format!("{}, {}", FLAG_SOUND, FLAG_SOUND_LONG) ); println!( " {:<19} Display visualizer of playing audio", - format_args!("{}, {}", FLAG_VISUALIZER, FLAG_VISUALIZER_LONG) + format!("{}, {}", FLAG_VISUALIZER, FLAG_VISUALIZER_LONG) ); println!( " {:<19} Print this help message", - format_args!("{}, {}", FLAG_HELP, FLAG_HELP_LONG) + format!("{}, {}", FLAG_HELP, FLAG_HELP_LONG) ); println!( " {:<19} Print the version number", - format_args!("{}, {}", FLAG_VERSION, FLAG_VERSION_LONG) + format!("{}, {}", FLAG_VERSION, FLAG_VERSION_LONG) ); println!(); @@ -126,6 +137,7 @@ mod tests { work_mins: 25, break_mins: 5, enable_notifications: false, + enable_sound: false, enable_visualizer: false, }) ); @@ -145,6 +157,7 @@ mod tests { work_mins: 25, break_mins: 5, enable_notifications: false, + enable_sound: false, enable_visualizer: true, }) ); @@ -165,6 +178,7 @@ mod tests { work_mins: 50, break_mins: 10, enable_notifications: true, + enable_sound: false, enable_visualizer: true, }) ); @@ -194,6 +208,7 @@ mod tests { work_mins: 25, break_mins: 5, enable_notifications: false, + enable_sound: false, enable_visualizer: false, }) ); diff --git a/src/main.rs b/src/main.rs index 2c03afb..abd921e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod constants; mod events; mod msg; mod notification; +mod sound; mod terminal; mod ui; @@ -61,7 +62,8 @@ fn main() -> Result<(), io::Error> { }; let mut termato = Termato::new(config.work_mins, config.break_mins) - .with_notifications(config.enable_notifications); + .with_notifications(config.enable_notifications) + .with_sound(config.enable_sound); let (tx, rx) = mpsc::channel(); diff --git a/src/sound.rs b/src/sound.rs new file mode 100644 index 0000000..a6b2015 --- /dev/null +++ b/src/sound.rs @@ -0,0 +1,88 @@ +use std::sync::mpsc; +use std::thread; +use std::time::Duration; + +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; + +const FREQ_D5_HZ: f32 = 587.33; +const FREQ_A5_HZ: f32 = 880.00; + +const NOTE_1_DURATION_SECS: f32 = 0.15; +const NOTE_2_DURATION_SECS: f32 = 0.25; +const TOTAL_CHIME_DURATION_SECS: f32 = NOTE_1_DURATION_SECS + NOTE_2_DURATION_SECS; +const CHIME_VOLUME_GAIN: f32 = 0.25; + +pub fn play_notification_sound() { + thread::spawn(move || { + // term bell fallback + print!("\x07"); + + let _ = std::io::Write::flush(&mut std::io::stdout()); + + let _ = play_audio_chime(); + }); +} + +fn synthesize_sine_wave(freq_hz: f32, t_seconds: f32) -> f32 { + (2.0 * std::f32::consts::PI * freq_hz * t_seconds).sin() +} + +fn calculate_linear_decay(t_seconds: f32, duration_secs: f32) -> f32 { + (1.0 - (t_seconds / duration_secs)).max(0.0) +} + +fn play_audio_chime() -> Result<(), Box> { + let host = cpal::default_host(); + + let device = host + .default_output_device() + .ok_or("No default output audio device found")?; + + let config: cpal::StreamConfig = device.default_output_config()?.into(); + + let sample_rate = config.sample_rate as f32; + let channels = config.channels as usize; + + let mut sample_clock = 0u64; + let (tx, rx) = mpsc::channel::<()>(); + + let stream = device.build_output_stream( + config, + move |data: &mut [f32], _| { + for frame in data.chunks_mut(channels) { + // Convert sample counter to elapsed time in seconds + let elapsed_time = sample_clock as f32 / sample_rate; + sample_clock += 1; + + let sample = if elapsed_time < NOTE_1_DURATION_SECS { + let wave = synthesize_sine_wave(FREQ_D5_HZ, elapsed_time); + let envelope = calculate_linear_decay(elapsed_time, NOTE_1_DURATION_SECS); + wave * envelope * CHIME_VOLUME_GAIN + } else if elapsed_time < TOTAL_CHIME_DURATION_SECS { + let t_note2 = elapsed_time - NOTE_1_DURATION_SECS; + let wave = synthesize_sine_wave(FREQ_A5_HZ, t_note2); + let envelope = calculate_linear_decay(t_note2, NOTE_2_DURATION_SECS); + wave * envelope * CHIME_VOLUME_GAIN + } else { + 0.0 + }; + + for channel in frame.iter_mut() { + *channel = sample; + } + + if elapsed_time >= TOTAL_CHIME_DURATION_SECS { + let _ = tx.send(()); + } + } + }, + |err| eprintln!("Audio output stream error: {}", err), + None, + )?; + + stream.play()?; + + let _ = rx.recv_timeout(Duration::from_millis(500)); + + Ok(()) +} From 71eb940afc4d867493cb95655c6f718a19f50de8 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:17:58 -0500 Subject: [PATCH 2/8] refactor: store config on termato state --- src/app.rs | 88 +++++++++++++++++++++++------------------------------ src/main.rs | 4 +-- 2 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/app.rs b/src/app.rs index 81226c9..23d50a7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use crate::args::CliConfig; use crate::constants::SECONDS_PER_MIN; use crate::msg::Message; use crate::notification::send_desktop_notification; @@ -10,30 +11,25 @@ pub enum TimerMode { } pub struct Termato { - pub work_mins: u32, - pub break_mins: u32, + pub config: CliConfig, pub mode: TimerMode, pub is_running: bool, pub duration_in_secs: u32, pub time_remaining_in_sec: u32, pub show_help: bool, - pub enable_notifications: bool, - pub enable_sound: bool, should_quit: bool, } impl Termato { - pub fn new(work_mins: u32, break_mins: u32) -> Self { + pub fn new(config: CliConfig) -> Self { + let duration_in_secs = config.work_mins * SECONDS_PER_MIN; Termato { - work_mins, - break_mins, + config, mode: TimerMode::Work, is_running: false, - duration_in_secs: work_mins * SECONDS_PER_MIN, - time_remaining_in_sec: work_mins * SECONDS_PER_MIN, + duration_in_secs, + time_remaining_in_sec: duration_in_secs, show_help: false, - enable_notifications: false, - enable_sound: false, should_quit: false, } } @@ -42,16 +38,6 @@ impl Termato { self.should_quit } - pub fn with_notifications(mut self, enable: bool) -> Self { - self.enable_notifications = enable; - self - } - - pub fn with_sound(mut self, enable: bool) -> Self { - self.enable_sound = enable; - self - } - pub fn update(&mut self, msg: Message) { match msg { Message::Tick => self.tick(), @@ -73,8 +59,8 @@ impl Termato { TimerMode::Break => TimerMode::Work, }; self.duration_in_secs = match self.mode { - TimerMode::Work => self.work_mins * SECONDS_PER_MIN, - TimerMode::Break => self.break_mins * SECONDS_PER_MIN, + TimerMode::Work => self.config.work_mins * SECONDS_PER_MIN, + TimerMode::Break => self.config.break_mins * SECONDS_PER_MIN, }; self.time_remaining_in_sec = self.duration_in_secs; } @@ -84,14 +70,14 @@ impl Termato { self.time_remaining_in_sec -= 1; if self.time_remaining_in_sec == 0 { - if self.enable_notifications { + if self.config.enable_notifications { match self.mode { TimerMode::Work => send_desktop_notification("Work Done", "Time to take a break."), TimerMode::Break => send_desktop_notification("Break Over", "Time to focus."), } } - if self.enable_sound { + if self.config.enable_sound { play_notification_sound(); } @@ -103,7 +89,7 @@ impl Termato { pub fn reset(&mut self) { self.is_running = false; self.mode = TimerMode::Work; - self.duration_in_secs = self.work_mins * SECONDS_PER_MIN; + self.duration_in_secs = self.config.work_mins * SECONDS_PER_MIN; self.time_remaining_in_sec = self.duration_in_secs; } @@ -116,24 +102,35 @@ impl Termato { mod tests { use super::*; + fn get_test_config(work_mins: u32, break_mins: u32) -> CliConfig { + CliConfig { + work_mins, + break_mins, + enable_notifications: false, + enable_sound: false, + enable_visualizer: false, + } + } + #[test] fn termato_new_when_called_it_should_return_expected_starting_state() { - let result = Termato::new(25, 5); + let result = Termato::new(get_test_config(25, 5)); - assert_eq!(result.work_mins, 25); - assert_eq!(result.break_mins, 5); + assert_eq!(result.config.work_mins, 25); + assert_eq!(result.config.break_mins, 5); assert_eq!(result.mode, TimerMode::Work); assert!(!result.is_running); assert_eq!(result.duration_in_secs, 1500); assert_eq!(result.time_remaining_in_sec, 1500); assert!(!result.show_help); - assert!(!result.enable_notifications); + assert!(!result.config.enable_notifications); + assert!(!result.config.enable_sound); assert!(!result.should_quit); } #[test] fn termato_update_when_called_with_messages_it_should_update_state() { - let mut termato = Termato::new(25, 5); + let mut termato = Termato::new(get_test_config(25, 5)); termato.update(Message::ToggleRunning); assert!(termato.is_running); @@ -154,7 +151,7 @@ mod tests { #[test] fn termato_tick_when_called_and_not_running_it_should_do_nothing() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.update(Message::Tick); @@ -163,7 +160,7 @@ mod tests { #[test] fn termato_tick_when_called_and_no_time_remaining_it_should_do_nothing() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.is_running = true; termato.time_remaining_in_sec = 0; @@ -174,7 +171,7 @@ mod tests { #[test] fn termato_tick_when_running_and_time_reamining_it_should_reduce_remaining_by_one() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.is_running = true; termato.update(Message::Tick); @@ -184,7 +181,7 @@ mod tests { #[test] fn termato_tick_when_running_on_work_and_time_remaining_reaches_zero_it_should_toggle_to_break() { - let mut termato = Termato::new(1, 2); + let mut termato = Termato::new(get_test_config(1, 2)); termato.is_running = true; termato.time_remaining_in_sec = 1; @@ -197,7 +194,7 @@ mod tests { #[test] fn termato_tick_when_running_on_break_and_time_remaining_reaches_zero_it_should_toggle_to_work() { - let mut termato = Termato::new(2, 1); + let mut termato = Termato::new(get_test_config(2, 1)); termato.mode = TimerMode::Break; termato.is_running = true; termato.time_remaining_in_sec = 1; @@ -211,7 +208,7 @@ mod tests { #[test] fn termato_toggle_mode_when_called_it_should_toggle_the_mode() { - let mut termato = Termato::new(2, 1); + let mut termato = Termato::new(get_test_config(2, 1)); termato.toggle_mode(); assert_eq!(termato.mode, TimerMode::Break); @@ -226,7 +223,7 @@ mod tests { #[test] fn termato_toggle_running_when_called_and_already_running_it_should_pause() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.is_running = true; termato.toggle_running(); @@ -236,7 +233,7 @@ mod tests { #[test] fn termato_toggle_running_when_called_and_already_paused_it_should_run() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.toggle_running(); @@ -245,7 +242,7 @@ mod tests { #[test] fn termato_rest_when_called_it_should_reset_the_apps_state() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.is_running = true; termato.mode = TimerMode::Break; termato.duration_in_secs = 1; @@ -261,7 +258,7 @@ mod tests { #[test] fn termato_toggle_help_when_called_it_should_toggle_help() { - let mut termato = Termato::new(1, 1); + let mut termato = Termato::new(get_test_config(1, 1)); termato.toggle_help(); @@ -271,13 +268,4 @@ mod tests { assert!(!termato.show_help); } - - #[test] - fn termato_notifications_when_called_it_should_verify_opt_in_behavior() { - let result = Termato::new(25, 5); - assert!(!result.enable_notifications); - - let result = result.with_notifications(true); - assert!(result.enable_notifications); - } } diff --git a/src/main.rs b/src/main.rs index abd921e..b069327 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,9 +61,7 @@ fn main() -> Result<(), io::Error> { None }; - let mut termato = Termato::new(config.work_mins, config.break_mins) - .with_notifications(config.enable_notifications) - .with_sound(config.enable_sound); + let mut termato = Termato::new(config); let (tx, rx) = mpsc::channel(); From 71f261f6d8ad80cffa11074dfcae178a017e8f22 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:21:41 -0500 Subject: [PATCH 3/8] feat: extend parser to support combining flag options --- src/args.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index a600876..a3deb4e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -47,12 +47,26 @@ impl Cli { match arg.as_str() { FLAG_HELP | FLAG_HELP_LONG => return CliAction::PrintHelp, FLAG_VERSION | FLAG_VERSION_LONG => return CliAction::PrintVersion, - FLAG_NOTIFY | FLAG_NOTIFY_LONG => enable_notifications = true, FLAG_SOUND | FLAG_SOUND_LONG => enable_sound = true, FLAG_VISUALIZER | FLAG_VISUALIZER_LONG => enable_visualizer = true, "--" => {} + s if s.starts_with("--") => match s { + FLAG_NOTIFY_LONG => enable_notifications = true, + FLAG_VISUALIZER_LONG => enable_visualizer = true, + FLAG_SOUND_LONG => enable_sound = true, + _ => return CliAction::Error(format!("Unknown option: '{}'", s)), + }, s if s.starts_with('-') => { - return CliAction::Error(format!("Unknown option: '{}'", s)); + for ch in s.chars().skip(1) { + match ch { + 'h' => return CliAction::PrintHelp, + 'v' => return CliAction::PrintVersion, + 'n' => enable_notifications = true, + 's' => enable_sound = true, + 'z' => enable_visualizer = true, + _ => return CliAction::Error(format!("Unknown option: '-{}'", ch)), + } + } } s => positional.push(s.to_string()), } @@ -213,4 +227,33 @@ mod tests { }) ); } + + #[test] + fn test_combined_short_flags() { + let args = vec![ + "termato".to_string(), + "-nsz".to_string(), + "25".to_string(), + "5".to_string(), + ]; + assert_eq!( + Cli::parse_args(args), + CliAction::Run(CliConfig { + work_mins: 25, + break_mins: 5, + enable_notifications: true, + enable_sound: true, + enable_visualizer: true, + }) + ); + } + + #[test] + fn test_combined_short_flags_with_invalid_char() { + let args = vec!["termato".to_string(), "-nxz".to_string()]; + assert_eq!( + Cli::parse_args(args), + CliAction::Error("Unknown option: '-x'".to_string()) + ); + } } From 7461dee12c6238c1c2fee6e2570d6543a62dd176 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:28:49 -0500 Subject: [PATCH 4/8] docs: update README --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 106c261..365f573 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A minimal and unintrusive Pomodoro timer written in Rust for the terminal. - Supports focus sessions and break sessions. - System-level alerts to notify you when a session ends. - Define custom focus and break session durations from the command line. +- Cross-platform sound notifications when a session ends. +- Cross-platform audio visualizer. ## Keyboard Controls @@ -52,7 +54,7 @@ termato ### Specifying Options and Custom Session Times -By default, desktop notifications are disabled. You can specify options and custom session durations as command-line arguments: +By default, desktop notifications, sound alerts, and the visualizer are disabled. You can specify options and custom session durations as command-line arguments: ```bash termato [options] [work_minutes] [break_minutes] @@ -64,10 +66,19 @@ For example, to enable desktop notifications and run a 50-minute focus session f termato -n 50 10 ``` -Or using the long flag: +To enable both desktop notifications and sound notifications with custom times: ```bash -termato --notify 50 10 +termato -ns 50 10 + +# or with long flags +termato --notify -sound 50 10 +``` + +You can also combine short flags together (e.g. -nsz to enable notifications, sound, and the audio visualizer): + +```bash +termato -nsz ``` ### Help and Version From b456397c300c92dd5c0365b098f21aea77f29314 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:33:09 -0500 Subject: [PATCH 5/8] fix: bug where visualizer remained displaying audio after audio finished --- src/audio.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index 74761d3..480932a 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -248,14 +248,28 @@ impl AudioVisualizer { thread::sleep(std::time::Duration::from_millis(16)); let samples = { - let buf = match audio_buffer.lock() { + let mut buf = match audio_buffer.lock() { Ok(b) => b, Err(_) => continue, }; + + // decay heights smoothly if buf.len() < chunk_size { + for val in prev_heights.iter_mut() { + *val = (*val - falloff).max(0.0); + } + + if let Ok(mut bars) = bar_data.lock() { + for (i, val) in prev_heights.iter().enumerate() { + bars[i] = ((*val * 10.0).clamp(0.0, 100.0)) as u64; + } + } + continue; } - buf[buf.len() - chunk_size..].to_vec() + + // drain the consumed samples + buf.drain(0..chunk_size).collect::>() }; Self::apply_hann_window(&samples, &mut windowed_buffer); From 1344625fc99b50ac1df8b540551eca884321e40d Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 15:39:36 -0500 Subject: [PATCH 6/8] fix: make sure we don't start playing around sound if one is already playing --- src/sound.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/sound.rs b/src/sound.rs index a6b2015..17ec61d 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -1,5 +1,6 @@ -use std::sync::mpsc; -use std::thread; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{LazyLock, mpsc}; + use std::time::Duration; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -12,14 +13,21 @@ const NOTE_2_DURATION_SECS: f32 = 0.25; const TOTAL_CHIME_DURATION_SECS: f32 = NOTE_1_DURATION_SECS + NOTE_2_DURATION_SECS; const CHIME_VOLUME_GAIN: f32 = 0.25; +static SOUND_BUSY: LazyLock = LazyLock::new(|| AtomicBool::new(false)); + pub fn play_notification_sound() { - thread::spawn(move || { - // term bell fallback + if SOUND_BUSY.swap(true, Ordering::AcqRel) { + return; + } + + std::thread::spawn(move || { + // term bell fall back print!("\x07"); let _ = std::io::Write::flush(&mut std::io::stdout()); - let _ = play_audio_chime(); + + SOUND_BUSY.store(false, Ordering::Release); }); } From fe5e2d85f056ef53d08d511319f7137372836607 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 17:36:29 -0500 Subject: [PATCH 7/8] chore: run cargo fmt --- src/app.rs | 14 +++++++------- src/events.rs | 2 +- src/ui.rs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app.rs b/src/app.rs index 23d50a7..4a69b06 100644 --- a/src/app.rs +++ b/src/app.rs @@ -103,14 +103,14 @@ mod tests { use super::*; fn get_test_config(work_mins: u32, break_mins: u32) -> CliConfig { - CliConfig { - work_mins, - break_mins, - enable_notifications: false, - enable_sound: false, - enable_visualizer: false, - } + CliConfig { + work_mins, + break_mins, + enable_notifications: false, + enable_sound: false, + enable_visualizer: false, } + } #[test] fn termato_new_when_called_it_should_return_expected_starting_state() { diff --git a/src/events.rs b/src/events.rs index 57af44f..b015058 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,5 @@ -use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use crate::msg::Message; +use crossterm::event::{self, Event, KeyCode, KeyModifiers}; pub fn handle_event(event: Event) -> Option { if let Event::Key(key) = event diff --git a/src/ui.rs b/src/ui.rs index 7984fec..78fb386 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,11 +6,11 @@ use ratatui::{ widgets::{Block, Borders, Clear, Paragraph}, }; -use crate::{audio::AudioVisualizer, constants::VIZ_NUM_BARS}; use crate::{ app::{Termato, TimerMode}, constants::SECONDS_PER_MIN, }; +use crate::{audio::AudioVisualizer, constants::VIZ_NUM_BARS}; // use unicode blocks for rendering // Empty -> " " From e1b176a595970c611cdd21ca11f3aab79aa3ca8e Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Tue, 28 Jul 2026 17:38:02 -0500 Subject: [PATCH 8/8] chore: bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 9f4dd22..f3209d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "termato" -version = "0.1.1" +version = "0.2.0" edition = "2024" repository = "https://github.com/StevanFreeborn/termato"