diff --git a/Cargo.lock b/Cargo.lock index e924b48..2b9aaaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,6 +417,7 @@ dependencies = [ "ctrlc", "derive_more", "directories", + "dotenvy", "image", "interprocess", "open", @@ -425,9 +426,13 @@ dependencies = [ "serde", "serde_qs 0.13.0", "sha256", + "tauri-winrt-notification", "thiserror", + "time", "tokio", "toml", + "tracing", + "tracing-subscriber", "tray-icon", "uuid", "windres", @@ -2379,7 +2384,7 @@ checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if", "libc", - "windows", + "windows 0.52.0", ] [[package]] @@ -2522,7 +2527,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -4467,6 +4472,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quick-xml" version = "0.36.2" @@ -5725,6 +5739,18 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "tauri-winrt-notification" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ecbfeedbd4e41fd66dc3c01951f49af81a824c4f493c02d91b837cf01caf69" +dependencies = [ + "quick-xml 0.31.0", + "thiserror", + "windows 0.58.0", + "windows-version", +] + [[package]] name = "tempfile" version = "3.13.0" @@ -6132,6 +6158,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", + "time", "tracing", "tracing-core", "tracing-log", @@ -6632,7 +6659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.36.2", "quote", ] @@ -6736,7 +6763,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -6749,6 +6786,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -6861,6 +6933,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" diff --git a/avam-client/Cargo.toml b/avam-client/Cargo.toml index e74a291..5d00c85 100644 --- a/avam-client/Cargo.toml +++ b/avam-client/Cargo.toml @@ -11,8 +11,10 @@ config = "0.14.0" ctrlc = "3.4.5" derive_more = { version = "1.0", features = ["full"] } directories = "5.0" +dotenvy = "0.15.7" image = "0.25" interprocess = { version = "2.2.1", features = ["tokio"] } +tauri-winrt-notification = "0.6.0" open = "5.3.0" rand = "0.8.5" reqwest = { version = "0.12.8", default-features = false, features = [ @@ -23,8 +25,11 @@ serde = { version = "1", features = ["derive"] } serde_qs = "0.13.0" sha256 = "1.5.0" thiserror = { version = "1.0" } +time = "0.3.36" tokio = { version = "1.40.0", features = ["full"] } toml = "0.8" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["time"] } tray-icon = "0.19" uuid = { version = "1.10.0", features = ["fast-rng", "serde", "v4"] } winit = "0.30" diff --git a/avam-client/src/app.rs b/avam-client/src/app.rs index c746381..85131eb 100644 --- a/avam-client/src/app.rs +++ b/avam-client/src/app.rs @@ -5,11 +5,25 @@ use tray_icon::menu::{MenuId, MenuItem}; use winit::{ application::ApplicationHandler, event::StartCause, - event_loop::{ActiveEventLoop, ControlFlow}, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, }; use crate::{config::Config, icon::TrayIcon, oauth, state_machine::Event, BASE_URL}; +pub async fn start( + config: Config, + sender: Sender, + receiver: Receiver, +) -> Result<(), anyhow::Error> { + let mut app = App::new(config, sender, receiver)?; + let event_loop = EventLoop::new()?; + + event_loop.run_app(&mut app)?; + tracing::info!("EventLoop Shutdown"); + + Ok(()) +} + pub struct App { config: Config, tray_icon: TrayIcon, @@ -112,7 +126,7 @@ impl ApplicationHandler for App { .unwrap(); } Event::Quit => { - println!("Shutting down EventLoop"); + tracing::info!("Shutting down EventLoop"); event_loop.exit() } _ => {} @@ -126,6 +140,16 @@ impl ApplicationHandler for App { let _ = self.sender.send(Event::Ready { config: self.config.clone(), }); + + if !self.config.toast_shown() { + tauri_winrt_notification::Toast::new(crate::AVAM_APP_ID) + .title("Avam lives in the system tray!") + .text1("(╯°□°)╯︵ ┻━┻") + .show() + .expect("unable to toast"); + } + + let _ = self.config.set_toast_shown(true); } fn window_event( diff --git a/avam-client/src/config.rs b/avam-client/src/config.rs index b95289c..6ca8178 100644 --- a/avam-client/src/config.rs +++ b/avam-client/src/config.rs @@ -15,6 +15,8 @@ use crate::{ pub struct Config { #[serde(with = "arc_rwlock_serde")] token: Arc>>, + #[serde(with = "arc_rwlock_serde")] + toast_shown: Arc>, #[serde(skip)] code_verifier: Arc>>, #[serde(skip)] @@ -27,6 +29,7 @@ impl Default for Config { fn default() -> Self { Self { token: Default::default(), + toast_shown: Default::default(), code_verifier: Default::default(), code_challenge_method: Default::default(), open_browser: Arc::new(RwLock::new(true)), @@ -49,6 +52,8 @@ pub enum ConfigError { #[error(transparent)] Toml(#[from] toml::ser::Error), #[error(transparent)] + Config(#[from] config::ConfigError), + #[error(transparent)] Unknown(#[from] anyhow::Error), } @@ -64,6 +69,18 @@ impl Config { } } +impl Config { + pub(crate) fn toast_shown(&self) -> bool { + *self.toast_shown.read().unwrap() + } + + pub(crate) fn set_toast_shown(&self, value: bool) -> Result<(), ConfigError> { + *self.toast_shown.write().unwrap() = value; + self.write()?; + Ok(()) + } +} + impl Config { pub fn set_code_verifier( &self, @@ -110,7 +127,12 @@ impl Config { .build() .unwrap(); - let config: Self = config.try_deserialize().unwrap_or_default(); + let token = config.get_string("token").ok(); + let toast_shown = config.get_bool("toast_shown").unwrap_or_default(); + let config = Self::default(); + + config.set_token(token)?; + config.set_toast_shown(toast_shown)?; config.write()?; diff --git a/avam-client/src/dirs.rs b/avam-client/src/dirs.rs index ff1d8d6..8a1d3aa 100644 --- a/avam-client/src/dirs.rs +++ b/avam-client/src/dirs.rs @@ -40,4 +40,23 @@ impl Dirs { c.push(".lock"); Ok(c) } + + #[cfg(not(debug_assertions))] + pub fn get_log_dir() -> Result { + let c = Self::get_config_dir()?; + let mut log_dir = c.parent().unwrap().to_path_buf(); + log_dir.push("logs"); + if !log_dir.exists() { + std::fs::create_dir_all(&log_dir)?; + } + + Ok(log_dir.to_path_buf()) + } + + #[cfg(not(debug_assertions))] + pub fn get_log_file() -> Result { + let mut l = Self::get_log_dir()?; + l.push("avam.log"); + Ok(l) + } } diff --git a/avam-client/src/icon.rs b/avam-client/src/icon.rs index 61385b6..ec386ff 100644 --- a/avam-client/src/icon.rs +++ b/avam-client/src/icon.rs @@ -97,7 +97,7 @@ impl TrayIcon { if let Some(item) = self.menu_items.get(id) { if let Some(i) = self.menu.items().iter().find(|i| i.id() == id) { if let Err(e) = item(i) { - eprintln!("{:#?}", e); + tracing::error!("{:#?}", e); } } } diff --git a/avam-client/src/main.rs b/avam-client/src/main.rs index 25a2aee..e4e4e9c 100644 --- a/avam-client/src/main.rs +++ b/avam-client/src/main.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![allow(clippy::needless_return)] mod app; @@ -17,7 +18,9 @@ use oauth::{start_code_listener, start_code_to_token}; use pipe::Pipe; use state_machine::Event; use tokio::task::JoinSet; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +pub static AVAM_APP_ID: &str = "AvamToast-ECEB71694A5E6105"; pub static BASE_URL: &str = "https://avam.avii.nl"; pub static PROJECT_NAME: &str = "Avii's Virtual Airline Manager"; pub static COPYRIGHT: &str = "Avii's Virtual Airline Manager © 2024"; @@ -35,6 +38,10 @@ pub struct Arguments { #[tokio::main] async fn main() -> Result<(), anyhow::Error> { + dotenvy::dotenv().ok(); + + init_logging()?; + let (event_sender, event_receiver) = tokio::sync::broadcast::channel(1); let args = Arguments::parse(); @@ -43,6 +50,7 @@ async fn main() -> Result<(), anyhow::Error> { } register_url_scheme()?; + register_notification_handler()?; let config = Config::new()?; @@ -52,7 +60,7 @@ async fn main() -> Result<(), anyhow::Error> { let sender = event_sender.clone(); let mut ctrl_c_counter = 0; ctrlc::set_handler(move || { - println!("CTRL_C: Quit singal sent"); + tracing::info!("CTRL_C: Quit singal sent"); ctrl_c_counter += 1; if ctrl_c_counter >= 3 { let _ = unregister_url_scheme(); @@ -61,7 +69,7 @@ async fn main() -> Result<(), anyhow::Error> { } if let Err(e) = sender.send(Event::Quit) { - println!("{:#?}", e) + tracing::error!("{:#?}", e) }; })?; @@ -86,7 +94,7 @@ async fn main() -> Result<(), anyhow::Error> { let c = config.clone(); let sender = event_sender.clone(); let receiver = event_receiver.resubscribe(); - start_tray_icon(c, sender, receiver).await?; + app::start(c, sender, receiver).await?; // Wait for everything to finish while let Some(result) = futures.join_next().await { @@ -97,28 +105,12 @@ async fn main() -> Result<(), anyhow::Error> { // Cleanup unregister_url_scheme()?; + unregister_notification_handler()?; Lock::unlock(); Ok(()) } -use app::App; -use tokio::sync::broadcast::{Receiver, Sender}; -use winit::event_loop::EventLoop; -async fn start_tray_icon( - config: Config, - sender: Sender, - receiver: Receiver, -) -> Result<(), anyhow::Error> { - let mut app = App::new(config, sender, receiver)?; - let event_loop = EventLoop::new()?; - - event_loop.run_app(&mut app)?; - println!("EventLoop Shutdonw"); - - Ok(()) -} - // // // @@ -178,3 +170,72 @@ fn unregister_url_scheme() -> Result<(), anyhow::Error> { Ok(()) } + +fn register_notification_handler() -> Result<(), anyhow::Error> { + use winreg::enums::*; + use winreg::RegKey; + + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let avam_schema_root = hkcu.create_subkey(format!( + "Software\\Classes\\AppUserModelId\\{}", + AVAM_APP_ID + ))?; + let current_exec = std::env::current_exe()?; + let mut icon = current_exec.parent().unwrap().to_path_buf(); + icon.push("icon.png"); + + avam_schema_root + .0 + .set_value("DisplayName", &crate::PROJECT_NAME)?; + avam_schema_root.0.set_value("IconBackgroundColor", &"0")?; + avam_schema_root + .0 + .set_value("IconUri", &icon.to_str().unwrap())?; + + Ok(()) +} + +fn unregister_notification_handler() -> Result<(), anyhow::Error> { + use winreg::enums::*; + use winreg::RegKey; + + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + hkcu.delete_subkey_all(format!( + "Software\\Classes\\AppUserModelId\\{}", + AVAM_APP_ID + )) + .ok(); + + Ok(()) +} + +fn init_logging() -> Result<(), anyhow::Error> { + #[cfg(not(debug_assertions))] + use dirs::Dirs; + + #[cfg(not(debug_assertions))] + use std::{ + fs::{self, File}, + sync::Arc, + }; + + #[cfg(not(debug_assertions))] + let log_file = Dirs::get_log_file()?; + + #[cfg(not(debug_assertions))] + if !log_file.exists() { + fs::write(&log_file, "")?; + } + + #[cfg(not(debug_assertions))] + let file = File::options().append(true).open(&log_file)?; + + let fmt = tracing_subscriber::fmt::layer(); + + #[cfg(not(debug_assertions))] + let fmt = fmt.with_ansi(false).with_writer(Arc::new(file)); + + tracing_subscriber::registry().with(fmt).init(); + + Ok(()) +} diff --git a/avam-client/src/oauth.rs b/avam-client/src/oauth.rs index 4285829..d310f6b 100644 --- a/avam-client/src/oauth.rs +++ b/avam-client/src/oauth.rs @@ -60,7 +60,7 @@ pub async fn start_code_to_token( tokio::select! { _ = sleep(Duration::from_millis(100)) => { if let Ok(Event::Quit) = event_receiver.try_recv() { - println!("Shutting down Code Transformer"); + tracing::info!("Shutting down Code Transformer"); break; } } @@ -90,6 +90,6 @@ pub async fn start_code_to_token( } } - println!("Code Transformer Shutdown"); + tracing::info!("Code Transformer Shutdown"); Ok(()) } diff --git a/avam-client/src/pipe.rs b/avam-client/src/pipe.rs index 4205cff..c515a8a 100644 --- a/avam-client/src/pipe.rs +++ b/avam-client/src/pipe.rs @@ -61,20 +61,20 @@ impl Pipe { let new_sender = pipe_sender.clone(); tokio::spawn(async move { if let Err(e) = Self::handle_conn(conn, new_sender).await { - eprintln!("error while handling connection: {e}"); + tracing::error!("error while handling connection: {e}"); } }); }, _ = sleep(Duration::from_millis(100)) => { if let Ok(Event::Quit) = quit_signal.try_recv() { - println!("Shutting down Code Listener"); + tracing::info!("Shutting down Code Listener"); break; } } } } - println!("Code Listener Shutdown"); + tracing::info!("Code Listener Shutdown"); Ok(()) } diff --git a/avam-client/src/state_machine.rs b/avam-client/src/state_machine.rs index 2686406..af84914 100644 --- a/avam-client/src/state_machine.rs +++ b/avam-client/src/state_machine.rs @@ -137,7 +137,7 @@ pub async fn start( loop { if let Ok(event) = event_receiver.recv().await { if event == Event::Quit { - println!("Shutting down State Machine"); + tracing::info!("Shutting down State Machine"); break; } @@ -153,6 +153,6 @@ pub async fn start( } } - println!("State Machine Shutdown"); + tracing::info!("State Machine Shutdown"); Ok(()) }