From ba74bedf92f580b0de13b82f0aa937ef9b95894e Mon Sep 17 00:00:00 2001
From: Maciek <108873945+rozwader@users.noreply.github.com>
Date: Sun, 13 Sep 2026 00:37:40 +0200
Subject: [PATCH] feat: added microsoft jdk and new shared mods folder layout
(#812)
* feat: added microsoft sdk provider
* feat: changed the default provider to microsoft everywhere
* feat: added checking for microsoft jdks and prompting user to install them (ui for discussion)
* feat: added toast popup when user tries to remove runtime that is pinned to some cluster
* feat: added 'migrating' user to the microsoft jdks
* feat: changed reading mods from cluster folders on launch to having cluster folders symlinks in the shared .minecraft folder where mods stay per given cluster; changed resource packs and shader packs from working per cluster to global, every resource pack and shader pack is now being shared between clusters; added mods disabling whenever a mod is manually removed from the mods folder
* fix/feat: fixed hardlinks counting as a duplicate of files it was pointing to (windows); added a progress bar to the disk storage view in settings
* chore: connected microsoft jdk branch and package folder management one
---------
Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
---
Cargo.lock | 2 +
Cargo.toml | 1 +
.../src/components/generic_prompt.rs | 21 +-
.../src/components/java_install_manager.rs | 1 +
.../src/components/microsoft_java_prompt.rs | 189 +++
packages/oneclient_app/src/components/mod.rs | 2 +
.../src/components/overlay_popup.rs | 5 +-
packages/oneclient_app/src/events.rs | 22 +-
packages/oneclient_app/src/hooks/actions.rs | 139 ++-
packages/oneclient_app/src/hooks/mod.rs | 9 +-
.../oneclient_app/src/layout/root_layout.rs | 3 +-
packages/oneclient_app/src/lib.rs | 1 +
packages/oneclient_app/src/main.rs | 7 +-
packages/oneclient_app/src/microsoft_java.rs | 357 ++++++
packages/oneclient_app/src/notifications.rs | 4 +-
packages/oneclient_app/src/state.rs | 9 +
.../view/app/cluster/package_manager/mod.rs | 5 +
.../view/app/cluster/package_manager/views.rs | 10 +
.../src/view/app/settings/storage.rs | 64 +-
packages/oneclient_cluster/src/cluster.rs | 17 +
packages/oneclient_cluster/src/lib.rs | 2 +-
packages/oneclient_cluster/src/manager.rs | 4 +-
packages/oneclient_common/src/domain.rs | 6 +
packages/oneclient_common/src/paths.rs | 16 +
.../oneclient_content/src/bundles/install.rs | 47 +-
.../src/packages/store/link.rs | 80 +-
.../src/packages/store/manifest.rs | 62 +-
.../src/packages/store/mod.rs | 4 +
packages/oneclient_core/src/clusters/mod.rs | 4 +-
.../oneclient_core/src/clusters/prepare.rs | 29 +
.../src/clusters/unlink_legacy.rs | 52 +-
packages/oneclient_core/src/game/fabric.rs | 109 ++
packages/oneclient_core/src/game/heal.rs | 21 +
packages/oneclient_core/src/game/launch.rs | 30 +-
packages/oneclient_core/src/game/mod.rs | 2 +
.../oneclient_core/src/game/shared_dir.rs | 1108 +++++++++++++++--
packages/oneclient_core/src/lib.rs | 2 +-
packages/oneclient_core/src/recovery.rs | 8 +
packages/oneclient_core/src/relocate.rs | 28 +-
.../oneclient_core/src/settings/launcher.rs | 4 +
packages/oneclient_core/src/storage.rs | 181 ++-
packages/oneclient_db/src/dao/artifact.rs | 53 +
packages/oneclient_db/src/models/artifact.rs | 8 +
packages/oneclient_db/src/models/mod.rs | 2 +-
packages/oneclient_java/Cargo.toml | 1 +
.../examples/install_provider.rs | 9 +-
packages/oneclient_java/src/service.rs | 143 ++-
.../oneclient_java/src/vendors/microsoft.rs | 286 +++++
packages/oneclient_java/src/vendors/mod.rs | 12 +
packages/polyio/Cargo.toml | 1 +
packages/polyio/src/file.rs | 96 +-
51 files changed, 3013 insertions(+), 265 deletions(-)
create mode 100644 packages/oneclient_app/src/components/microsoft_java_prompt.rs
create mode 100644 packages/oneclient_app/src/microsoft_java.rs
create mode 100644 packages/oneclient_core/src/game/fabric.rs
create mode 100644 packages/oneclient_java/src/vendors/microsoft.rs
diff --git a/Cargo.lock b/Cargo.lock
index 7d1c1d28..d0e64284 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4706,6 +4706,7 @@ dependencies = [
"async-trait",
"async_zip",
"futures-lite",
+ "futures-util",
"oneclient_common",
"oneclient_events",
"oneclient_net",
@@ -5073,6 +5074,7 @@ dependencies = [
"tokio",
"tokio-util",
"tracing",
+ "windows-sys 0.61.2",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index f87daa21..36e5d3c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -129,6 +129,7 @@ trash = {version = "=5.2.6"}
percent-encoding = {version = "=2.3.2"}
url = {version = "=2.5.8", features = ["serde"]}
uuid = {version = "=1.23.1", features = ["serde", "v4"]}
+windows-sys = {version = "=0.61.2", features = ["Win32_Foundation", "Win32_Storage_FileSystem"]}
# util (codegen/macro)
anyhow = {version = "=1.0.102"}
diff --git a/packages/oneclient_app/src/components/generic_prompt.rs b/packages/oneclient_app/src/components/generic_prompt.rs
index e1468bf0..96b22e2b 100644
--- a/packages/oneclient_app/src/components/generic_prompt.rs
+++ b/packages/oneclient_app/src/components/generic_prompt.rs
@@ -7,6 +7,7 @@ use oneclient_java::JAVA_CHOICE_DOWNLOAD;
use crate::components::{Button, OverlayPopup};
use crate::hooks::{use_dispatch, use_notifications_snapshot};
+use crate::microsoft_java::MICROSOFT_JAVA_CHOICE_INSTALL;
use crate::notifications::PendingPromptView;
use crate::theme::colors;
use crate::ui::border_all_color;
@@ -15,7 +16,9 @@ use crate::updater::UPDATE_CHOICE_INSTALL;
const CARD_BG: Color = Color::from_rgb(26, 34, 41);
fn claimed_elsewhere(prompt: &PendingPromptView) -> bool {
- prompt.has_choice(JAVA_CHOICE_DOWNLOAD) || prompt.has_choice(UPDATE_CHOICE_INSTALL)
+ prompt.has_choice(JAVA_CHOICE_DOWNLOAD)
+ || prompt.has_choice(UPDATE_CHOICE_INSTALL)
+ || prompt.has_choice(MICROSOFT_JAVA_CHOICE_INSTALL)
}
#[derive(PartialEq)]
@@ -149,15 +152,17 @@ mod tests {
}
#[test]
- fn the_two_rich_overlays_keep_their_own_prompts() {
- assert!(claimed_elsewhere(&prompt(vec![Choice::primary(
+ fn the_rich_overlays_keep_their_own_prompts() {
+ for id in [
JAVA_CHOICE_DOWNLOAD,
- "Download",
- )])));
- assert!(claimed_elsewhere(&prompt(vec![Choice::primary(
UPDATE_CHOICE_INSTALL,
- "Download",
- )])));
+ MICROSOFT_JAVA_CHOICE_INSTALL,
+ ] {
+ assert!(
+ claimed_elsewhere(&prompt(vec![Choice::primary(id, "Download")])),
+ "{id} has its own overlay"
+ );
+ }
}
#[test]
diff --git a/packages/oneclient_app/src/components/java_install_manager.rs b/packages/oneclient_app/src/components/java_install_manager.rs
index 16e5987e..0f28d586 100644
--- a/packages/oneclient_app/src/components/java_install_manager.rs
+++ b/packages/oneclient_app/src/components/java_install_manager.rs
@@ -8,6 +8,7 @@ use crate::ui::border_all_color;
fn providers() -> Vec<(JavaVendor, &'static str)> {
vec![
+ (JavaVendor::Microsoft, "Microsoft Build of OpenJDK"),
(JavaVendor::Zulu, "Azul Zulu"),
(JavaVendor::Adoptium, "Eclipse Temurin"),
(JavaVendor::Corretto, "Amazon Corretto"),
diff --git a/packages/oneclient_app/src/components/microsoft_java_prompt.rs b/packages/oneclient_app/src/components/microsoft_java_prompt.rs
new file mode 100644
index 00000000..10e6513d
--- /dev/null
+++ b/packages/oneclient_app/src/components/microsoft_java_prompt.rs
@@ -0,0 +1,189 @@
+use std::time::Duration;
+
+use freya::prelude::*;
+use oneclient_events::Answer;
+
+use crate::components::{Button, Icon, IconType, OverlayPopup};
+use crate::hooks::{use_dispatch, use_notifications_snapshot};
+use crate::microsoft_java::{MICROSOFT_JAVA_CHOICE_INSTALL, MICROSOFT_JAVA_CHOICE_NEVER};
+use crate::theme::colors;
+use crate::ui::border_all_color;
+
+const CARD_BG: Color = Color::from_rgb(26, 34, 41);
+
+const HOLD_SECONDS: u8 = 3;
+
+#[derive(PartialEq)]
+pub struct MicrosoftJavaPromptOverlay;
+
+impl Component for MicrosoftJavaPromptOverlay {
+ fn render(&self) -> impl IntoElement {
+ let snapshot = use_notifications_snapshot();
+ let dispatch = use_dispatch();
+ let remaining = use_state(|| HOLD_SECONDS);
+
+ let showing = snapshot
+ .pending_prompt
+ .as_ref()
+ .is_some_and(|prompt| prompt.has_choice(MICROSOFT_JAVA_CHOICE_INSTALL));
+
+ use_side_effect_with_deps(&showing, move |&showing| {
+ let mut remaining = remaining;
+
+ if !showing {
+ remaining.set(HOLD_SECONDS);
+ return;
+ }
+
+ spawn(async move {
+ let mut remaining = remaining;
+ for step in (0..HOLD_SECONDS).rev() {
+ tokio::time::sleep(Duration::from_secs(1)).await;
+
+ // Re-armed while this was sleeping so a newer prompt owns the
+ // hold now and this countdown is stale
+ if { *remaining.peek() } != step + 1 {
+ return;
+ }
+
+ remaining.set(step);
+ }
+ });
+ });
+
+ let Some(prompt) = snapshot.pending_prompt.clone() else {
+ return rect().into_element();
+ };
+
+ if !prompt.has_choice(MICROSOFT_JAVA_CHOICE_INSTALL) {
+ return rect().into_element();
+ }
+
+ let held = *remaining.read();
+ let locked = held > 0;
+
+ let dismiss_label = prompt
+ .dismiss
+ .clone()
+ .unwrap_or_else(|| "Cancel".to_string());
+ let never_label = prompt
+ .choice(MICROSOFT_JAVA_CHOICE_NEVER)
+ .map(|choice| choice.label.clone())
+ .unwrap_or_else(|| "Don't ask again".to_string());
+ let install_label = prompt
+ .choice(MICROSOFT_JAVA_CHOICE_INSTALL)
+ .map(|choice| choice.label.clone())
+ .unwrap_or_else(|| "Proceed".to_string());
+
+ let close = dispatch.clone();
+ let cancel = dispatch.clone();
+ let never = dispatch.clone();
+ let accept = dispatch.clone();
+
+ OverlayPopup::new()
+ // Closing from the backdrop is a dismissal too so it waits as well
+ .on_close(move |_| {
+ if !locked {
+ close.dismiss_prompt();
+ }
+ })
+ .child(
+ rect()
+ .width(Size::window_percent(100.))
+ .height(Size::window_percent(100.))
+ .center()
+ .child(
+ rect()
+ .vertical()
+ .width(Size::px(440.))
+ .max_width(Size::window_percent(90.))
+ .spacing(14.)
+ .padding(Gaps::new_all(20.))
+ .corner_radius(CornerRadius::new_all(14.))
+ .background(CARD_BG)
+ .border(border_all_color(1., colors::component_border()))
+ .child(
+ rect()
+ .horizontal()
+ .cross_align(Alignment::Center)
+ .spacing(10.)
+ .child(
+ Icon::new(IconType::DownloadCloud02)
+ .size(20.)
+ .color(colors::brand()),
+ )
+ .child(
+ label()
+ .text(prompt.title.clone())
+ .font_size(16.)
+ .font_weight(FontWeight::SEMI_BOLD)
+ .color(colors::fg_primary()),
+ ),
+ )
+ .child(
+ label()
+ .text(prompt.question.clone())
+ .font_size(12.)
+ .max_lines(6)
+ .width(Size::fill())
+ .color(colors::fg_secondary()),
+ )
+ .child(
+ rect()
+ .horizontal()
+ .width(Size::fill())
+ .cross_align(Alignment::Center)
+ .main_align(Alignment::SpaceBetween)
+ .child(
+ label()
+ .text(if locked {
+ held.to_string()
+ } else {
+ String::new()
+ })
+ .font_size(12.)
+ .color(colors::fg_secondary()),
+ )
+ .child(
+ rect()
+ .horizontal()
+ .spacing(8.)
+ .child(
+ Button::new()
+ .secondary()
+ .disabled(locked)
+ .on_press(move |_| cancel.dismiss_prompt())
+ .text(dismiss_label),
+ )
+ .child(
+ Button::new()
+ .secondary()
+ .disabled(locked)
+ .on_press(move |_| {
+ never.answer_prompt(Answer::new(
+ MICROSOFT_JAVA_CHOICE_NEVER,
+ ))
+ })
+ .text(never_label),
+ )
+ .child(
+ Button::new()
+ .primary()
+ .on_press(move |_| {
+ accept.answer_prompt(Answer::new(
+ MICROSOFT_JAVA_CHOICE_INSTALL,
+ ))
+ })
+ .child(
+ Icon::new(IconType::DownloadCloud02)
+ .size(14.),
+ )
+ .text(install_label),
+ ),
+ ),
+ ),
+ ),
+ )
+ .into_element()
+ }
+}
diff --git a/packages/oneclient_app/src/components/mod.rs b/packages/oneclient_app/src/components/mod.rs
index 75dd453b..e3bf43ad 100644
--- a/packages/oneclient_app/src/components/mod.rs
+++ b/packages/oneclient_app/src/components/mod.rs
@@ -16,6 +16,7 @@ mod icons;
mod java_install_manager;
mod generic_prompt;
mod java_prompt;
+mod microsoft_java_prompt;
mod link_confirm;
mod local_image;
mod log_viewer;
@@ -62,6 +63,7 @@ pub use icons::{Icon, IconTint, IconType};
pub use java_install_manager::JavaInstallManager;
pub use generic_prompt::GenericPromptOverlay;
pub use java_prompt::JavaPromptOverlay;
+pub use microsoft_java_prompt::MicrosoftJavaPromptOverlay;
pub use link_confirm::ConfirmLinkOverlay;
pub use local_image::LocalImage;
pub use markdown::{Markdown, MarkdownStyle};
diff --git a/packages/oneclient_app/src/components/overlay_popup.rs b/packages/oneclient_app/src/components/overlay_popup.rs
index 4f22759e..f4f5d5ba 100644
--- a/packages/oneclient_app/src/components/overlay_popup.rs
+++ b/packages/oneclient_app/src/components/overlay_popup.rs
@@ -8,6 +8,7 @@ use freya::{
use crate::hooks::use_overlay_claim;
const SCRIM_ALPHA: f32 = 90.;
+const SCRIM_MIN_ALPHA: u8 = 1;
/// Overlay level of a top level popup. Its scrim sits two levels below it.
pub const OVERLAY_BASE_LEVEL: u8 = 12;
@@ -116,7 +117,7 @@ impl Component for OverlayPopup {
});
let scrim_alpha = if self.backdrop { SCRIM_ALPHA } else { 0. };
- let alpha = (fade.read().value() * scrim_alpha) as u8;
+ let alpha = ((fade.read().value() * scrim_alpha) as u8).max(SCRIM_MIN_ALPHA);
rect()
.layer(Layer::Overlay)
@@ -130,7 +131,7 @@ impl Component for OverlayPopup {
.height(Size::window_percent(100.))
.layer(Layer::OverlayLevel(self.overlay_level.saturating_sub(2)))
.background(Color::from_argb(alpha, 0, 0, 0))
- .on_press(move |_| {
+ .on_all_press(move |_| {
if let Some(on_close) = scrim_close.as_ref() {
on_close.call(());
}
diff --git a/packages/oneclient_app/src/events.rs b/packages/oneclient_app/src/events.rs
index 43dbddde..56bd0328 100644
--- a/packages/oneclient_app/src/events.rs
+++ b/packages/oneclient_app/src/events.rs
@@ -11,7 +11,7 @@ use tokio::sync::mpsc;
use crate::hooks::PumpSignal;
use crate::notifications::{MESSAGE_TOAST_TTL, PendingPromptView};
-use crate::state::{AppChannel, AppState, LoginProgress};
+use crate::state::{AppChannel, AppState, LoginProgress, StorageScanProgress};
/// Quiet period before log lines are written without it every line wakes the log view
const GAME_LOG_FLUSH: Duration = Duration::from_millis(120);
@@ -127,6 +127,7 @@ impl EventPump {
let mut logs: Vec<(i64, String)> = Vec::new();
let mut failed: Option<(i64, String)> = None;
let mut login: Option