#![cfg_attr( all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] use std::{ path::{Path, PathBuf}, sync::{Arc, Mutex}, thread::sleep, time::Duration, }; use audio::monitor; use fs::WebRay; use log::{debug, error, trace, warn}; use serde_json::Value; use tauri::{ api::path::{cache_dir, config_dir}, Manager, State, //, WindowEvent, }; use crate::config::Config; mod audio; mod config; mod fs; const MIC_THRESHOLD: f32 = 0.5f32; struct MicThreshold(Arc>); struct AudioLevel(Arc>); struct BlinkInterval(Arc>); struct CurrentConfig(Arc>); struct RayToLoad(Arc>>); struct MicSense(Arc>); fn main() { env_logger::init(); let threshold = Arc::new(Mutex::new(MIC_THRESHOLD)); let level = Arc::new(Mutex::new(0.)); let blink_interval = Arc::new(Mutex::new(1500)); let ray = Arc::new(Mutex::new(None)); let sens = Arc::new(Mutex::new(1.0)); if let Some(d) = cache_dir() { use std::fs; if let Ok(s) = fs::read_to_string(d.join("cathode").join("last_selected")) { debug!("Found selected ray in cache"); *ray.lock().unwrap() = Some(Path::new(&s).to_path_buf()); } } // Create the config directory if needed let config = config::load_config(); { *blink_interval.lock().unwrap() = config.blink_interval; *sens.lock().unwrap() = config.mic_sens; } let current_conf = Arc::new(Mutex::new(config)); let config = current_conf.clone(); // let c2 = current_conf.clone(); tauri::Builder::default() .manage(MicThreshold(threshold.clone())) .manage(AudioLevel(level.clone())) .manage(BlinkInterval(blink_interval.clone())) .manage(CurrentConfig(current_conf.clone())) .manage(RayToLoad(ray.clone())) .manage(MicSense(sens.clone())) // .on_window_event(move |event| match event.event() { // WindowEvent::CloseRequested { .. } => { // debug!("close requested"); // if let Err(e) = config::save_config(&*c2.lock().unwrap()) { // error!("Error writing config file: {}", e); // } // } // _ => {} // }) .setup(move |app| { let window = app.get_window("main").unwrap(); tauri::async_runtime::spawn(async move { monitor(window, threshold, level, sens).await; }); let window = app.get_window("main").unwrap(); tauri::async_runtime::spawn(async move { loop { if rand::random() { trace!("Blinking"); if let Some(e) = window.emit_all("blink", "").err() { warn!("Failed to emit blink event: {}", e); } } let blink = blink_interval.lock().unwrap(); sleep(Duration::from_millis(*blink)); } }); let window = app.get_window("main").unwrap(); tauri::async_runtime::spawn(async move { use notify::{event::AccessKind, Event, EventKind, RecursiveMode, Result, Watcher}; if let Some(path) = config_dir() { let mut watcher = notify::recommended_watcher(move |res: Result| match res { Ok(event) => { if let EventKind::Access(AccessKind::Close(_)) = event.kind { *config.lock().unwrap() = config::load_config(); window.emit_all("reload-config", "").unwrap(); } } Err(e) => error!("error watching filesystem {:?}", e), }) .unwrap(); watcher .watch( &path.join("cathode").join("config.toml"), RecursiveMode::NonRecursive, ) .unwrap(); loop { sleep(Duration::from_millis(5000)); } } }); match app.get_cli_matches() { Ok(matches) => { trace!("matches OK"); if let Some(arg) = matches.args.get("file") { trace!("CLI arg value: {:?}", arg); if let Value::String(path) = arg.value.clone() { *ray.lock().unwrap() = Some(PathBuf::from(path)); } } } Err(e) => eprintln!("{}", e), } Ok(()) }) .invoke_handler(tauri::generate_handler![ log, set_mic_threshold, get_mic_threshold, set_mic_sens, get_mic_sens, get_audio_level, get_blink_interval, set_blink_interval, get_config, set_config, save_current_config, get_ray_to_load, fs::open_image, fs::save_ray, fs::open_ray, audio::get_devices, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } #[tauri::command] fn get_ray_to_load(ray: State<'_, RayToLoad>) -> Option { let ray = { (*ray.0.lock().unwrap()).clone() }; if let Some(r) = ray { let ray = fs::load_ray(r); ray } else { None } } #[tauri::command] fn log(msg: String) { debug!("frontend: {}", msg); } #[tauri::command] fn set_mic_threshold(threshold: f32, current: State<'_, MicThreshold>) { *current.0.lock().unwrap() = threshold; } #[tauri::command] fn get_mic_threshold(current: State<'_, MicThreshold>) -> f32 { *current.0.lock().unwrap() } #[tauri::command] fn set_mic_sens(sens: f32, current: State<'_, MicSense>) { *current.0.lock().unwrap() = sens; } #[tauri::command] fn get_mic_sens(current: State<'_, MicSense>) -> f32 { *current.0.lock().unwrap() } #[tauri::command] fn get_audio_level(level: State<'_, AudioLevel>) -> f32 { *level.0.lock().unwrap() } #[tauri::command] fn get_blink_interval(current: State<'_, BlinkInterval>) -> u64 { *current.0.lock().unwrap() } #[tauri::command] fn set_blink_interval(value: u64, current: State<'_, BlinkInterval>) { *current.0.lock().unwrap() = value; } #[tauri::command] fn get_config(current: State<'_, CurrentConfig>) -> Config { current.0.lock().unwrap().clone() } #[tauri::command] fn set_config(config: Config, current: State<'_, CurrentConfig>) { *current.0.lock().unwrap() = config } #[tauri::command] fn save_current_config(current: State<'_, CurrentConfig>) -> Result<(), String> { config::save_config(&*current.0.lock().unwrap()).map_err(|e| format!("{}", e)) }