Add launcher preferences and modular landing studio [skip ci]

This commit is contained in:
SCOPEDD committed 2026-09-30 06:48:03 -04:00
1 parent 454620f21e
commit 6deff62a3a
25 files changed
+822 -682

No files matched your search

+41 -12
View File
@@ -19,6 +19,7 @@ use tokio::io::{AsyncBufReadExt, BufReader};
#[derive(Serialize, Clone)]
pub struct StatePayload {
pub instance_id: String,
pub run_id: Option<String>,
/// "preparing" | "running" | "idle" | "error"
pub state: String,
pub message: Option<String>,
@@ -27,6 +28,7 @@ pub struct StatePayload {
#[derive(Serialize, Clone)]
pub struct ExitPayload {
pub instance_id: String,
pub run_id: String,
pub code: Option<i32>,
pub crashed: bool,
pub tail: Vec<String>,
@@ -34,7 +36,7 @@ pub struct ExitPayload {
}
pub fn emit_state(app: &AppHandle, id: &str, state: &str, message: Option<String>) {
app.emit("launch://state", StatePayload { instance_id: id.into(), state: state.into(), message }).ok();
app.emit("launch://state", StatePayload { instance_id: id.into(), run_id: None, state: state.into(), message }).ok();
}
/// Fetch the instance definition, falling back to the last copy we saw so
@@ -119,7 +121,19 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
deep_verify: deep,
};
let installed = install::install(&state.http, &state.layout, &spec, &report).await?;
sync::sync(&state.http, &panel, &game_dir, inst.revision, &manifest.files, spec.concurrency, deep, &report).await?;
let already_running = state.game.lock().unwrap().iter().any(|g| g.instance_id == instance_id);
// The on-disk file may be newer when the launcher was closed before Minecraft.
let current_options = options::read_vanilla_preferences(&game_dir).unwrap_or_default();
let mut saved_options = if current_options.is_empty() {
settings.instance_game_options.get(&instance_id).cloned().unwrap_or_default()
} else {
current_options
};
// Do not rewrite pack files or options while another copy is using this directory.
if !already_running {
sync::sync(&state.http, &panel, &game_dir, inst.revision, &manifest.files, spec.concurrency, deep, &report).await?;
options::apply(&game_dir, &mut saved_options)?;
}
if let Some(server) = &inst.server {
if server.inject && !server.address.is_empty() {
@@ -127,7 +141,13 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
servers_dat::upsert(&game_dir, &name, &servers_dat::format_address(&server.address, server.port)).ok();
}
}
if settings.keybinds_enabled && installed.version.is_modern() && !settings.keybinds.is_empty() {
if !already_running && settings.vanilla_controls_enabled {
let mut controls = std::collections::BTreeMap::new();
controls.insert("autoJump".to_string(), settings.auto_jump.to_string());
controls.insert("mouseSensitivity".to_string(), settings.sensitivity.clamp(0.0, 1.0).to_string());
options::apply(&game_dir, &mut controls)?;
}
if !already_running && settings.keybinds_enabled && installed.version.is_modern() && !settings.keybinds.is_empty() {
options::apply_keybinds(&game_dir, &settings.keybinds).ok();
}
@@ -203,8 +223,9 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
}
let (kill_tx, kill_rx) = tokio::sync::oneshot::channel();
*state.game.lock().unwrap() = Some(RunningGame { instance_id: instance_id.clone(), kill: kill_tx });
emit_state(&app, &instance_id, "running", None);
let run_id = uuid::Uuid::new_v4().to_string();
state.game.lock().unwrap().push(RunningGame { run_id: run_id.clone(), instance_id: instance_id.clone(), kill: Some(kill_tx) });
app.emit("launch://state", StatePayload { instance_id: instance_id.clone(), run_id: Some(run_id.clone()), state: "running".into(), message: None }).ok();
if let Some(window) = app.get_webview_window("main") {
match settings.after_launch.as_str() {
@@ -241,6 +262,7 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
let app2 = app.clone();
let id2 = instance_id.clone();
let run_id2 = run_id.clone();
tokio::spawn(async move {
let mut tail: VecDeque<String> = VecDeque::with_capacity(200);
let mut batch: Vec<String> = Vec::new();
@@ -297,15 +319,22 @@ pub async fn run(app: AppHandle, instance_id: String, start: bool, deep: bool) -
},
&id2,
);
*st.game.lock().unwrap() = None;
if let Some(w) = app2.get_webview_window("main") {
w.show().ok();
w.unminimize().ok();
if crashed {
w.set_focus().ok();
let preferences = options::read_vanilla_preferences(&st.layout.instance_dir(&id2)).unwrap_or_default();
if !preferences.is_empty() {
st.settings.write().unwrap().instance_game_options.insert(id2.clone(), preferences);
st.save_settings().ok();
}
st.game.lock().unwrap().retain(|g| g.run_id != run_id2);
if st.game.lock().unwrap().is_empty() {
if let Some(w) = app2.get_webview_window("main") {
w.show().ok();
w.unminimize().ok();
if crashed {
w.set_focus().ok();
}
}
}
app2.emit("game://exit", ExitPayload { instance_id: id2.clone(), code, crashed, tail: tail.into_iter().collect(), crash_report })
app2.emit("game://exit", ExitPayload { instance_id: id2.clone(), run_id: run_id2, code, crashed, tail: tail.into_iter().collect(), crash_report })
.ok();
emit_state(&app2, &id2, "idle", None);
});