From 1c8db8ceffea0c78220ffe803865ae520f1ccc1c Mon Sep 17 00:00:00 2001
From: LynithDev <61880709+LynithDev@users.noreply.github.com>
Date: Sun, 12 Jul 2026 17:38:36 +0200
Subject: [PATCH] feat: Java 25+ is now downloadable fix(ui): Scrollviews with
no scrollable areas capturing scroll wheel events
---
packages/oneclient_app/src/bridge/runtime.rs | 6 +-
.../oneclient_app/src/components/dropdown.rs | 105 ++++++++++-----
.../src/components/scrollview.rs | 14 +-
.../src/view/app/settings/java.rs | 25 +++-
.../examples/java_install_provider.rs | 10 +-
.../examples/java_list_providers.rs | 22 ++--
packages/oneclient_core/src/http/response.rs | 32 ++++-
packages/oneclient_core/src/java/install.rs | 13 +-
packages/oneclient_core/src/java/manager.rs | 33 ++---
.../src/java/vendors/adoptium.rs | 68 ++++++++--
.../src/java/vendors/corretto.rs | 103 +++++++++++++++
.../src/java/vendors/liberica.rs | 123 ++++++++++++++++++
.../oneclient_core/src/java/vendors/mod.rs | 20 ++-
.../oneclient_core/src/java/vendors/zulu.rs | 24 +++-
14 files changed, 498 insertions(+), 100 deletions(-)
create mode 100644 packages/oneclient_core/src/java/vendors/corretto.rs
create mode 100644 packages/oneclient_core/src/java/vendors/liberica.rs
diff --git a/packages/oneclient_app/src/bridge/runtime.rs b/packages/oneclient_app/src/bridge/runtime.rs
index 7729bd0c..595da137 100644
--- a/packages/oneclient_app/src/bridge/runtime.rs
+++ b/packages/oneclient_app/src/bridge/runtime.rs
@@ -432,11 +432,7 @@ impl CoreBridgeRuntime {
)
.await
{
- Ok(runtime) => {
- notifier.send_info(
- "Java installed",
- &format!("Java {} ({})", runtime.major, runtime.vendor),
- );
+ Ok(_) => {
notifier.invalidate_java();
}
Err(err) => {
diff --git a/packages/oneclient_app/src/components/dropdown.rs b/packages/oneclient_app/src/components/dropdown.rs
index ad16b2ec..a8cd3e7b 100644
--- a/packages/oneclient_app/src/components/dropdown.rs
+++ b/packages/oneclient_app/src/components/dropdown.rs
@@ -59,18 +59,50 @@ impl Component for Dropdown {
let mut open = use_state(|| false);
let mut hovering = use_state(|| false);
+ let mut button_area = use_state(|| None::);
+ let mut list_size = use_state(|| None::);
+
let selected = self.selected.clone();
let options = self.options.clone();
let on_select = self.on_select.clone();
- let list_width = self.width.clone();
let is_open = open();
+ if !is_open && list_size().is_some() {
+ let _ = list_size.take();
+ }
+
let trigger_bg = if hovering() {
colors::ghost_overlay_hover()
} else {
colors::ghost_overlay()
};
+ let visible = options.len().min(MAX_VISIBLE_OPTIONS);
+ let list_h = visible as f32 * OPTION_HEIGHT
+ + visible.saturating_sub(1) as f32 * OPTION_SPACING;
+
+ let offset_y = match (button_area(), list_size()) {
+ (Some(button), Some(list)) => {
+ let root_height = Platform::get().root_size.peek().height;
+ let space_below = root_height - button.max_y();
+ let space_above = button.min_y();
+ if list.height > space_below && list.height <= space_above {
+ -(button.height() + list.height + 8.)
+ } else {
+ 0.
+ }
+ }
+ _ => 0.,
+ };
+
+ let list_width = button_area()
+ .map(|b| Size::px(b.width()))
+ .unwrap_or_else(|| self.width.clone());
+
+ let on_global_pointer_press = move |_: Event| {
+ open.set_if_modified(false);
+ };
+
rect()
.width(self.width.clone())
.child(
@@ -85,6 +117,10 @@ impl Component for Dropdown {
.background(trigger_bg)
.on_pointer_enter(move |_| hovering.set(true))
.on_pointer_leave(move |_| hovering.set(false))
+ .on_global_pointer_press(on_global_pointer_press)
+ .on_sized(move |e: Event| {
+ button_area.set_if_modified(Some(e.area));
+ })
.on_press(move |e: Event| {
e.prevent_default();
e.stop_propagation();
@@ -103,44 +139,41 @@ impl Component for Dropdown {
),
)
.maybe_child(is_open.then(|| {
- let visible = options.len().min(MAX_VISIBLE_OPTIONS);
- let list_h = visible as f32 * OPTION_HEIGHT
- + visible.saturating_sub(1) as f32 * OPTION_SPACING;
-
- rect().height(Size::px(0.)).width(list_width)
- .layer(Layer::Overlay)
- .child(
- rect()
- .layer(Layer::RelativeOverlay(10))
- .position(Position::new_global().top(0.).left(0.))
- .width(Size::window_percent(100.))
- .height(Size::window_percent(100.))
- .on_press(move |_| open.set(false)),
- )
- .child(
+ rect().width(Size::px(0.)).height(Size::px(0.)).child(
rect()
- .width(Size::fill())
+ .layer(Layer::Overlay)
+ .width(list_width)
.margin(Gaps::new(4., 0., 0., 0.))
- .layer(Layer::RelativeOverlay(12))
- .padding(4.)
- .corner_radius(CornerRadius::new_all(8.))
- .border(border_all_color(1., colors::component_border()))
- .background(colors::page_elevated())
- .overflow(Overflow::Clip)
+ .offset_y(offset_y)
+ .content(Content::Fit)
+ .on_sized(move |e: Event| {
+ list_size.set_if_modified(Some(e.area.size));
+ })
.child(
- ScrollArea::new()
- .height(Size::px(list_h))
- .spacing(OPTION_SPACING)
- .children(options.into_iter().enumerate().map(|(idx, option)| {
- let on_select = on_select.clone();
- DropdownOption::new(option, move |_| {
- if let Some(handler) = &on_select {
- handler.call(idx);
- }
- open.set(false);
- })
- .into_element()
- })),
+ rect()
+ .width(Size::fill())
+ .padding(4.)
+ .corner_radius(CornerRadius::new_all(8.))
+ .border(border_all_color(1., colors::component_border()))
+ .background(colors::page_elevated())
+ .overflow(Overflow::Clip)
+ .child(
+ ScrollArea::new()
+ .height(Size::px(list_h))
+ .spacing(OPTION_SPACING)
+ .children(options.into_iter().enumerate().map(
+ |(idx, option)| {
+ let on_select = on_select.clone();
+ DropdownOption::new(option, move |_| {
+ if let Some(handler) = &on_select {
+ handler.call(idx);
+ }
+ open.set(false);
+ })
+ .into_element()
+ },
+ )),
+ ),
),
)
}))
diff --git a/packages/oneclient_app/src/components/scrollview.rs b/packages/oneclient_app/src/components/scrollview.rs
index 8e054209..b33b207a 100644
--- a/packages/oneclient_app/src/components/scrollview.rs
+++ b/packages/oneclient_app/src/components/scrollview.rs
@@ -42,7 +42,7 @@ pub(crate) fn scrollbar_pos_and_size(inner: f32, viewport: f32, scroll: f32) ->
let scroll_range = inner - viewport;
let thumb_range = viewport - thumb;
let normalized = -scroll / scroll_range;
-
+
(normalized * thumb_range, thumb)
}
@@ -307,7 +307,17 @@ impl ScrollArea {
let us_thumb = on_user_scroll.clone();
let us_h_thumb = on_user_scroll;
+ let can_scroll_v = vp_h > 0. && vp_h < ct_h;
+ let can_scroll_h = horizontal && vp_w > 0. && vp_w < content_w;
+
let on_wheel = move |e: Event| {
+ let wants_h = horizontal && *shift_held.read();
+
+ // check if scrollable
+ if (wants_h && !can_scroll_h) || !can_scroll_v {
+ return;
+ }
+
e.stop_propagation();
if (e.delta_y != 0.0 || e.delta_x != 0.0)
@@ -316,7 +326,7 @@ impl ScrollArea {
cb.call(());
}
- if horizontal && *shift_held.read() {
+ if wants_h {
let delta = if e.delta_x != 0.0 { e.delta_x } else { e.delta_y };
if delta != 0.0 {
let cur_x = corrected_scroll(content_w, vp_w, *scroll_x.read());
diff --git a/packages/oneclient_app/src/view/app/settings/java.rs b/packages/oneclient_app/src/view/app/settings/java.rs
index fe7277b0..5a9c3e63 100644
--- a/packages/oneclient_app/src/view/app/settings/java.rs
+++ b/packages/oneclient_app/src/view/app/settings/java.rs
@@ -15,6 +15,8 @@ fn providers() -> Vec<(JavaVendor, &'static str)> {
vec![
(JavaVendor::Zulu, "Azul Zulu"),
(JavaVendor::Adoptium, "Eclipse Temurin"),
+ (JavaVendor::Corretto, "Amazon Corretto"),
+ (JavaVendor::Liberica, "BellSoft Liberica"),
]
}
@@ -209,18 +211,31 @@ impl Component for RuntimeRow {
.child(
rect()
.width(Size::flex(1.0))
+ .overflow(Overflow::Clip)
.child(
- label()
- .text(runtime.absolute_path.clone())
- .font_size(12.)
- .max_lines(1)
- .color(colors::fg_secondary()),
+ ScrollArea::new()
+ .horizontal(path_content_width(&runtime.absolute_path))
+ .width(Size::fill())
+ .height(Size::px(18.))
+ .show_scrollbar(false)
+ .child(
+ label()
+ .text(runtime.absolute_path.clone())
+ .font_size(12.)
+ .max_lines(1)
+ .color(colors::fg_secondary())
+ .into_element(),
+ ),
),
)
.child(remove_button(dispatch, path))
}
}
+fn path_content_width(path: &str) -> f32 {
+ (path.chars().count() as f32 * 7.0).max(1.0)
+}
+
fn remove_button(dispatch: BridgeDispatch, path: String) -> impl IntoElement {
Button::new()
.ghost()
diff --git a/packages/oneclient_core/examples/java_install_provider.rs b/packages/oneclient_core/examples/java_install_provider.rs
index 9f30cbe6..49f82916 100644
--- a/packages/oneclient_core/examples/java_install_provider.rs
+++ b/packages/oneclient_core/examples/java_install_provider.rs
@@ -4,7 +4,9 @@ use std::env;
use oneclient_core::dev;
use oneclient_core::java::check_java_runtime;
use oneclient_core::java::vendors::AdoptiumRuntimeProvider;
+use oneclient_core::java::vendors::CorrettoRuntimeProvider;
use oneclient_core::java::vendors::JavaRuntimeProvider;
+use oneclient_core::java::vendors::LibericaRuntimeProvider;
use oneclient_core::java::vendors::ZuluRuntimeProvider;
use oneclient_core::logger;
use oneclient_core::LauncherResult;
@@ -18,7 +20,7 @@ async fn main() -> LauncherResult<()> {
let vendor = args
.next()
.unwrap_or_else(|| {
- eprintln!("usage: java_install_provider [major]");
+ eprintln!("usage: java_install_provider [major]");
std::process::exit(1);
});
@@ -33,13 +35,15 @@ async fn main() -> LauncherResult<()> {
let provider: Box = match vendor.as_str() {
"zulu" => Box::new(ZuluRuntimeProvider),
"adoptium" => Box::new(AdoptiumRuntimeProvider),
+ "corretto" => Box::new(CorrettoRuntimeProvider),
+ "liberica" => Box::new(LibericaRuntimeProvider),
other => {
- eprintln!("unknown vendor '{other}', use zulu, adoptium, or corretto");
+ eprintln!("unknown vendor '{other}', use zulu, adoptium, corretto, or liberica");
std::process::exit(1);
}
};
- let packages = provider.list_packages_by_major(major, &services).await?;
+ let packages = provider.list_packages(Some(major), &services).await?;
let package = packages
.into_iter()
.find(|p| p.java_version.contains(&major))
diff --git a/packages/oneclient_core/examples/java_list_providers.rs b/packages/oneclient_core/examples/java_list_providers.rs
index d10f6a6a..669c0d2d 100644
--- a/packages/oneclient_core/examples/java_list_providers.rs
+++ b/packages/oneclient_core/examples/java_list_providers.rs
@@ -10,19 +10,25 @@ use oneclient_core::logger;
async fn main() -> LauncherResult<()> {
logger::init_debug()?;
- let major: u32 = env::args()
- .nth(1)
- .unwrap_or_else(|| "21".to_string())
- .parse()
- .expect("usage: java_list_providers [major_version]");
+ let arg = env::args().nth(1).unwrap_or_else(|| "21".to_string());
+ let major: Option = if arg == "all" {
+ None
+ } else {
+ Some(arg.parse().expect("usage: java_list_providers [major|all]"))
+ };
let services = dev::ephemeral_services().await?;
- println!("Listing JRE packages for Java {major} (metadata only):\n");
+ match major {
+ Some(major) => println!("Listing JRE packages for Java {major} (metadata only):\n"),
+ None => println!("Listing JRE packages for all majors (metadata only):\n"),
+ }
for provider in runtime_providers() {
- let packages = provider.list_packages_by_major(major, &services).await?;
- print_vendor(provider.vendor(), &packages);
+ match provider.list_packages(major, &services).await {
+ Ok(packages) => print_vendor(provider.vendor(), &packages),
+ Err(err) => println!("{:?}: ERROR {err}", provider.vendor()),
+ }
}
Ok(())
diff --git a/packages/oneclient_core/src/http/response.rs b/packages/oneclient_core/src/http/response.rs
index 65a23477..99d7b993 100644
--- a/packages/oneclient_core/src/http/response.rs
+++ b/packages/oneclient_core/src/http/response.rs
@@ -13,6 +13,8 @@ pub struct ResponseOptions {
pub struct ResponseNotifyOptions {
child: Option,
standalone_label: Option,
+ standalone_id: Option,
+ done_label: Option,
}
impl ResponseNotifyOptions {
@@ -20,6 +22,8 @@ impl ResponseNotifyOptions {
Self {
child: Some(child),
standalone_label: None,
+ standalone_id: None,
+ done_label: None,
}
}
@@ -27,8 +31,20 @@ impl ResponseNotifyOptions {
Self {
child: None,
standalone_label: Some(label.into()),
+ standalone_id: None,
+ done_label: None,
}
}
+
+ pub fn with_id(mut self, id: Uuid) -> Self {
+ self.standalone_id = Some(id);
+ self
+ }
+
+ pub fn done_label(mut self, label: impl Into) -> Self {
+ self.done_label = Some(label.into());
+ self
+ }
}
#[async_trait::async_trait]
@@ -59,7 +75,14 @@ impl ResponseExt for Response {
.notify
.as_ref()
.and_then(|n| n.standalone_label.clone());
- let standalone_id = standalone_label.as_ref().map(|_| Uuid::new_v4());
+ let done_label = options
+ .notify
+ .as_ref()
+ .and_then(|n| n.done_label.clone());
+
+ let standalone_id = standalone_label
+ .as_ref()
+ .map(|_| options.notify.as_ref().and_then(|n| n.standalone_id).unwrap_or_else(Uuid::new_v4));
if let Some(ref child) = grouped_child {
child.set_progress(0, Some(total));
@@ -67,6 +90,7 @@ impl ResponseExt for Response {
notifier.send_progress(id, label, 0, total);
}
+ let notifier = notifier.clone();
let stream = futures_lite::StreamExt::map(self.bytes_stream(), move |item| {
match item {
Ok(chunk) => {
@@ -75,6 +99,12 @@ impl ResponseExt for Response {
if let Some(ref child) = grouped_child {
child.set_progress(current, Some(total));
} else if let (Some(id), Some(label)) = (&standalone_id, &standalone_label) {
+ let done = current >= total;
+ let label = if done {
+ done_label.as_deref().unwrap_or(label)
+ } else {
+ label
+ };
notifier.send_progress(id, label, current, total);
}
diff --git a/packages/oneclient_core/src/java/install.rs b/packages/oneclient_core/src/java/install.rs
index 65bf5475..6673b5e6 100644
--- a/packages/oneclient_core/src/java/install.rs
+++ b/packages/oneclient_core/src/java/install.rs
@@ -22,16 +22,19 @@ pub async fn install_package(
let archive_path = java_dir.join(polyio::sanitize_path(&package.name));
+ let major = package.java_version.first().copied().unwrap_or(0);
services.requester
.download_file(
reqwest::Request::new(Method::GET, Url::parse(&package.download_url)?),
&archive_path,
ResponseOptions {
- notify: Some(ResponseNotifyOptions::standalone(format!(
- "Installing {} {}",
- package.vendor,
- package.java_version.first().copied().unwrap_or(0)
- ))),
+ notify: Some(
+ ResponseNotifyOptions::standalone(format!(
+ "Installing {} {}",
+ package.vendor, major
+ ))
+ .done_label(format!("Installed {} {}", package.vendor, major)),
+ ),
},
services,
)
diff --git a/packages/oneclient_core/src/java/manager.rs b/packages/oneclient_core/src/java/manager.rs
index 7e6271cd..fb957867 100644
--- a/packages/oneclient_core/src/java/manager.rs
+++ b/packages/oneclient_core/src/java/manager.rs
@@ -1,3 +1,4 @@
+use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
@@ -92,23 +93,23 @@ impl JavaManager {
return Ok(Vec::new());
};
- let results = futures_util::future::join_all(INSTALLABLE_MAJORS.iter().map(|&major| {
- let provider = &provider;
- async move {
- (major, provider.latest_package_by_major(major, services).await)
- }
- }))
- .await;
+ let packages = provider.list_packages(None, services).await?;
- let mut available = Vec::new();
- for (major, result) in results {
- match result {
- Ok(Some(package)) => available.push(AvailableJava { major, package }),
- Ok(None) => {}
- Err(err) => tracing::warn!(?vendor, major, "failed to query packages: {err}"),
- }
+ let mut by_major =
+ BTreeMap::::new();
+
+ for package in packages {
+ let Some(&major) = package.java_version.first() else {
+ continue;
+ };
+ by_major.entry(major).or_insert(package);
}
+ let mut available: Vec = by_major
+ .into_iter()
+ .map(|(major, package)| AvailableJava { major, package })
+ .collect();
+
available.sort_by_key(|b| std::cmp::Reverse(b.major));
Ok(available)
@@ -171,10 +172,10 @@ async fn download_and_register(
for provider in vendors::runtime_providers() {
let vendor = provider.vendor();
- let packages = provider.list_packages_by_major(major, services).await;
+ let packages = provider.list_packages(Some(major), services).await;
let package = match &packages {
- Ok(packages) => match packages.first() {
+ Ok(packages) => match packages.iter().find(|p| p.java_version.first() == Some(&major)).or_else(|| packages.first()) {
Some(package) => package,
None => {
tracing::warn!(?vendor, major, "no packages found");
diff --git a/packages/oneclient_core/src/java/vendors/adoptium.rs b/packages/oneclient_core/src/java/vendors/adoptium.rs
index 8b94f85e..ed02a4e9 100644
--- a/packages/oneclient_core/src/java/vendors/adoptium.rs
+++ b/packages/oneclient_core/src/java/vendors/adoptium.rs
@@ -10,6 +10,12 @@ use crate::state::LauncherServices;
pub struct AdoptiumRuntimeProvider;
+// `/v3/info/available_releases`
+#[derive(Debug, Deserialize)]
+struct AdoptiumAvailableReleases {
+ available_releases: Vec,
+}
+
#[derive(Debug, Deserialize)]
struct AdoptiumRelease {
version_data: AdoptiumVersionData,
@@ -19,6 +25,7 @@ struct AdoptiumRelease {
#[derive(Debug, Deserialize)]
struct AdoptiumVersionData {
semver: String,
+ major: u32,
}
#[derive(Debug, Deserialize)]
@@ -38,30 +45,50 @@ impl JavaRuntimeProvider for AdoptiumRuntimeProvider {
JavaVendor::Adoptium
}
- async fn list_packages_by_major(
+ async fn list_packages(
&self,
- major: u32,
+ major: Option,
services: &LauncherServices,
) -> LauncherResult> {
- let url = adoptium_url(major)?;
+ // fetched from `/v3/info/available_releases`.
+ let (low, high) = match major {
+ Some(major) => (major, major),
+ None => {
+ let info = services
+ .requester
+ .send_as::(Request::new(
+ Method::GET,
+ Url::parse("https://api.adoptium.net/v3/info/available_releases")?,
+ ))
+ .await?;
+ let low = info.available_releases.iter().copied().min().unwrap_or(8);
+ let high = info.available_releases.iter().copied().max().unwrap_or(low);
+ (low, high)
+ }
+ };
+
+ let url = adoptium_url(low, high)?;
let releases = services
.requester
.send_as::>(Request::new(Method::GET, url))
.await?;
let mut packages = Vec::new();
-
+
for release in releases {
- let java_version: Vec = release
+ let mut java_version: Vec = release
.version_data
.semver
.split(|c: char| !c.is_numeric())
.filter_map(|s| s.parse::().ok())
.collect();
+ if java_version.first() != Some(&release.version_data.major) {
+ java_version.insert(0, release.version_data.major);
+ }
for binary in release.binaries {
packages.push(JavaPackage {
- archive: PackageArchive::Zip,
+ archive: PackageArchive::from_filename(&binary.package.name),
download_url: binary.package.link,
java_version: java_version.clone(),
name: binary.package.name,
@@ -74,10 +101,29 @@ impl JavaRuntimeProvider for AdoptiumRuntimeProvider {
}
}
-fn adoptium_url(major: u32) -> LauncherResult {
- Ok(Url::parse(&format!(
- "https://api.adoptium.net/v3/assets/feature_releases/{major}/ga?os={ADOPTIUM_OS}&architecture={ADOPTIUM_ARCH}&image_type=jre&jvm_impl=hotspot&project=jdk&heap_size=normal&vendor=eclipse&page=0&page_size=5"
- ))?)
+// One request spanning every GA major via a version range (`[low,high]`),
+// newest build per major, instead of one request per major.
+fn adoptium_url(low: u32, high: u32) -> LauncherResult {
+ let range = format!("%5B{low}%2C{}%5D", high + 1);
+
+ let mut url = Url::parse(&format!(
+ "https://api.adoptium.net/v3/assets/version/{range}"
+ ))?;
+
+ url.query_pairs_mut()
+ .append_pair("os", ADOPTIUM_OS)
+ .append_pair("architecture", ADOPTIUM_ARCH)
+ .append_pair("image_type", "jre")
+ .append_pair("jvm_impl", "hotspot")
+ .append_pair("project", "jdk")
+ .append_pair("heap_size", "normal")
+ .append_pair("vendor", "eclipse")
+ .append_pair("release_type", "ga")
+ .append_pair("sort_method", "DATE")
+ .append_pair("sort_order", "DESC")
+ .append_pair("page", "0")
+ .append_pair("page_size", "50");
+ Ok(url)
}
const ADOPTIUM_ARCH: &str = cfg_select! {
@@ -89,7 +135,7 @@ const ADOPTIUM_ARCH: &str = cfg_select! {
const ADOPTIUM_OS: &str = cfg_select! {
target_os = "windows" => "windows",
- target_os = "macos" => "mac",
+ target_os = "macos" => "mac",
target_os = "linux" => cfg_select! {
target_env = "musl" => "alpine-linux",
_ => "linux",
diff --git a/packages/oneclient_core/src/java/vendors/corretto.rs b/packages/oneclient_core/src/java/vendors/corretto.rs
new file mode 100644
index 00000000..26aba9c8
--- /dev/null
+++ b/packages/oneclient_core/src/java/vendors/corretto.rs
@@ -0,0 +1,103 @@
+use reqwest::{Method, Request};
+use serde_json::Value;
+use url::Url;
+
+use crate::LauncherResult;
+use crate::java::PackageArchive;
+use crate::java::data::JavaPackage;
+use crate::java::vendors::{JavaRuntimeProvider, JavaVendor};
+use crate::state::LauncherServices;
+
+pub struct CorrettoRuntimeProvider;
+
+// { os: { arch: { "jdk": { major: { ext: ... } } } } }
+const INDEX_URL: &str =
+ "https://corretto.github.io/corretto-downloads/latest_links/indexmap_with_checksum.json";
+
+#[async_trait::async_trait]
+impl JavaRuntimeProvider for CorrettoRuntimeProvider {
+ fn vendor(&self) -> JavaVendor {
+ JavaVendor::Corretto
+ }
+
+ async fn list_packages(
+ &self,
+ major: Option,
+ services: &LauncherServices,
+ ) -> LauncherResult> {
+ let index = services
+ .requester
+ .send_as::(Request::new(Method::GET, Url::parse(INDEX_URL)?))
+ .await?;
+
+ // Corretto ships JDK only
+ let Some(by_major) = index
+ .get(CORRETTO_OS)
+ .and_then(|by_arch| by_arch.get(CORRETTO_ARCH))
+ .and_then(|by_type| by_type.get("jdk"))
+ .and_then(Value::as_object)
+ else {
+ return Ok(Vec::new());
+ };
+
+ let mut packages = Vec::new();
+ for (major_str, by_ext) in by_major {
+ let Ok(this_major) = major_str.parse::() else {
+ continue;
+ };
+ if let Some(filter) = major
+ && filter != this_major
+ {
+ continue;
+ }
+
+ let has_ext = by_ext
+ .as_object()
+ .is_some_and(|exts| exts.contains_key(CORRETTO_EXT.0));
+ if !has_ext {
+ continue;
+ }
+
+ let download_url = latest_url(this_major, CORRETTO_EXT.0);
+ let name = format!(
+ "amazon-corretto-{this_major}-{CORRETTO_ARCH}-{CORRETTO_OS}-jdk.{}",
+ CORRETTO_EXT.0
+ );
+
+ packages.push(JavaPackage {
+ archive: CORRETTO_EXT.1,
+ download_url,
+ java_version: vec![this_major],
+ name,
+ vendor: JavaVendor::Corretto,
+ });
+ }
+
+ packages.sort_by_key(|p| std::cmp::Reverse(p.java_version.first().copied().unwrap_or(0)));
+ Ok(packages)
+ }
+}
+
+fn latest_url(major: u32, ext: &str) -> String {
+ format!(
+ "https://corretto.aws/downloads/latest/amazon-corretto-{major}-{CORRETTO_ARCH}-{CORRETTO_OS}-jdk.{ext}"
+ )
+}
+
+const CORRETTO_ARCH: &str = cfg_select! {
+ target_arch = "x86" => "x86",
+ target_arch = "x86_64" => "x64",
+ target_arch = "arm" => "arm",
+ target_arch = "aarch64" => "aarch64",
+};
+
+const CORRETTO_OS: &str = cfg_select! {
+ target_os = "windows" => "windows",
+ target_os = "macos" => "macos",
+ target_os = "linux" => "linux",
+};
+
+const CORRETTO_EXT: (&str, PackageArchive) = cfg_select! {
+ target_os = "windows" => ("zip", PackageArchive::Zip),
+ _ => ("tar.gz", PackageArchive::TarGz),
+};
diff --git a/packages/oneclient_core/src/java/vendors/liberica.rs b/packages/oneclient_core/src/java/vendors/liberica.rs
new file mode 100644
index 00000000..8f5b1c5e
--- /dev/null
+++ b/packages/oneclient_core/src/java/vendors/liberica.rs
@@ -0,0 +1,123 @@
+use reqwest::{Method, Request};
+use serde::Deserialize;
+use url::Url;
+
+use crate::LauncherResult;
+use crate::java::PackageArchive;
+use crate::java::data::JavaPackage;
+use crate::java::vendors::{JavaRuntimeProvider, JavaVendor};
+use crate::state::LauncherServices;
+
+pub struct LibericaRuntimeProvider;
+
+#[derive(Debug, Deserialize)]
+struct LibericaRelease {
+ #[serde(rename = "downloadUrl")]
+ download_url: String,
+ filename: String,
+ #[serde(rename = "featureVersion")]
+ feature_version: u32,
+ #[serde(rename = "updateVersion", default)]
+ update_version: u32,
+ #[serde(rename = "buildVersion", default)]
+ build_version: u32,
+ version: String,
+}
+
+#[async_trait::async_trait]
+impl JavaRuntimeProvider for LibericaRuntimeProvider {
+ fn vendor(&self) -> JavaVendor {
+ JavaVendor::Liberica
+ }
+
+ async fn list_packages(
+ &self,
+ major: Option,
+ services: &LauncherServices,
+ ) -> LauncherResult> {
+ let url = liberica_url(major)?;
+ let releases = services
+ .requester
+ .send_as::>(Request::new(Method::GET, url))
+ .await?;
+
+ let mut releases = releases;
+ releases.sort_by_key(|r| {
+ std::cmp::Reverse((r.feature_version, r.update_version, r.build_version))
+ });
+
+ let packages: Vec = releases
+ .into_iter()
+ .map(|r| {
+ let mut java_version: Vec = r
+ .version
+ .split(|c: char| !c.is_numeric())
+ .filter_map(|s| s.parse::().ok())
+ .collect();
+ if java_version.first() != Some(&r.feature_version) {
+ java_version.insert(0, r.feature_version);
+ }
+
+ JavaPackage {
+ archive: PackageArchive::from_filename(&r.filename),
+ download_url: r.download_url,
+ java_version,
+ name: r.filename,
+ vendor: JavaVendor::Liberica,
+ }
+ })
+ .collect();
+
+ Ok(packages)
+ }
+}
+
+fn liberica_url(major: Option) -> LauncherResult {
+ let mut url = Url::parse("https://api.bell-sw.com/v1/liberica/releases")?;
+ {
+ let mut q = url.query_pairs_mut();
+ q.append_pair("os", LIBERICA_OS)
+ .append_pair("arch", LIBERICA_ARCH)
+ .append_pair("bundle-type", "jre")
+ .append_pair("bitness", LIBERICA_BITNESS)
+ .append_pair("package-type", LIBERICA_PACKAGE)
+ .append_pair("installation-type", "archive")
+ .append_pair(
+ "fields",
+ "downloadUrl,filename,featureVersion,updateVersion,buildVersion,version",
+ )
+ .append_pair("output", "json");
+ if let Some(major) = major {
+ q.append_pair("version-feature", &major.to_string());
+ }
+ }
+ Ok(url)
+}
+
+const LIBERICA_ARCH: &str = cfg_select! {
+ target_arch = "x86" => "x86",
+ target_arch = "x86_64" => "x86",
+ target_arch = "arm" => "arm",
+ target_arch = "aarch64" => "arm",
+};
+
+const LIBERICA_BITNESS: &str = cfg_select! {
+ target_arch = "x86" => "32",
+ target_arch = "x86_64" => "64",
+ target_arch = "arm" => "32",
+ target_arch = "aarch64" => "64",
+};
+
+const LIBERICA_OS: &str = cfg_select! {
+ target_os = "windows" => "windows",
+ target_os = "macos" => "macos",
+ target_os = "linux" => cfg_select! {
+ target_env = "musl" => "linux-musl",
+ _ => "linux"
+ },
+};
+
+const LIBERICA_PACKAGE: &str = cfg_select! {
+ target_os = "windows" => "zip",
+ _ => "tar.gz",
+};
diff --git a/packages/oneclient_core/src/java/vendors/mod.rs b/packages/oneclient_core/src/java/vendors/mod.rs
index 49d8d171..6c21fbab 100644
--- a/packages/oneclient_core/src/java/vendors/mod.rs
+++ b/packages/oneclient_core/src/java/vendors/mod.rs
@@ -5,9 +5,13 @@ use crate::state::LauncherServices;
use crate::LauncherResult;
mod adoptium;
+mod corretto;
+mod liberica;
mod zulu;
pub use adoptium::AdoptiumRuntimeProvider;
+pub use corretto::CorrettoRuntimeProvider;
+pub use liberica::LibericaRuntimeProvider;
use serde::{Deserialize, Deserializer, Serialize};
pub use zulu::ZuluRuntimeProvider;
@@ -15,6 +19,8 @@ pub fn runtime_providers() -> Vec> {
vec![
Box::new(ZuluRuntimeProvider),
Box::new(AdoptiumRuntimeProvider),
+ Box::new(CorrettoRuntimeProvider),
+ Box::new(LibericaRuntimeProvider),
]
}
@@ -22,9 +28,9 @@ pub fn runtime_providers() -> Vec> {
pub trait JavaRuntimeProvider: Send + Sync {
fn vendor(&self) -> JavaVendor;
- async fn list_packages_by_major(
+ async fn list_packages(
&self,
- major: u32,
+ major: Option,
services: &LauncherServices,
) -> LauncherResult>;
@@ -33,8 +39,8 @@ pub trait JavaRuntimeProvider: Send + Sync {
major: u32,
services: &LauncherServices
) -> LauncherResult