use onelauncher_core::api::proxy::ProxyTauri; use onelauncher_core::api::tauri::{TauRPCLauncherExt, TauriLauncherDebugApi}; use onelauncher_core::error::LauncherResult; use onelauncher_core::store::proxy::ProxyState; use onelauncher_core::store::semaphore::SemaphoreStore; use onelauncher_core::store::{Core, CoreOptions, Dirs, State}; use tauri::{Emitter, Manager}; use crate::api::commands::OneClientApi; use crate::oneclient::initialize_oneclient; pub mod api; pub mod constants; pub mod ext; pub mod oneclient; #[derive(Clone, serde::Serialize)] pub struct SingleInstancePayload { args: Vec, cwd: String, } #[tracing::instrument] async fn initialize_core() -> LauncherResult<()> { let level = if cfg!(debug_assertions) { tracing::Level::DEBUG } else { tracing::Level::INFO }; let opts = CoreOptions { curseforge_api_key: Some(constants::CURSEFORGE_API_KEY.to_string()), launcher_name: "OneClient".to_string(), launcher_version: env!("CARGO_PKG_VERSION").to_string(), launcher_website: "https://polyfrost.org/".to_string(), discord_client_id: Some(constants::DISCORD_CLIENT_ID.to_string()), plus_backend_url: Some(constants::PLUS_BACKEND_URL.to_string()), fetch_attempts: 3, logger_filter: Some(format!( "{}={level},onelauncher_core={level}", env!("CARGO_PKG_NAME") )), ..Default::default() }; Core::initialize(opts).await?; Dirs::get().await?; onelauncher_core::start_logger().await; SemaphoreStore::get().await; tracing::info!("initialized core modules"); let debug_info = onelauncher_core::api::tauri::TauriLauncherDebugApiImpl .get_full_debug_info_parsed_string() .await; tracing::info!("\n{}", debug_info); Ok(()) } #[tracing::instrument(skip_all)] #[allow(clippy::large_stack_frames)] async fn initialize_tauri(builder: tauri::Builder) -> LauncherResult { let router = taurpc::Router::new() .export_config( specta_typescript::Typescript::default() .bigint(specta_typescript::BigIntExportBehavior::Number) .formatter(ext::specta::formatter) .header("// @ts-nocheck\n"), ) .merge(api::commands::OneClientApiImpl.into_handler()) .use_launcher_api(); let builder = builder .plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { println!("{}, {argv:?}, {cwd}", app.package_info().name); app.emit("single-instance", SingleInstancePayload { args: argv, cwd }) .unwrap(); })) .plugin(tauri_plugin_updater::Builder::new().build()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_os::init()); #[cfg(not(target_os = "linux"))] let builder = builder.menu(tauri::menu::Menu::default); let builder = builder .invoke_handler(router.into_handler()) .setup(move |app| { app.manage(ext::updater::UpdaterState::default()); setup_window(app.handle()).expect("failed to setup main window"); #[cfg(desktop)] app.handle() .plugin(tauri_plugin_global_shortcut::Builder::new().build())?; Ok(()) }); let app = builder .build(tauri::generate_context!()) .expect("failed to build tauri application"); Ok(app) } #[tracing::instrument(skip_all)] async fn initialize_state(handle: &tauri::AppHandle) -> LauncherResult<()> { let proxy = ProxyTauri::new(handle.clone()); ProxyState::initialize(proxy).await?; State::get().await?; if let Err(err) = onelauncher_core::api::credentials::refresh_accounts().await { tracing::warn!("failed to refresh accounts on startup: {err}"); } tracing::info!("initialized launcher successfully"); Ok(()) } pub async fn run() { initialize_core().await.expect("failed to initialize core"); let app = initialize_tauri(tauri::Builder::default()) .await .expect("failed to initialize tauri"); initialize_state(app.handle()) .await .expect("failed to initialize state"); initialize_oneclient().await; app.run(|_, _| {}); } fn setup_window(handle: &tauri::AppHandle) -> Result<(), Box> { let win = handle .get_webview_window("main") .ok_or_else(|| anyhow::anyhow!("no window called main was found"))?; ext::updater::init(handle)?; let win_clone = win; #[allow(unused_variables)] let app_handle = handle.clone(); tokio::task::spawn(async move { if State::get().await.is_ok() { // native_window_frame=true means use native decorations // native_window_frame=false means use custom frame (no decorations) #[cfg(target_os = "macos")] { win_clone.set_decorations(true).ok(); let win_weak = win_clone.clone(); app_handle .run_on_main_thread(move || { #[cfg(target_os = "macos")] { use objc2_app_kit::{NSWindow, NSWindowButton}; if let Ok(ns_window_ptr) = win_weak.ns_window() { let ns_window = ns_window_ptr.cast::(); unsafe { let ns_window = &*ns_window; if let Some(btn) = ns_window.standardWindowButton(NSWindowButton::CloseButton) { btn.setHidden(true); } if let Some(btn) = ns_window .standardWindowButton(NSWindowButton::MiniaturizeButton) { btn.setHidden(true); } if let Some(btn) = ns_window.standardWindowButton(NSWindowButton::ZoomButton) { btn.setHidden(true); } } } } }) .ok(); } #[cfg(not(target_os = "macos"))] win_clone.set_decorations(false).ok(); } win_clone.show().ok(); }); Ok(()) }