diff --git a/src/config.rs b/src/config.rs index b36ef24..c3d6649 100644 --- a/src/config.rs +++ b/src/config.rs @@ -136,8 +136,8 @@ impl GeneralConfig { } fn post_load(&self) { - GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.0, 5.0); - GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.0, 5.0); + GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.05, 5.0); + GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0); } } @@ -192,12 +192,31 @@ pub fn load_general() -> GeneralConfig { // Add files from conf.d directory let path_conf_d = get_conf_d_path(); if let Ok(paths_unsorted) = std::fs::read_dir(path_conf_d) { + let mut paths: Vec<_> = paths_unsorted + .filter_map(|r| match r { + Ok(entry) => Some(entry), + Err(e) => { + error!("Failed to read conf.d directory: {}", e); + None + } + }) + .collect(); // Sort paths alphabetically - let mut paths: Vec<_> = paths_unsorted.map(|r| r.unwrap()).collect(); // TODO safe unwrap? paths.sort_by_key(|dir| dir.path()); for path in paths { - if !path.file_type().unwrap().is_file() { - // TODO safe unwrap? + let file_type = match path.file_type() { + Ok(file_type) => file_type, + Err(e) => { + error!( + "Failed to get file type of {}: {}", + path.path().to_string_lossy(), + e + ); + continue; + } + }; + + if !file_type.is_file() { continue; } diff --git a/src/gui/font.rs b/src/gui/font.rs index de47b58..8227590 100644 --- a/src/gui/font.rs +++ b/src/gui/font.rs @@ -106,9 +106,7 @@ impl FontCache { } let pattern_str = format!("{PRIMARY_FONT}-{size}:style=bold:charset={cp:04x}"); - - let mut pattern = - OwnedPattern::from_str(&pattern_str).expect("Failed to create fontconfig pattern"); + let mut pattern = OwnedPattern::from_str(&pattern_str).unwrap(); // safe because PRIMARY_FONT is const self.fc .substitute(&mut pattern, fontconfig::MatchKind::Pattern); pattern.default_substitute(); diff --git a/src/gui/modular/button.rs b/src/gui/modular/button.rs index f9c0401..1b4c706 100644 --- a/src/gui/modular/button.rs +++ b/src/gui/modular/button.rs @@ -1,6 +1,5 @@ use std::{ f32::consts::PI, - io::Cursor, ops::Add, process::{self, Child}, sync::Arc, @@ -8,7 +7,6 @@ use std::{ }; use glam::{Quat, Vec3A}; -use rodio::{Decoder, Source}; use serde::Deserialize; use crate::{ @@ -613,11 +611,8 @@ fn run_window(window: &Arc, action: &WindowAction, app: &mut AppState) { } } +const THUMP_AUDIO_WAV: &'static [u8] = include_bytes!("../../res/380885.wav"); + fn audio_thump(app: &mut AppState) { - if let Some(handle) = app.audio.get_handle() { - let wav = include_bytes!("../../res/380885.wav"); - let cursor = Cursor::new(wav); - let source = Decoder::new_wav(cursor).unwrap(); - let _ = handle.play_raw(source.convert_samples()); - } + app.audio.play(THUMP_AUDIO_WAV); } diff --git a/src/overlays/keyboard.rs b/src/overlays/keyboard.rs index ccd3fc4..302e6fa 100644 --- a/src/overlays/keyboard.rs +++ b/src/overlays/keyboard.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - io::Cursor, process::{Child, Command}, str::FromStr, }; @@ -18,7 +17,6 @@ use crate::{ use glam::{vec2, vec3a, Affine2, Vec4}; use once_cell::sync::Lazy; use regex::Regex; -use rodio::{Decoder, Source}; use serde::{Deserialize, Serialize}; const PIXELS_PER_UNIT: f32 = 80.; @@ -83,10 +81,14 @@ where verbs: key_events_for_macro(macro_verbs), }); } else if let Some(exec_args) = LAYOUT.exec_commands.get(key) { + if exec_args.is_empty() { + log::error!("Keyboard: EXEC args empty for {}", key); + continue; + } maybe_state = Some(KeyButtonData::Exec { program: exec_args .first() - .expect("Keyboard: Invalid EXEC args") + .unwrap() // safe because we checked is_empty .clone(), args: exec_args.iter().skip(1).cloned().collect(), }); @@ -227,18 +229,12 @@ struct KeyboardData { processes: Vec, } +const KEY_AUDIO_WAV: &'static [u8] = include_bytes!("../res/421581.wav"); + impl KeyboardData { fn key_click(&mut self, app: &mut AppState) { - if !app.session.config.keyboard_sound_enabled { - return; - } - - if let Some(handle) = app.audio.get_handle() { - // https://freesound.org/people/UberBosser/sounds/421581/ - let wav = include_bytes!("../res/421581.wav"); - let cursor = Cursor::new(wav); - let source = Decoder::new_wav(cursor).unwrap(); - let _ = handle.play_raw(source.convert_samples()); + if app.session.config.keyboard_sound_enabled { + app.audio.play(KEY_AUDIO_WAV); } } } @@ -253,7 +249,7 @@ enum KeyButtonData { static LAYOUT: Lazy = Lazy::new(Layout::load_from_disk); static MACRO_REGEX: Lazy = - Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap()); + Lazy::new(|| Regex::new(r"^([A-Za-z0-1_-]+)(?: +(UP|DOWN))?$").unwrap()); // want panic #[derive(Debug, Deserialize, Serialize)] pub struct Layout { @@ -314,11 +310,17 @@ impl Layout { key = &key[3..]; } if key.contains('_') { - key = key.split('_').next().unwrap(); + key = key.split('_').next().unwrap_or_else(|| { + log::error!( + "keyboard.yaml: Key '{}' must not start or end with '_'!", + key + ); + "???" + }); } vec![format!( "{}{}", - key.chars().next().unwrap().to_uppercase(), + key.chars().next().unwrap().to_uppercase(), // safe because we checked is_empty &key[1..].to_lowercase() )] } diff --git a/src/overlays/toast.rs b/src/overlays/toast.rs index f93b0e2..f425124 100644 --- a/src/overlays/toast.rs +++ b/src/overlays/toast.rs @@ -1,12 +1,9 @@ use std::{ - io::Cursor, ops::Add, sync::{atomic::AtomicUsize, Arc}, time::Instant, }; -use rodio::{Decoder, Source}; - use glam::vec3a; use crate::{ @@ -21,6 +18,7 @@ use crate::{ const FONT_SIZE: isize = 16; const PADDING: (f32, f32) = (25., 7.); const PIXELS_TO_METERS: f32 = 1. / 2000.; +const TOAST_AUDIO_WAV: &'static [u8] = include_bytes!("../res/557297.wav"); static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); @@ -79,12 +77,7 @@ impl Toast { .enqueue_at(TaskType::DropOverlay(selector), destroy_at); if has_sound { - if let Some(handle) = app.audio.get_handle() { - let wav = include_bytes!("../res/557297.wav"); - let cursor = Cursor::new(wav); - let source = Decoder::new_wav(cursor).unwrap(); - let _ = handle.play_raw(source.convert_samples()); - } + app.audio.play(TOAST_AUDIO_WAV); } } } diff --git a/src/state.rs b/src/state.rs index fe06790..7b68356 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,8 +1,8 @@ -use std::{path::PathBuf, sync::Arc}; +use std::{io::Cursor, path::PathBuf, sync::Arc}; use anyhow::bail; use glam::Vec3; -use rodio::{OutputStream, OutputStreamHandle}; +use rodio::{Decoder, OutputStream, OutputStreamHandle, Source}; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; use vulkano::format::Format; @@ -126,18 +126,33 @@ impl AudioOutput { } } - pub fn get_handle(&mut self) -> Option<&OutputStreamHandle> { + fn get_handle(&mut self) -> Option<&OutputStreamHandle> { if self.audio_stream.is_none() && self.first_try { self.first_try = false; if let Ok((stream, handle)) = OutputStream::try_default() { self.audio_stream = Some((stream, handle)); } else { - log::error!("Failed to open audio stream"); + log::error!("Failed to open audio stream. Audio will not work."); return None; } } self.audio_stream.as_ref().map(|(_, h)| h) } + + pub fn play(&mut self, wav_bytes: &'static [u8]) { + let Some(handle) = self.get_handle() else { + return; + }; + let cursor = Cursor::new(wav_bytes); + let source = match Decoder::new_wav(cursor) { + Ok(source) => source, + Err(e) => { + log::error!("Failed to play sound: {:?}", e); + return; + } + }; + let _ = handle.play_raw(source.convert_samples()); + } } pub struct ScreenMeta {