From fbab841bbd750358fc75604eae83aa79e232c38f Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:24:41 +0200 Subject: [PATCH] feat: Pass stdout + stderr + pid callbacks to frontend --- onelauncher/src/game/client.rs | 52 ++++++++++++++++++++------ onelauncher/src/game/client_manager.rs | 9 +++-- src-tauri/src/api/game.rs | 30 ++++++++++++--- src/bridge/game.ts | 21 ++++++++++- src/ui/pages/Home.tsx | 8 +++- 5 files changed, 97 insertions(+), 23 deletions(-) diff --git a/onelauncher/src/game/client.rs b/onelauncher/src/game/client.rs index 94fc8093..7d6d4902 100644 --- a/onelauncher/src/game/client.rs +++ b/onelauncher/src/game/client.rs @@ -1,10 +1,10 @@ -use std::{io::{stderr, stdout}, path::PathBuf}; +use std::{path::PathBuf, process::Stdio}; use anyhow::anyhow; use async_trait::async_trait; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; -use tokio::process; +use tokio::{io::{AsyncBufReadExt, BufReader}, process}; use uuid::Uuid; use crate::auth::Account; @@ -46,11 +46,17 @@ pub struct SetupInfo { pub natives_dir: PathBuf, } +pub struct LaunchCallbacks { + pub on_launch: Box, + pub on_stdout: Box, + pub on_stderr: Box, +} + #[async_trait] pub trait ClientTrait<'a>: Send + Sync { fn new(cluster: &'a Cluster, manifest: &'a Manifest) -> Self where Self: Sized; - async fn launch(&self, info: LaunchInfo) -> crate::Result<()> { + async fn launch(&self, info: LaunchInfo, mut callbacks: LaunchCallbacks) -> crate::Result { let manifest = &self.get_manifest().minecraft_manifest; let args = get_arguments(manifest)? @@ -63,9 +69,10 @@ pub trait ClientTrait<'a>: Send + Sync { .replace("${auth_access_token}", &info.account.access_token) .replace("${auth_session}", "0") // TODO: Figure out how to get this session ID. .replace("${user_properties}", "{}") // TODO: Figure out these properties. - .replace("${user_type}", "1"); + .replace("${user_type}", "1") + .replace("${version_type}", "OneLauncher"); - process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") + let mut process = process::Command::new(&info.java).arg("-XX:-UseAdaptiveSizePolicy") .arg("-XX:-OmitStackTraceInFastThrow") .arg("-Dminecraft.launcher.brand=onelauncher") .arg("-Dminecraft.launcher.version=1") @@ -76,13 +83,36 @@ pub trait ClientTrait<'a>: Send + Sync { .arg(info.setup.libraries) .arg(manifest.main_class.clone()) .args(args.split_whitespace()) - .stdout(stdout()) // TODO: Implement better logging - .stderr(stderr()) - .spawn()? - .wait() - .await?; + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + let pid = process.id().unwrap_or(0); + (callbacks.on_launch)(pid); + + let stdout = process.stdout.take().ok_or(anyhow!("Couldn't take stdout"))?; + let stderr = process.stderr.take().ok_or(anyhow!("Couldn't take stderr"))?; - Ok(()) + let mut stdout_lines = BufReader::new(stdout).lines(); + let mut stderr_lines = BufReader::new(stderr).lines(); + + let stdout_task = tokio::spawn(async move { + while let Some(line) = stdout_lines.next_line().await.expect("Couldn't read line") { + (callbacks.on_stdout)(line); + } + }); + + let stderr_task = tokio::spawn(async move { + while let Some(line) = stderr_lines.next_line().await.expect("Couldn't read line") { + (callbacks.on_stderr)(line); + } + }); + + let status = process.wait().await?; + tokio::try_join!(stdout_task, stderr_task)?; + + let exit_code = status.code().unwrap_or(0); + Ok(exit_code) } async fn setup(&self) -> crate::Result; diff --git a/onelauncher/src/game/client_manager.rs b/onelauncher/src/game/client_manager.rs index 9b87f873..bbb71f19 100644 --- a/onelauncher/src/game/client_manager.rs +++ b/onelauncher/src/game/client_manager.rs @@ -7,7 +7,7 @@ use uuid::Uuid; use crate::{auth::Account, utils::dirs}; -use super::{client::{ClientTrait, Cluster, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; +use super::{client::{ClientTrait, Cluster, LaunchCallbacks, LaunchInfo, Manifest}, clients::{self, vanilla, ClientType}, java}; #[derive(Debug, thiserror::Error)] pub enum ClientManagerError { @@ -75,7 +75,8 @@ impl ClientManager { pub async fn launch_cluster( &mut self, uuid: Uuid, - ) -> crate::Result<()> { + callbacks: LaunchCallbacks, + ) -> crate::Result { let client = self.get_impl_uuid(uuid)?; let java = java::download_java(&dirs::java_dir()?, client.get_manifest().minecraft_manifest.java_version.major_version).await?; @@ -95,8 +96,8 @@ impl ClientManager { mem_max: 4096, }; - client.launch(info).await?; - Ok(()) + let exit_code = client.launch(info, callbacks).await?; + Ok(exit_code) } pub async fn create_cluster( diff --git a/src-tauri/src/api/game.rs b/src-tauri/src/api/game.rs index b2aa982b..3f7ab183 100644 --- a/src-tauri/src/api/game.rs +++ b/src-tauri/src/api/game.rs @@ -1,5 +1,5 @@ -use onelauncher::game::{client::{Cluster, Manifest}, clients::ClientType}; -use tauri::State; +use onelauncher::game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}; +use tauri::{Manager, State}; use tokio::sync::Mutex; use uuid::Uuid; @@ -41,12 +41,32 @@ pub async fn get_cluster( #[tauri::command] pub async fn launch_cluster( + app: tauri::AppHandle, state: State<'_, Mutex>, uuid: Uuid, -) -> Result<(), String> { +) -> Result { let manager = &mut state.lock().await.client_manager; - manager.launch_cluster(uuid).await?; - Ok(()) + + let on_launch_app = app.clone(); + let on_stdout_app = app.clone(); + let on_stderr_app = app.clone(); + + let callbacks = LaunchCallbacks { + on_launch: Box::new(move |pid| { + on_launch_app.emit("game:launch", pid).expect("Failed to emit game:launched"); + }), + + on_stdout: Box::new(move |line| { + on_stdout_app.emit("game:stdout", line).expect("Failed to emit game:stdout"); + }), + + on_stderr: Box::new(move |line| { + on_stderr_app.emit("game:stderr", line).expect("Failed to emit game:stderr"); + }) + }; + + let exit_code = manager.launch_cluster(uuid, callbacks).await?; + Ok(exit_code) } #[tauri::command] diff --git a/src/bridge/game.ts b/src/bridge/game.ts index 258d5f9f..d7625dba 100644 --- a/src/bridge/game.ts +++ b/src/bridge/game.ts @@ -1,4 +1,5 @@ import { invoke } from '@tauri-apps/api/core'; +import { listen } from '@tauri-apps/api/event'; export async function getCluster(uuid: string): Promise { return await invoke( @@ -61,9 +62,25 @@ export async function refreshClientManager(): Promise { return await invoke('plugin:onelauncher|refresh_client_manager'); } -export async function launchCluster(uuid: string): Promise { - return await invoke( +interface LaunchCallbacks { + on_launch: (pid: number) => any; + on_stdout: (line: string) => any; + on_stderr: (line: string) => any; +}; + +export async function launchCluster(uuid: string, callbacks: LaunchCallbacks): Promise { + const unlisten_launch = await listen('game:launch', e => callbacks.on_launch(e.payload)); + const unlisten_stdout = await listen('game:stdout', e => callbacks.on_stdout(e.payload)); + const unlisten_stderr = await listen('game:stderr', e => callbacks.on_stderr(e.payload)); + + const exit_code = await invoke( 'plugin:onelauncher|launch_cluster', { uuid }, ); + + unlisten_launch(); + unlisten_stdout(); + unlisten_stderr(); + + return exit_code; } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index b1b990a3..d2bdfee4 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -76,7 +76,13 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { } function launch() { - launchCluster(props.id); + const exit_code = launchCluster(props.id, { + on_launch: pid => console.log('Launched with PID:', pid), + on_stdout: line => console.log('STDOUT:', line), + on_stderr: line => console.log('STDERR:', line), + }); + + console.log('Exit with code: ', exit_code); } return (