mod commands; mod lan_game; pub mod log; pub mod serial_core; #[cfg(feature = "devkit")] pub mod devkit; use commands::serial::SerialConnectionState; #[cfg(feature = "devkit")] use tauri::Manager; #[cfg(target_os = "android")] fn usb_serial_plugin() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("usb-serial") .setup(|_app, api| { api.register_android_plugin("com.lenn.tauri_serial", "UsbSerialPlugin")?; Ok(()) }) .build() } #[cfg(feature = "devkit")] fn start_server_exe(exe_path: &std::path::Path) { let mut command = std::process::Command::new(exe_path); command.arg("--port").arg("50051"); #[cfg(target_os = "windows")] { use std::os::windows::process::CommandExt; command.creation_flags(0x08000000); } match command.spawn() { Ok(_) => ::log::info!("DevKit Python server launched: {}", exe_path.display()), Err(error) => ::log::warn!("Failed to start DevKit Python server: {error}"), } } #[cfg(feature = "devkit")] fn is_local_port_open(port: u16) -> bool { use std::net::{SocketAddr, TcpStream}; use std::time::Duration; let addr = SocketAddr::from(([127, 0, 0, 1], port)); TcpStream::connect_timeout(&addr, Duration::from_millis(250)).is_ok() } #[cfg(feature = "devkit")] fn find_server_exe( resource_dir: &std::path::Path, exe_name: &str, ) -> Option { let mut candidates = Vec::new(); candidates.push(resource_dir.join(exe_name)); if let Ok(current_exe) = std::env::current_exe() { if let Some(parent) = current_exe.parent() { candidates.push(parent.join(exe_name)); } } if let Ok(current_dir) = std::env::current_dir() { candidates.push(current_dir.join("src-tauri").join("resources").join(exe_name)); candidates.push(current_dir.join("devkit").join("dist").join(exe_name)); candidates.push(current_dir.join("resources").join(exe_name)); } candidates.into_iter().find(|path| path.exists()) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri::Builder::default() .plugin(tauri_plugin_process::init()) .manage(SerialConnectionState::default()) .plugin(tauri_plugin_opener::init()); #[cfg(target_os = "android")] let builder = builder.plugin(usb_serial_plugin()); #[cfg(not(any(target_os = "android", target_os = "ios")))] let builder = builder.plugin(tauri_plugin_updater::Builder::new().build()); #[cfg(any(target_os = "android", target_os = "ios"))] let builder = builder; #[cfg(feature = "devkit")] let builder = { let devkit_state = devkit::DevKitState::default(); let devkit_state_clone = devkit_state.clone(); builder.manage(devkit_state).setup(move |app| { tauri::async_runtime::spawn(async { if let Err(error) = lan_game::serve().await { ::log::error!("LAN game server failed: {error:?}"); } }); let resource_dir = app .path() .resource_dir() .unwrap_or_else(|_| std::path::PathBuf::from("./resources")); let app_handle = app.handle().clone(); tauri::async_runtime::spawn(async move { let devkit_port = 50051u16; #[cfg(target_os = "windows")] let exe_name = "je-skin-devkit-server.exe"; #[cfg(not(target_os = "windows"))] let exe_name = "je-skin-devkit-server"; if is_local_port_open(devkit_port) { ::log::info!( "DevKit port {} already in use, skipping Python server auto-start", devkit_port ); } else { let server_exe = find_server_exe(&resource_dir, exe_name); if let Some(exe_path) = server_exe { start_server_exe(&exe_path); tokio::time::sleep(std::time::Duration::from_millis(1200)).await; } else { ::log::info!("DevKit Python server not found, skipping auto-start"); } } if let Err(error) = devkit_state_clone.start(app_handle, devkit_port).await { ::log::warn!("DevKit auto-start failed: {error}"); } else { ::log::info!("DevKit gRPC client initialized for 127.0.0.1:{devkit_port}"); } }); Ok(()) }) }; #[cfg(not(feature = "devkit"))] let builder = builder.setup(|_app| { tauri::async_runtime::spawn(async { if let Err(error) = lan_game::serve().await { ::log::error!("LAN game server failed: {error:?}"); } }); Ok(()) }); #[cfg(all(feature = "devkit", not(target_os = "android")))] let builder = builder.invoke_handler(tauri::generate_handler![ commands::file_explorer::file_explorer_list, commands::serial::serial_enum, commands::serial::serial_connect, commands::serial::serial_disconnect, commands::serial::serial_export_csv, commands::serial::serial_has_record_data, commands::serial::serial_export_csv_to_path, commands::serial::serial_import_csv, commands::serial::serial_import_csv_from_path, commands::window::win_minimize, commands::window::win_toggle_maximize, commands::window::win_close, commands::devkit::devkit_status, commands::devkit::devkit_start, commands::devkit::devkit_stop, commands::devkit::devkit_get_config, commands::devkit::devkit_set_config, commands::devkit::devkit_process_export ]); #[cfg(all(not(feature = "devkit"), not(target_os = "android")))] let builder = builder.invoke_handler(tauri::generate_handler![ commands::file_explorer::file_explorer_list, commands::serial::serial_enum, commands::serial::serial_connect, commands::serial::serial_disconnect, commands::serial::serial_export_csv, commands::serial::serial_has_record_data, commands::serial::serial_export_csv_to_path, commands::serial::serial_import_csv, commands::serial::serial_import_csv_from_path, commands::window::win_minimize, commands::window::win_toggle_maximize, commands::window::win_close ]); #[cfg(all(feature = "devkit", target_os = "android"))] let builder = builder.invoke_handler(tauri::generate_handler![ commands::file_explorer::file_explorer_list, commands::serial::serial_enum, commands::serial::serial_connect_fd, commands::serial::serial_disconnect, commands::serial::serial_export_csv, commands::serial::serial_has_record_data, commands::serial::serial_export_csv_to_path, commands::serial::serial_import_csv, commands::serial::serial_import_csv_from_path, commands::window::win_minimize, commands::window::win_toggle_maximize, commands::window::win_close, commands::devkit::devkit_status, commands::devkit::devkit_start, commands::devkit::devkit_stop, commands::devkit::devkit_get_config, commands::devkit::devkit_set_config, commands::devkit::devkit_process_export ]); #[cfg(all(not(feature = "devkit"), target_os = "android"))] let builder = builder.invoke_handler(tauri::generate_handler![ commands::file_explorer::file_explorer_list, commands::serial::serial_enum, commands::serial::serial_connect_fd, commands::serial::serial_disconnect, commands::serial::serial_export_csv, commands::serial::serial_has_record_data, commands::serial::serial_export_csv_to_path, commands::serial::serial_import_csv, commands::serial::serial_import_csv_from_path, commands::window::win_minimize, commands::window::win_toggle_maximize, commands::window::win_close ]); builder .run(tauri::generate_context!()) .expect("error while running tauri application"); }