#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![allow(clippy::needless_return)] mod app; mod client; mod config; mod dirs; mod icon; mod lock; mod models; mod oauth; mod pipe; mod state_machine; use crate::config::Config; use clap::Parser; use lock::Lock; use oauth::{start_code_listener, start_code_to_token}; use pipe::Pipe; use state_machine::Event; use tokio::{sync::broadcast::channel, task::JoinSet}; use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer}; 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"; pub static CLIENT_ID: uuid::Uuid = uuid::uuid!("f9525060-0a34-4233-87e2-0f9990b7c6db"); pub static REDIRECT_URI: &str = "avam:token"; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] pub struct Arguments { #[arg(short, long)] code: Option, #[arg(short, long, action)] force: bool, } #[tokio::main] async fn main() -> Result<(), anyhow::Error> { dotenvy::dotenv().ok(); init_logging()?; let (event_sender, event_receiver) = channel(10); let args = Arguments::parse(); if handle_single_instance(&args).await? { return Ok(()); } register_url_scheme()?; register_notification_handler()?; let config = Config::new()?; let mut futures = JoinSet::new(); // Register Quit handler let sender = event_sender.clone(); let mut ctrl_c_counter = 0; ctrlc::set_handler(move || { tracing::info!("CTRL_C: Quit singal sent"); ctrl_c_counter += 1; if ctrl_c_counter >= 3 { let _ = unregister_url_scheme(); Lock::unlock(); std::process::exit(1); } if let Err(e) = sender.send(Event::Quit) { tracing::error!("{:#?}", e) }; })?; // Start the State Machine let c = config.clone(); let sender = event_sender.clone(); let receiver = event_receiver.resubscribe(); futures.spawn(state_machine::start(c, sender, receiver)); // // Start the code listener let receiver = event_receiver.resubscribe(); let (pipe_sender, pipe_receiver) = tokio::sync::broadcast::channel(10); futures.spawn(start_code_listener(pipe_sender, receiver)); // Start token listener let c = config.clone(); let sender = event_sender.clone(); let receiver = event_receiver.resubscribe(); futures.spawn(start_code_to_token(c, pipe_receiver, sender, receiver)); // Prepare channels for socket <-> simconnect let (simconnect_sender, __simconnect_receiver) = channel(10); let (__socket_sender, socket_receiver) = channel(10); // Start the websocket client let c = config.clone(); let sender = event_sender.clone(); let receiver = event_receiver.resubscribe(); futures.spawn(client::start( c, simconnect_sender, socket_receiver, sender, receiver, )); // Start the simconnect listener // The simconnect sends data to the webscoket // It also receives data from the websocket to do things like set plane id and fuel and such things // If possible even position // Start the Tray Icon let c = config.clone(); let sender = event_sender.clone(); let receiver = event_receiver.resubscribe(); app::start(c, sender, receiver).await?; // Wait for everything to finish while let Some(result) = futures.join_next().await { if let Ok(Err(e)) = result { panic!("{:#?}", e); } } // Cleanup unregister_url_scheme()?; unregister_notification_handler()?; Lock::unlock(); Ok(()) } // // // // // // // // // // // // /// returns `Ok(true)` if we need to quit gracefully /// returns `Err(e)` if we're already running async fn handle_single_instance(args: &Arguments) -> Result { let lock = Lock::new(args.force)?; if lock.is_locked() && args.code.is_none() { return Err(anyhow::anyhow!("Lockfile exists, exiting.")); } if let Some(code) = &args.code { Pipe::send(code).await?; return Ok(true); } Lock::lock(); Ok(false) } fn register_url_scheme() -> Result<(), anyhow::Error> { use winreg::enums::*; use winreg::RegKey; let hkcu = RegKey::predef(HKEY_CURRENT_USER); let avam_schema_root = hkcu.create_subkey("Software\\Classes\\avam")?; avam_schema_root.0.set_value("URL Protocol", &"")?; let command = avam_schema_root.0.create_subkey("shell\\open\\command")?; let current_exec = std::env::current_exe()?; command.0.set_value( "", &format!("\"{}\" -c \"%1\"", current_exec.to_str().unwrap()), )?; Ok(()) } fn unregister_url_scheme() -> Result<(), anyhow::Error> { use winreg::enums::*; use winreg::RegKey; let hkcu = RegKey::predef(HKEY_CURRENT_USER); hkcu.delete_subkey_all("Software\\Classes\\avam").ok(); 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().with_filter(tracing_subscriber::filter::filter_fn( |metadata| metadata.level() < &Level::TRACE, )); #[cfg(not(debug_assertions))] let fmt = fmt.with_ansi(false).with_writer(Arc::new(file)); tracing_subscriber::registry().with(fmt).init(); Ok(()) }