Compare commits

..
Author SHA1 Message Date
ShulkerSakuraandGlavo 27ffc8eec5 Add MacOS Style Icon (#3755)
* Add MacOS Style Icon

* MacOS Style Alt Icon

* Scaled Mac Style Icon

* Delete unused icons

* Rename icon

* update

* Optimize icon file

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-03-22 19:13:32 +08:00
Glavo e2f50c92e9 修复无法解析部分 Java 版本号的问题 (#3759)
* 修复无法解析部分 Java 版本号的问题

* update

* update
2025-03-22 12:17:13 +08:00
Glavo e8ff42c4b2 修复取消导出整合包时弹出报错对话框的问题 (#3752)
* 修复取消导出整合包时弹出报错对话框的问题

* Update
2025-03-21 11:01:37 +08:00
Glavo 44ca2cdb55 更新主页公告 (#3751)
* 更新主页公告

* update

* update
2025-03-20 21:25:30 +08:00
Glavo 77bbee02f9 消除 JEP 498 引入的弃用警告 (#3750)
* 消除 JEP 498 引入的弃用警告

* Fix checkstyle
2025-03-20 16:59:46 +08:00
Glavo e4c7b137f1 Add --enable-native-access for javafx.graphics (#3749)
* Add --enable-native-access for javafx.graphics

* fix checkstyle

* update
2025-03-20 16:21:17 +08:00
Glavo efd088e014 在日志中记录编码信息 (#3747)
* 在日志中记录 Code Page

* update

* update
2025-03-20 10:02:16 +08:00
Glavo 2ae266491c 更新关于页面头像 (#3745) 2025-03-18 20:11:07 +08:00
Glavo 0912613cfc 优化 ScrollUtils (#3432)
* 优化 ScrollUtils

* update
2025-03-18 13:31:47 +08:00
18 changed files with 197 additions and 51 deletions

No files matched your search

+3 -2
View File
@@ -144,8 +144,9 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
"javafx.controls/com.sun.javafx.scene.control",
"javafx.controls/com.sun.javafx.scene.control.behavior",
"javafx.controls/javafx.scene.control.skin",
"jdk.attach/sun.tools.attach"
).joinToString(" ")
"jdk.attach/sun.tools.attach",
).joinToString(" "),
"Enable-Native-Access" to "ALL-UNNAMED"
)
System.getenv("GITHUB_SHA")?.also {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -229,6 +229,11 @@ public final class Launcher extends Application {
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
}
LOG.info("System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName());
LOG.info("Native Encoding: " + OperatingSystem.NATIVE_CHARSET);
LOG.info("JNU Encoding: " + System.getProperty("sun.jnu.encoding"));
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
LOG.info("Code Page: " + OperatingSystem.CODE_PAGE);
}
LOG.info("Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName());
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import org.jackhuang.hmcl.ui.AwtUtils;
import org.jackhuang.hmcl.util.ModuleHelper;
import org.jackhuang.hmcl.util.SelfDependencyPatcher;
import org.jackhuang.hmcl.ui.SwingUtils;
import org.jackhuang.hmcl.java.JavaRuntime;
@@ -31,6 +32,7 @@ import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -69,6 +71,8 @@ public final class Main {
checkJavaFX();
verifyJavaFX();
addEnableNativeAccess();
enableUnsafeMemoryAccess();
Launcher.main(args);
}
@@ -79,7 +83,7 @@ public final class Main {
}
private static void initIcon() {
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon@8x.png"));
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon-mac.png"));
AwtUtils.setAppleIcon(image);
}
@@ -116,10 +120,36 @@ public final class Main {
Class.forName("javafx.stage.Stage"); // javafx.graphics
Class.forName("javafx.scene.control.Skin"); // javafx.controls
} catch (Exception e) {
e.printStackTrace(System.err);
showErrorAndExit(i18n("fatal.javafx.incomplete"));
}
}
private static void addEnableNativeAccess() {
if (JavaRuntime.CURRENT_VERSION > 21) {
try {
ModuleHelper.addEnableNativeAccess(Class.forName("javafx.stage.Stage")); // javafx.graphics
} catch (ClassNotFoundException e) {
e.printStackTrace(System.err);
showErrorAndExit(i18n("fatal.javafx.incomplete"));
}
}
}
private static void enableUnsafeMemoryAccess() {
// https://openjdk.org/jeps/498
if (JavaRuntime.CURRENT_VERSION == 24 || JavaRuntime.CURRENT_VERSION == 25) {
try {
Class<?> clazz = Class.forName("sun.misc.Unsafe");
Method trySetMemoryAccessWarned = clazz.getDeclaredMethod("trySetMemoryAccessWarned");
trySetMemoryAccessWarned.setAccessible(true);
trySetMemoryAccessWarned.invoke(null);
} catch (Throwable e) {
e.printStackTrace(System.err);
}
}
}
/**
* Indicates that a fatal error has occurred, and that the application cannot start.
*/
@@ -776,6 +776,8 @@ public final class FXUtils {
String icon;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
icon = "/assets/img/icon.png";
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
icon = "/assets/img/icon-mac.png";
} else {
icon = "/assets/img/icon@4x.png";
}
@@ -25,14 +25,11 @@ import javafx.animation.Animation.Status;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.util.Duration;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.jackhuang.hmcl.util.Holder;
/**
* Utility class for ScrollPanes.
@@ -142,23 +139,23 @@ final class ScrollUtils {
smoothScroll(scrollPane, speed, trackPadAdjustment);
}
private static final double[] FRICTIONS = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
private static final Duration DURATION = Duration.millis(3);
private static void smoothScroll(ScrollPane scrollPane, double speed, double trackPadAdjustment) {
final double[] frictions = {0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
final double[] derivatives = new double[frictions.length];
AtomicReference<Double> atomicSpeed = new AtomicReference<>(speed);
final double[] derivatives = new double[FRICTIONS.length];
Timeline timeline = new Timeline();
AtomicReference<ScrollDirection> scrollDirection = new AtomicReference<>();
Holder<ScrollDirection> scrollDirectionHolder = new Holder<>();
final EventHandler<MouseEvent> mouseHandler = event -> timeline.stop();
final EventHandler<ScrollEvent> scrollHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
scrollDirection.set(determineScrollDirection(event));
if (isTrackPad(event, scrollDirection.get())) {
atomicSpeed.set(speed / trackPadAdjustment);
} else {
atomicSpeed.set(speed);
}
derivatives[0] += scrollDirection.get().intDirection * atomicSpeed.get();
ScrollDirection scrollDirection = determineScrollDirection(event);
scrollDirectionHolder.value = scrollDirection;
double currentSpeed = isTrackPad(event, scrollDirection) ? speed / trackPadAdjustment : speed;
derivatives[0] += scrollDirection.intDirection * currentSpeed;
if (timeline.getStatus() == Status.STOPPED) {
timeline.play();
}
@@ -180,28 +177,26 @@ final class ScrollUtils {
}
});
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
timeline.getKeyFrames().add(new KeyFrame(DURATION, event -> {
for (int i = 0; i < derivatives.length; i++) {
derivatives[i] *= frictions[i];
derivatives[i] *= FRICTIONS[i];
}
for (int i = 1; i < derivatives.length; i++) {
derivatives[i] += derivatives[i - 1];
}
double dy = derivatives[derivatives.length - 1];
Function<Bounds, Double> sizeFunction = (scrollDirection.get() == ScrollDirection.UP || scrollDirection.get() == ScrollDirection.DOWN) ? Bounds::getHeight : Bounds::getWidth;
double size = sizeFunction.apply(scrollPane.getContent().getLayoutBounds());
double value;
switch (scrollDirection.get()) {
double size;
switch (scrollDirectionHolder.value) {
case LEFT:
case RIGHT:
value = Math.min(Math.max(scrollPane.hvalueProperty().get() + dy / size, 0), 1);
scrollPane.hvalueProperty().set(value);
size = scrollPane.getContent().getLayoutBounds().getWidth();
scrollPane.setHvalue(Math.min(Math.max(scrollPane.getHvalue() + dy / size, 0), 1));
break;
case UP:
case DOWN:
value = Math.min(Math.max(scrollPane.vvalueProperty().get() + dy / size, 0), 1);
scrollPane.vvalueProperty().set(value);
size = scrollPane.getContent().getLayoutBounds().getHeight();
scrollPane.setVvalue(Math.min(Math.max(scrollPane.getVvalue() + dy / size, 0), 1));
break;
}
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -69,6 +70,12 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
} else {
if (executor.getException() == null)
return;
if (executor.getException() instanceof CancellationException) {
onEnd();
return;
}
String appendix = StringUtils.getStackTrace(executor.getException());
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
((WizardProvider.FailureCallback) settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
@@ -0,0 +1,31 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
/**
* @author Glavo
*/
public final class ModuleHelper {
public static void addEnableNativeAccess(Class<?> clazzInModule) {
// do nothing
}
private ModuleHelper() {
}
}
@@ -0,0 +1,46 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* @author Glavo
*/
public final class ModuleHelper {
public static void addEnableNativeAccess(Class<?> clazzInModule) {
Module module = clazzInModule.getModule();
if (module.isNamed()) {
try {
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
MethodHandle implAddEnableNativeAccess = lookup.findVirtual(Module.class, "implAddEnableNativeAccess", MethodType.methodType(Module.class));
Module ignored = (Module) implAddEnableNativeAccess.invokeExact(module);
} catch (Throwable e) {
e.printStackTrace(System.err);
}
} else {
System.err.println("TODO: Add enable native access for anonymous modules is not yet supported");
}
}
private ModuleHelper() {
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

@@ -1313,12 +1313,16 @@ update.changelog=Changelog
update.channel.dev=Beta
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
\n\
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
\n\
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
update.channel.dev.title=Beta Channel Notice
update.channel.nightly=Nightly
update.channel.nightly.hint=You are currently using a Nightly channel build of the launcher. While it may include some extra features, it is also always less stable than the other channel builds.\n\
\n\
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
\n\
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
update.channel.nightly.title=Nightly Channel Notice
update.channel.stable=Release
update.checking=Checking for Updates
@@ -1109,12 +1109,12 @@ update.changelog=更新日誌
update.channel.dev=開發版
update.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能,僅用於體驗新功能。開發版功能未受充分驗證,使用起來可能不穩定!\n\
\n\
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以關注 HMCL 的重要動態,或關注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以瞭解 HMCL 的開發進展。
update.channel.dev.title=開發版提示
update.channel.nightly=預覽版
update.channel.nightly.hint=你正在使用 HMCL 預覽版。預覽版更新較為頻繁,包含一些未在穩定版和開發版中包含的測試性功能,僅用於體驗新功能。預覽版功能未受充分驗證,使用起來可能不穩定!\n\
\n\
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。
如果你遇到了使用問題,可以透過設定中 <a href="hmcl://settings/feedback">回報頁面</a> 提供的管道進行回報。歡迎關注 Bilibili <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以關注 HMCL 的重要動態,或關注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以瞭解 HMCL 的開發進展。
update.channel.nightly.title=預覽版提示
update.channel.stable=穩定版
update.checking=正在檢查更新
@@ -1120,12 +1120,12 @@ update.changelog=更新日志
update.channel.dev=开发版
update.channel.dev.hint=你正在使用 HMCL 开发版。开发版包含一些未在稳定版中包含的测试性功能,仅用于体验新功能。开发版功能未受充分验证,使用起来可能不稳定!<a href="https://hmcl.huangyuhui.net/download">下载稳定版</a>\n\
\n\
如果你使用时遇到了问题,可以通过设置中<a href="hmcl://settings/feedback">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以关注 HMCL 的开发进展。
如果你使用时遇到了问题,可以通过设置中<a href="hmcl://settings/feedback">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以关注 HMCL 的重要动态,或关注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以了解 HMCL 的开发进展。
update.channel.dev.title=开发版提示
update.channel.nightly=预览版
update.channel.nightly.hint=你正在使用 HMCL 预览版。预览版更新较为频繁,包含一些未在稳定版和开发版中包含的测试性功能,仅用于体验新功能。预览版功能未受充分验证,使用起来可能不稳定!<a href="https://hmcl.huangyuhui.net/download">下载稳定版</a>\n\
\n\
如果你使用时遇到了问题,可以通过设置中<a href="hmcl://settings/feedback">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以关注 HMCL 的开发进展。
如果你使用时遇到了问题,可以通过设置中<a href="hmcl://settings/feedback">反馈页面</a>提供的渠道进行反馈。欢迎关注 B 站账号 <a href="https://space.bilibili.com/1445341">@huanghongxun</a> 以关注 HMCL 的重要动态,或关注 <a href="https://space.bilibili.com/20314891">@Glavo</a> 以了解 HMCL 的开发进展。
update.channel.nightly.title=预览版提示
update.channel.stable=稳定版
update.checking=正在检查更新
@@ -38,25 +38,23 @@ import java.nio.file.Path;
* @author Glavo
*/
public final class JavaInfo {
public static int parseVersion(String version) {
int startIndex = version.startsWith("1.") ? 2 : 0;
int endIndex = startIndex;
while (endIndex < version.length()) {
char ch = version.charAt(endIndex);
if (ch >= '0' && ch <= '9')
endIndex++;
else
break;
}
try {
int idx = version.indexOf('.');
if (idx < 0) {
idx = version.indexOf('u');
return idx > 0 ? Integer.parseInt(version.substring(0, idx)) : Integer.parseInt(version);
} else {
int major = Integer.parseInt(version.substring(0, idx));
if (major != 1) {
return major;
} else {
int idx2 = version.indexOf('.', idx + 1);
if (idx2 < 0) {
return -1;
}
return Integer.parseInt(version.substring(idx + 1, idx2));
}
}
} catch (NumberFormatException e) {
return endIndex > startIndex ? Integer.parseInt(version.substring(startIndex, endIndex)) : -1;
} catch (Throwable e) {
// The version number is too long
return -1;
}
}
@@ -124,6 +124,8 @@ public enum OperatingSystem {
public static final String OS_RELEASE_NAME;
public static final String OS_RELEASE_PRETTY_NAME;
public static final int CODE_PAGE;
public static final Pattern INVALID_RESOURCE_CHARACTERS;
private static final String[] INVALID_RESOURCE_BASENAMES;
private static final String[] INVALID_RESOURCE_FULLNAMES;
@@ -184,13 +186,30 @@ public enum OperatingSystem {
osName = "Windows 11";
}
int codePage = -1;
try {
Process process = Runtime.getRuntime().exec(new String[]{"chcp.com"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), NATIVE_CHARSET))) {
Matcher matcher = Pattern.compile("(?<cp>[0-9]+)$")
.matcher(reader.readLine().trim());
if (matcher.find()) {
codePage = Integer.parseInt(matcher.group("cp"));
}
}
process.destroy();
} catch (Throwable ignored) {
}
SYSTEM_NAME = osName;
SYSTEM_VERSION = versionNumber;
SYSTEM_BUILD_NUMBER = buildNumber;
CODE_PAGE = codePage;
} else {
SYSTEM_NAME = System.getProperty("os.name");
SYSTEM_VERSION = System.getProperty("os.version");
SYSTEM_BUILD_NUMBER = -1;
CODE_PAGE = -1;
}
Map<String, String> osRelease = Collections.emptyMap();
@@ -12,7 +12,15 @@ public final class JavaRuntimeTest {
@Test
public void testParseVersion() {
assertEquals(8, parseVersion("1.8.0_302"));
assertEquals(8, parseVersion("1.8-internal"));
assertEquals(11, parseVersion("11"));
assertEquals(11, parseVersion("11.0.12"));
assertEquals(11, parseVersion("11-internal"));
assertEquals(11, parseVersion("11+abc"));
assertEquals(-1, parseVersion("abc"));
assertEquals(-1, parseVersion("1."));
assertEquals(-1, parseVersion("1.-internal"));
assertEquals(-1, parseVersion(""));
}
}