Skip to content
6 changes: 6 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ public void grantDeviceCode(String userCode, String verificationURI) {
@Override
public void openBrowser(String url) throws IOException {
lastlyOpenedURL = url;

try {
Thread.sleep(3000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉强制等待三秒会影响到了解登陆流程的用户的使用体验,可能需要更好的逻辑来改善用户体验。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个实际上问题不大,登录微软账户也不是什么需要频繁进行的操作

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个实际上问题不大,登录微软账户也不是什么需要频繁进行的操作

我使用的时候对这个硬控三秒感到很烦躁。至少对我个人来说,这个非常影响使用体验。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个实际上问题不大,登录微软账户也不是什么需要频繁进行的操作

我使用的时候对这个硬控三秒感到很烦躁。至少对我个人来说,这个非常影响使用体验。

实际上其他启动器也是类似的逻辑。如果你依然认为有必要更改,可以直接缩短此处的时间。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以把等待时间改短一点。我觉得 1s 或 2s 应该够了。

} catch (InterruptedException ignored) {
}
Comment on lines +156 to +159
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Thread.sleep() blocks the calling thread for 3 seconds. If this method is called on the UI thread or a thread from a limited thread pool, it could cause unresponsiveness or performance issues. Consider using a scheduled executor or asynchronous delay (e.g., CompletableFuture.delayedExecutor() or JavaFX PauseTransition) instead of blocking the thread.

Copilot uses AI. Check for mistakes.

FXUtils.openLink(url);

onOpenBrowser.fireEvent(new OpenBrowserEvent(this, url));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ public static String localizeErrorMessage(Exception exception) {
} else if (exception instanceof MicrosoftService.NoMinecraftJavaEditionProfileException) {
return i18n("account.methods.microsoft.error.no_character");
} else if (exception instanceof MicrosoftService.NoXuiException) {
return i18n("account.methods.microsoft.error.add_family_probably");
return i18n("account.methods.microsoft.error.add_family");
} else if (exception instanceof OAuthServer.MicrosoftAuthenticationNotSupportedException) {
return i18n("account.methods.microsoft.snapshot");
} else if (exception instanceof OAuthAccount.WrongAccountException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
import javafx.scene.control.Label;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.*;

import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
Expand Down Expand Up @@ -65,11 +63,7 @@
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -287,72 +281,95 @@ private void initDetailsPane() {
btnAccept.disableProperty().unbind();
detailsContainer.getChildren().remove(detailsPane);
lblErrorMessage.setText("");
lblErrorMessage.setVisible(true);
}

if (factory == Accounts.FACTORY_MICROSOFT) {
VBox vbox = new VBox(8);
if (!Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
if (deviceCode != null) {
FXUtils.copyText(deviceCode.getUserCode());
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getUserCode(), deviceCode.getVerificationUri()));
} else {
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
}
});
FXUtils.onClicked(hintPane, () -> {
if (deviceCode.get() != null) {
FXUtils.copyText(deviceCode.get().getUserCode());
}
});

holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value -> {
runInFX(() -> deviceCode.set(value));
}));
FlowPane box = new FlowPane();
box.setHgap(8);
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
birthLink.setExternalLink("https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a");
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
JFXHyperlink createProfileLink = new JFXHyperlink(i18n("account.methods.microsoft.makegameidsettings"));
createProfileLink.setExternalLink("https://www.minecraft.net/msaprofile/mygames/editprofile");
JFXHyperlink bannedQueryLink = new JFXHyperlink(i18n("account.methods.ban_query"));
bannedQueryLink.setExternalLink("https://enforcement.xbox.com/enforcement/showenforcementhistory");
box.getChildren().setAll(profileLink, birthLink, purchaseLink, deauthorizeLink, forgotpasswordLink, createProfileLink, bannedQueryLink);
GridPane.setColumnSpan(box, 2);

if (!IntegrityChecker.isOfficial()) {
HintPane unofficialHint = new HintPane(MessageDialogPane.MessageType.WARNING);
unofficialHint.setText(i18n("unofficial.hint"));
vbox.getChildren().add(unofficialHint);
}

vbox.getChildren().addAll(hintPane, box);
detailsPane = vbox;

btnAccept.setDisable(false);
} else {
if (Accounts.OAUTH_CALLBACK.getClientId().isEmpty()) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("account.methods.microsoft.snapshot"));
vbox.getChildren().add(hintPane);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the early return on line 295, the detailsPane is never added to detailsContainer. The detailsContainer.getChildren().add(detailsPane) call on line 373 is unreachable for this code path. Consider adding detailsContainer.getChildren().add(detailsPane); before the return statement on line 295.

Suggested change
vbox.getChildren().add(hintPane);
vbox.getChildren().add(hintPane);
detailsContainer.getChildren().add(detailsPane);

Copilot uses AI. Check for mistakes.
return;
}

JFXHyperlink officialWebsite = new JFXHyperlink(i18n("account.methods.microsoft.snapshot.website"));
officialWebsite.setExternalLink(Metadata.PUBLISH_URL);

vbox.getChildren().setAll(hintPane, officialWebsite);
btnAccept.setDisable(true);
if (!IntegrityChecker.isOfficial()) {
HintPane hintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
hintPane.setSegment(i18n("unofficial.hint"));
vbox.getChildren().add(hintPane);
}

detailsPane = vbox;
VBox codeBox = new VBox(8);
Label hint = new Label(i18n("account.methods.microsoft.code"));
Label code = new Label();
code.setMouseTransparent(true);
code.setStyle("-fx-font-size: 24");
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The font size is set using an inline style string ("-fx-font-size: 24"). This could cause inconsistency with the application's theme. Consider defining a CSS class for the code display or using the theme's font sizes for consistency.

Suggested change
code.setStyle("-fx-font-size: 24");
code.getStyleClass().add("microsoft-device-code");

Copilot uses AI. Check for mistakes.
codeBox.getChildren().addAll(hint, code);
codeBox.setAlignment(Pos.CENTER);
vbox.getChildren().add(codeBox);

HintPane hintPane = new HintPane(MessageDialogPane.MessageType.INFO);
HintPane errHintPane = new HintPane(MessageDialogPane.MessageType.ERROR);
errHintPane.setVisible(false);
errHintPane.setManaged(false);

codeBox.setVisible(false);
codeBox.setManaged(false);

FXUtils.onChangeAndOperate(deviceCode, deviceCode -> {
if (deviceCode != null) {
FXUtils.copyText(deviceCode.getUserCode());
code.setText(deviceCode.getUserCode());
hintPane.setSegment(i18n("account.methods.microsoft.manual", deviceCode.getVerificationUri()));
codeBox.setVisible(true);
codeBox.setManaged(true);
} else {
hintPane.setSegment(i18n("account.methods.microsoft.hint"));
codeBox.setVisible(false);
codeBox.setManaged(false);
}
});

lblErrorMessage.setVisible(false);
lblErrorMessage.textProperty().addListener((obs, oldVal, newVal) -> {
boolean hasError = !newVal.isEmpty();
errHintPane.setSegment(newVal);
errHintPane.setVisible(hasError);
errHintPane.setManaged(hasError);
hintPane.setVisible(!hasError);
hintPane.setManaged(!hasError);
codeBox.setVisible(!hasError && deviceCode.get() != null);
codeBox.setManaged(!hasError && deviceCode.get() != null);
});

FXUtils.onClicked(codeBox, () -> {
if (deviceCode.get() != null) FXUtils.copyText(deviceCode.get().getUserCode());
});

holder.add(Accounts.OAUTH_CALLBACK.onGrantDeviceCode.registerWeak(value ->
runInFX(() -> deviceCode.set(value))
));

HBox linkBox = new HBox();
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The HBox linkBox is created without spacing between hyperlinks. The previous code used FlowPane with setHgap(8) to provide spacing. Consider setting spacing for the HBox: HBox linkBox = new HBox(8); to maintain visual consistency.

Suggested change
HBox linkBox = new HBox();
HBox linkBox = new HBox(8);

Copilot uses AI. Check for mistakes.
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
JFXHyperlink deauthorizeLink = new JFXHyperlink(i18n("account.methods.microsoft.deauthorize"));
deauthorizeLink.setExternalLink("https://account.live.com/consent/Edit?client_id=000000004C794E0A");
JFXHyperlink forgotpasswordLink = new JFXHyperlink(i18n("account.methods.forgot_password"));
forgotpasswordLink.setExternalLink("https://account.live.com/ResetPassword.aspx");
linkBox.getChildren().setAll(profileLink, purchaseLink, deauthorizeLink, forgotpasswordLink);

vbox.getChildren().addAll(hintPane, errHintPane, linkBox);
btnAccept.setDisable(false);
} else {
detailsPane = new AccountDetailsInputPane(factory, btnAccept::fire);
btnAccept.disableProperty().bind(((AccountDetailsInputPane) detailsPane).validProperty().not());
}

detailsContainer.getChildren().add(detailsPane);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ public OAuthAccountLoginDialog(OAuthAccount account, Consumer<AuthInfo> success,

HBox box = new HBox(8);
JFXHyperlink birthLink = new JFXHyperlink(i18n("account.methods.microsoft.birth"));
birthLink.setOnAction(e -> FXUtils.openLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a"));
birthLink.setExternalLink("https://support.microsoft.com/account-billing/how-to-change-a-birth-date-on-a-microsoft-account-837badbc-999e-54d2-2617-d19206b9540a");
JFXHyperlink profileLink = new JFXHyperlink(i18n("account.methods.microsoft.profile"));
profileLink.setOnAction(e -> FXUtils.openLink("https://account.live.com/editprof.aspx"));
profileLink.setExternalLink("https://account.live.com/editprof.aspx");
JFXHyperlink purchaseLink = new JFXHyperlink(i18n("account.methods.microsoft.purchase"));
purchaseLink.setOnAction(e -> FXUtils.openLink(YggdrasilService.PURCHASE_URL));
purchaseLink.setExternalLink(YggdrasilService.PURCHASE_URL);
box.getChildren().setAll(profileLink, birthLink, purchaseLink);
GridPane.setColumnSpan(box, 2);

Expand Down
2 changes: 1 addition & 1 deletion HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ account.methods.microsoft.manual=Your device code is <b>%1$s</b>. Please click h
account.methods.microsoft.makegameidsettings=Create Profile / Edit Profile Name
account.methods.microsoft.profile=Account Profile
account.methods.microsoft.purchase=Buy Minecraft
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the <a href="https://hmcl.huangyuhui.net/download">official build</a> to log in.
account.methods.microsoft.snapshot.website=Official Website
account.methods.offline=Offline
account.methods.offline.name.special_characters=Use only letters, numbers, and underscores (max 16 chars)
Expand Down
2 changes: 1 addition & 1 deletion HMCL/src/main/resources/assets/lang/I18N_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ account.methods.microsoft.profile=編輯帳戶配置檔
account.methods.microsoft.purchase=購買 Minecraft
account.methods.forgot_password=忘記密碼
account.methods.ban_query=查詢帳戶是否被封禁
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載官方版本進行登入
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載 <a href="https://hmcl.huangyuhui.net/download">官方版本</a> 進行登入
account.methods.microsoft.snapshot.website=官方網站
account.methods.offline=離線模式
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名,且長度不超過 16 個字元
Expand Down
32 changes: 9 additions & 23 deletions HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -103,42 +103,28 @@ account.methods=登录方式
account.methods.authlib_injector=外置登录
account.methods.microsoft=微软账户
account.methods.microsoft.birth=如何更改账户出生日期
account.methods.microsoft.code=代码 (已自动复制)
account.methods.microsoft.close_page=已完成微软账户授权。其余登录步骤将由启动器自动执行。你现在可以关闭本页面了。
account.methods.microsoft.deauthorize=解除账户授权
account.methods.microsoft.error.add_family=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family_probably=请点击上方“编辑账户个人信息”更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.add_family=请点击 <a href="https://support.microsoft.com/account-billing/837badbc-999e-54d2-2617-d19206b9540a">此处</a> 更改你的账户出生日期,使年龄满 18 岁以上,或将账户加入到家庭中。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.country_unavailable=你所在的国家或地区不受 Xbox Live 的支持。
account.methods.microsoft.error.missing_xbox_account=请点击上方“创建档案”关联 Xbox 账户。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案。请点击上方“创建档案”以创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击上方“检测账户是否被封禁”查看账户状态
account.methods.microsoft.error.no_character=请确认你已经购买了 Minecraft: Java 版。\n若已购买,则可能未创建游戏档案。请点击 <a href="https://www.minecraft.net/msaprofile/mygames/editprofile">此处</a> 创建游戏档案。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.banned=你的账户可能被 Xbox Live 封禁。\n你可以点击 <a href="https://enforcement.xbox.com/enforcement/showenforcementhistory">此处</a> 按钮查询账户封禁状态
account.methods.microsoft.error.unknown=未知问题。错误码:%d。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.error.wrong_verify_method=请在微软账户登录页面使用密码登录,不要使用验证码登录。\n你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.logging_in=登录中……
account.methods.microsoft.makegameidsettings=创建档案 / 编辑档案名称
account.methods.microsoft.hint=你需要按照以下步骤添加账户:\n\
\ 1. 点击“登录”按钮;\n\
\ 2. 在弹出的网页中输入 HMCL 显示的代码,并点击“允许访问”;\n\
\ 3. 按照网站的提示登录;\n\
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载。请使用网络代理并重试。</b>\n\
如遇到问题,你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.manual=你需要按照以下步骤添加:\n\
\ 1. 点击“登录”按钮;\n\
\ 2. 在弹出的网页中输入 <b>%1$s</b> (已自动复制),并点击“允许访问”;\n\
\ 3. 按照网站的提示登录;\n\
\ 4. 当网站提示“是否允许此应用访问你的信息?”时,请点击“接受”;\n\
\ 5. 在网站提示“大功告成”后,等待账户完成添加即可。\n\
若网站未能显示,请手动在浏览器中打开:%2$s\n\
若网站提示“出现错误”或账户添加失败时,请按照以上步骤重新添加。\n\
<b>若设备网络环境不佳,可能会导致网页加载缓慢甚至无法加载。请使用网络代理并重试。</b>\n\
account.methods.microsoft.hint=点击“登录”按钮开始添加微软账户。
account.methods.microsoft.manual=请在弹出的网页中输入上方显示的代码以完成登录。\n\
若网站未能显示,请手动在浏览器中打开:%s\n\
<b>若网络环境不佳,可能会导致网页加载缓慢甚至无法加载,请使用网络代理并重试。</b>\n\
如遇到问题,你可以点击右上角帮助按钮进行求助。
account.methods.microsoft.profile=编辑账户个人信息
account.methods.microsoft.purchase=购买 Minecraft
account.methods.forgot_password=忘记密码
account.methods.ban_query=检测账户是否被封禁
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载官方版本来登录微软账户
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载 <a href="https://hmcl.huangyuhui.net/download">官方版本</a> 来登录微软账户
account.methods.microsoft.snapshot.website=官方网站
account.methods.offline=离线模式
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名,且长度不超过 16 个字符
Expand Down
Loading