优化关于页面运行环境信息的排版 (#7151)

This commit is contained in:
Glavo authored and GitHub committed 2026-11-01 15:55:40 +08:00
1 parent 8b706466e7
commit 440b42a8b7
21 files changed
+769 -148

No files matched your search

@@ -19,6 +19,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.SystemInfo;
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.PageFileConfiguration;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -31,7 +32,7 @@ import java.util.List;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/// Displays launcher, system hardware, and project information.
/// Displays launcher, system, device, runtime, and project information.
@NotNullByDefault
public final class AboutPage extends SpinnerPane {
@@ -58,45 +59,49 @@ public final class AboutPage extends SpinnerPane {
about.getContent().setAll(launcher);
}
ComponentList environment = new ComponentList();
ComponentList systemInfo = new ComponentList();
{
var system = new LinePane();
system.setLargeTitle(true);
system.setLeading(SVG.LUCIDE_APP_WINDOW.createIcon(32));
system.setTitle(i18n("about.environment.system"));
system.setTitle(i18n("about.system.os"));
system.setTitle(i18n("about.system.os"));
system.setSubtitle((OperatingSystem.OS_RELEASE_PRETTY_NAME == null
? "%s %s (%s)".formatted(OperatingSystem.SYSTEM_NAME, OperatingSystem.SYSTEM_VERSION.getVersion(), Architecture.SYSTEM_ARCH.getDisplayName())
: "%s (%s %s, %s)".formatted(OperatingSystem.OS_RELEASE_PRETTY_NAME, OperatingSystem.SYSTEM_NAME, OperatingSystem.SYSTEM_VERSION.getVersion(), Architecture.SYSTEM_ARCH.getDisplayName())));
environment.getContent().add(system);
systemInfo.getContent().add(system);
if (SystemInfo.getTotalMemorySize() > 0) {
var memory = new LinePane();
memory.setLargeTitle(true);
memory.setLeading(SVG.LUCIDE_MEMORY_STICK.createIcon(32));
memory.setTitle(i18n("about.environment.memory"));
memory.setSubtitle("%.1f GiB".formatted(DataSizeUnit.GIGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));
environment.getContent().add(memory);
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && SystemInfo.getDesktopEnvironment() != null) {
var desktop = new LinePane();
desktop.setLargeTitle(true);
desktop.setLeading(SVG.LUCIDE_APP_WINDOW.createIcon(32));
desktop.setTitle(i18n("about.system.desktop"));
desktop.setSubtitle(SystemInfo.getDesktopEnvironment());
systemInfo.getContent().add(desktop);
}
}
ComponentList deviceInfo = new ComponentList();
{
@Unmodifiable List<CentralProcessor> processors = SystemInfo.getCentralProcessors();
for (int i = 0; i < processors.size(); i++) {
String title = processors.size() == 1
? i18n("about.environment.cpu")
: i18n("about.environment.cpu.n", i + 1);
? i18n("about.device.cpu")
: i18n("about.device.cpu.n", i + 1);
CentralProcessor processor = processors.get(i);
String name = processor.getName();
@Nullable CentralProcessor.Cores cores = processor.getCores();
String subtitle;
if (cores == null)
subtitle = i18n("about.environment.cpu.name", name);
subtitle = i18n("about.device.cpu.name", name);
else if (cores.efficiency() > 0) {
subtitle = cores.logical() == cores.physical()
? i18n("about.environment.cpu.name.cores.hybrid", name, cores.performance(), cores.efficiency())
: i18n("about.environment.cpu.name.cores.hybrid.threads", name, cores.performance(), cores.efficiency(), cores.logical());
? i18n("about.device.cpu.name.cores.hybrid", name, cores.performance(), cores.efficiency())
: i18n("about.device.cpu.name.cores.hybrid.threads", name, cores.performance(), cores.efficiency(), cores.logical());
} else {
subtitle = cores.logical() == cores.physical()
? i18n("about.environment.cpu.name.cores", name, cores.physical())
: i18n("about.environment.cpu.name.cores.threads", name, cores.physical(), cores.logical());
? i18n("about.device.cpu.name.cores", name, cores.physical())
: i18n("about.device.cpu.name.cores.threads", name, cores.physical(), cores.logical());
}
var pane = new LinePane();
@@ -104,7 +109,7 @@ public final class AboutPage extends SpinnerPane {
pane.setTitle(title);
pane.setSubtitle(subtitle);
pane.setLeading(SVG.LUCIDE_CPU.createIcon(32));
environment.getContent().add(pane);
deviceInfo.getContent().add(pane);
}
@Unmodifiable List<GraphicsCard> visibleGraphicsCards = SystemInfo.getGraphicsCards().stream()
@@ -116,8 +121,8 @@ public final class AboutPage extends SpinnerPane {
GraphicsCard graphicsCard = visibleGraphicsCards.get(i);
String title = visibleGraphicsCards.size() == 1
? i18n("about.environment.gpu")
: i18n("about.environment.gpu.n", i + 1);
? i18n("about.device.gpu")
: i18n("about.device.gpu.n", i + 1);
var button = new LinePane();
button.setLargeTitle(true);
@@ -126,9 +131,37 @@ public final class AboutPage extends SpinnerPane {
button.setLeading(SVG.LUCIDE_GPU.createIcon(32));
environment.getContent().add(button);
deviceInfo.getContent().add(button);
}
if (SystemInfo.getTotalMemorySize() > 0) {
String subtitle = i18n("about.device.memory.system",
"%.1f GiB".formatted(DataSizeUnit.GIGABYTES.convertFromBytes(SystemInfo.getTotalMemorySize())));
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
long pageFileSize = SystemInfo.getTotalPageFileSize();
if (pageFileSize < 0) {
subtitle = i18n("about.device.memory.page_files.unknown", subtitle);
} else {
String pageFileCapacity = "%.1f GiB".formatted(DataSizeUnit.GIGABYTES.convertFromBytes(pageFileSize));
PageFileConfiguration configuration = SystemInfo.getPageFileConfiguration();
subtitle = switch (configuration.mode()) {
case MANUAL -> i18n("about.device.memory.page_files.maximum", subtitle, pageFileCapacity, "%.1f GiB".formatted(DataSizeUnit.GIGABYTES.convertFromBytes(configuration.maximumSize())));
case SYSTEM_MANAGED, MIXED, UNKNOWN -> i18n("about.device.memory.page_files", subtitle, pageFileCapacity);
};
}
}
var memory = new LinePane();
memory.setLargeTitle(true);
memory.setLeading(SVG.LUCIDE_MEMORY_STICK.createIcon(32));
memory.setTitle(i18n("about.device.memory"));
memory.setSubtitle(subtitle);
deviceInfo.getContent().add(memory);
}
}
ComponentList runtimeInfo = new ComponentList();
{
var java = new LinePane();
java.setLargeTitle(true);
java.setLeading(SVG.LOCAL_CAFE.createIcon(32));
@@ -137,14 +170,14 @@ public final class AboutPage extends SpinnerPane {
Runtime.version().toString(),
System.getProperty("java.vm.name"),
System.getProperty("java.vendor")));
environment.getContent().add(java);
runtimeInfo.getContent().add(java);
var javaFX = new LinePane();
javaFX.setLargeTitle(true);
javaFX.setLeading(SVG.LOCAL_CAFE.createIcon(32));
javaFX.setTitle("JavaFX");
javaFX.setSubtitle(System.getProperty("javafx.runtime.version"));
environment.getContent().add(javaFX);
runtimeInfo.getContent().add(javaFX);
}
ComponentList deps = loadIconedTwoLineList("/assets/about/deps.json");
@@ -172,8 +205,12 @@ public final class AboutPage extends SpinnerPane {
content.getChildren().setAll(
ComponentList.createComponentListTitle(i18n("about")),
about,
ComponentList.createComponentListTitle(i18n("about.environment")),
environment,
ComponentList.createComponentListTitle(i18n("about.system")),
systemInfo,
ComponentList.createComponentListTitle(i18n("about.device")),
deviceInfo,
ComponentList.createComponentListTitle(i18n("about.runtime")),
runtimeInfo,
ComponentList.createComponentListTitle(i18n("about.dependency")),
deps,
ComponentList.createComponentListTitle(i18n("about.legal")),
@@ -9,22 +9,29 @@ about.copyright=Copyright
about.copyright.statement=Copyright (C) 2013-2026 The HMCL Authors
about.claim=EULA
about.claim.statement=Click this link for full text.
about.environment=Environment
about.environment.cpu=CPU
about.environment.cpu.n=CPU %d
about.environment.cpu.name=%s
about.environment.cpu.name.cores=%s (%d Cores)
about.environment.cpu.name.cores.threads=%s (%d Cores, %d Threads)
about.environment.cpu.name.cores.hybrid=%s (%dP + %dE)
about.environment.cpu.name.cores.hybrid.threads=%s (%dP + %dE, %d Threads)
about.environment.gpu=GPU
about.environment.gpu.n=GPU %d
about.environment.memory=Memory
about.environment.system=Operating System
about.device=Device Information
about.device.cpu=CPU
about.device.cpu.n=CPU %d
about.device.cpu.name=%s
about.device.cpu.name.cores=%s (%d Cores)
about.device.cpu.name.cores.threads=%s (%d Cores, %d Threads)
about.device.cpu.name.cores.hybrid=%s (%dP + %dE)
about.device.cpu.name.cores.hybrid.threads=%s (%dP + %dE, %d Threads)
about.device.gpu=GPU
about.device.gpu.n=GPU %d
about.device.memory=Memory
about.device.memory.page_files=%s, page files allocated %s
about.device.memory.page_files.maximum=%s, page files allocated %s, page file configured limit %s
about.device.memory.page_files.unknown=%s, page files unknown
about.device.memory.system=System memory %s
about.dependency=Third-party Libraries
about.legal=Legal Acknowledgement
about.open_source=Open Source
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
about.runtime=Runtime Environment
about.system=System Information
about.system.desktop=Desktop Environment
about.system.os=Operating System
account=Accounts
account.cape=Cape
@@ -9,22 +9,29 @@ about.copyright=حقوق النشر
# about.copyright.statement=
about.claim=اتفاقية الترخيص
about.claim.statement=انقر على هذا الرابط للاطلاع على النص الكامل.
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=مكتبات الطرف الثالث
about.legal=الإقرارات القانونية
about.open_source=المصدر المفتوح
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=الحسابات
account.cape=الرداء
@@ -8,22 +8,29 @@ about.copyright=Urheberrecht
# about.copyright.statement=
about.claim=EULA
about.claim.statement=Klicken Sie auf diesen Link für den vollständigen Text.
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=Drittanbieter-Bibliotheken
about.legal=Rechtliche Hinweise
about.open_source=Open Source
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=Konten
account.cape=Umhang
@@ -9,22 +9,29 @@ about.copyright=Copyright
# about.copyright.statement=
about.claim=EULA
about.claim.statement=Haga clic en este enlace para ver el texto completo.
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=Bibliotecas de terceros
about.legal=Reconocimiento legal
about.open_source=Código abierto
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=Cuentas
account.cape=Capa
@@ -8,22 +8,29 @@ about.copyright=著作権
# about.copyright.statement=
about.claim=EULA
about.claim.statement=全文はこのリンクをクリック
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=依存関係
about.legal=法的承認
about.open_source=オープンソース
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=アカウント
account.cape=マント
@@ -8,22 +8,29 @@ about.copyright=持權
# about.copyright.statement=
about.claim=客契
about.claim.statement=擊鏈以詳閱
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=所依
about.legal=告以律
about.open_source=開源
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=戶簿
account.cape=蓑衣
@@ -8,22 +8,29 @@ about.copyright=Авторские права
# about.copyright.statement=
about.claim=EULA
about.claim.statement=Кликните на ссылку, чтобы увидеть весь текст.
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=Сторонние библиотеки
about.legal=Правовая информация
about.open_source=Открытый исходный код
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=Аккаунты
account.cape=Плащ
@@ -9,22 +9,29 @@ about.copyright=Авторське право
# about.copyright.statement=
about.claim=Ліцензійна угода з кінцевим користувачем (EULA)
about.claim.statement=Натисніть це посилання для перегляду повного тексту.
# about.environment=
# about.environment.cpu=
# about.environment.cpu.n=
# about.environment.cpu.name=
# about.environment.cpu.name.cores=
# about.environment.cpu.name.cores.threads=
# about.environment.cpu.name.cores.hybrid=
# about.environment.cpu.name.cores.hybrid.threads=
# about.environment.gpu=
# about.environment.gpu.n=
# about.environment.memory=
# about.environment.system=
# about.device=
# about.device.cpu=
# about.device.cpu.n=
# about.device.cpu.name=
# about.device.cpu.name.cores=
# about.device.cpu.name.cores.threads=
# about.device.cpu.name.cores.hybrid=
# about.device.cpu.name.cores.hybrid.threads=
# about.device.gpu=
# about.device.gpu.n=
# about.device.memory=
# about.device.memory.page_files=
# about.device.memory.page_files.maximum=
# about.device.memory.page_files.unknown=
# about.device.memory.system=
about.dependency=Сторонні бібліотеки
about.legal=Правове визнання
about.open_source=Відкрите джерело
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
# about.runtime=
# about.system=
# about.system.desktop=
# about.system.os=
account=Облікові записи
account.cape=Плащ
@@ -8,22 +8,29 @@ about.copyright=版权
# about.copyright.statement=
about.claim=用户协议
about.claim.statement=点击链接以查看全文
about.environment=环境
about.environment.cpu=处理器
about.environment.cpu.n=处理器 %d
about.environment.cpu.name=%s
about.environment.cpu.name.cores=%s (%d 核心)
about.environment.cpu.name.cores.threads=%s (%d 核心,%d 线程)
about.environment.cpu.name.cores.hybrid=%s (%d 性能核 + %d 能效核)
about.environment.cpu.name.cores.hybrid.threads=%s (%d 性能核 + %d 能效核,%d 线程)
about.environment.gpu=显卡
about.environment.gpu.n=显卡 %d
about.environment.memory=内存
about.environment.system=操作系统
about.device=设备信息
about.device.cpu=处理器
about.device.cpu.n=处理器 %d
about.device.cpu.name=%s
about.device.cpu.name.cores=%s (%d 核心)
about.device.cpu.name.cores.threads=%s (%d 核心,%d 线程)
about.device.cpu.name.cores.hybrid=%s (%d 性能核 + %d 能效核)
about.device.cpu.name.cores.hybrid.threads=%s (%d 性能核 + %d 能效核,%d 线程)
about.device.gpu=显卡
about.device.gpu.n=显卡 %d
about.device.memory=内存
about.device.memory.page_files=%s,页面文件已分配 %s
about.device.memory.page_files.maximum=%s,页面文件已分配 %s,页面文件配置上限 %s
about.device.memory.page_files.unknown=%s,页面文件未知
about.device.memory.system=系统内存 %s
about.dependency=依赖
about.legal=法律声明
about.open_source=开源
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL)
about.runtime=运行环境
about.system=系统信息
about.system.desktop=桌面环境
about.system.os=操作系统
account=账户
account.cape=披风
@@ -8,22 +8,29 @@ about.copyright=著作權
# about.copyright.statement=
about.claim=使用者協議
about.claim.statement=點擊連結以查看全文
about.environment=環境
about.environment.cpu=處理器
about.environment.cpu.n=處理器 %d
about.environment.cpu.name=%s
about.environment.cpu.name.cores=%s (%d 核心)
about.environment.cpu.name.cores.threads=%s (%d 核心,%d 執行緒)
about.environment.cpu.name.cores.hybrid=%s (%d 性能核 + %d 能效核)
about.environment.cpu.name.cores.hybrid.threads=%s (%d 性能核 + %d 能效核,%d 執行緒)
about.environment.gpu=顯示卡
about.environment.gpu.n=顯示卡 %d
about.environment.memory=記憶體
about.environment.system=作業系統
about.device=裝置資訊
about.device.cpu=處理器
about.device.cpu.n=處理器 %d
about.device.cpu.name=%s
about.device.cpu.name.cores=%s (%d 核心)
about.device.cpu.name.cores.threads=%s (%d 核心,%d 執行緒)
about.device.cpu.name.cores.hybrid=%s (%d 性能核 + %d 能效核)
about.device.cpu.name.cores.hybrid.threads=%s (%d 性能核 + %d 能效核,%d 執行緒)
about.device.gpu=顯示卡
about.device.gpu.n=顯示卡 %d
about.device.memory=記憶體
about.device.memory.page_files=%s,分頁檔已配置 %s
about.device.memory.page_files.maximum=%s,分頁檔已配置 %s,分頁檔設定上限 %s
about.device.memory.page_files.unknown=%s,分頁檔未知
about.device.memory.system=系統記憶體 %s
about.dependency=相依元件
about.legal=法律宣告
about.open_source=開放原始碼
about.open_source.statement=GPL v3 (https://github.com/HMCL-dev/HMCL/)
about.runtime=執行環境
about.system=系統資訊
about.system.desktop=桌面環境
about.system.os=作業系統
account=帳戶
account.cape=披風
@@ -6,11 +6,13 @@ import org.jackhuang.hmcl.util.DataSizeUnit;
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
import org.jackhuang.hmcl.util.platform.hardware.PageFileConfiguration;
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector;
import org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector;
import org.jackhuang.hmcl.util.platform.windows.*;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.List;
@@ -46,6 +48,9 @@ public final class SystemInfo {
/// The immutable graphics card snapshot, empty if detection is unavailable or fails.
public static final @Unmodifiable List<GraphicsCard> GRAPHICS_CARDS = DETECTOR.detectGraphicsCards();
/// The cached desktop environment description, or `null` if unavailable.
public static final @Nullable String DESKTOP = DETECTOR.detectDesktopEnvironment();
}
/// Initializes cached hardware information if needed and logs a system summary on each call.
@@ -86,6 +91,16 @@ public final class SystemInfo {
if (totalMemorySize > 0 && usedMemorySize > 0)
builder.append(" (").append((int) (((double) usedMemorySize / totalMemorySize) * 100)).append("%)");
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
long pageFileSize = getTotalPageFileSize();
builder.append("\n - Page Files (Current Capacity): ")
.append(pageFileSize >= 0 ? DataSizeUnit.format(pageFileSize) : "Unknown");
PageFileConfiguration configuration = getPageFileConfiguration();
builder.append(", Configuration: ").append(configuration.mode());
if (configuration.mode() == PageFileConfiguration.Mode.MANUAL)
builder.append(", Configured Limit: ").append(DataSizeUnit.format(configuration.maximumSize()));
}
LOG.info(builder.toString());
}
@@ -106,6 +121,18 @@ public final class SystemInfo {
return Holder.TOTAL_MEMORY;
}
/// Queries current page file capacity in bytes without caching it.
///
/// Returns zero when no page files exist, or -1 if unsupported or detection fails.
public static long getTotalPageFileSize() {
return Holder.DETECTOR.getTotalPageFileSize();
}
/// Queries configured page file sizing policy without caching; changes may require a restart.
public static PageFileConfiguration getPageFileConfiguration() {
return Holder.DETECTOR.getPageFileConfiguration();
}
/// Queries the available physical memory in bytes, including reclaimable memory where supported.
///
/// Returns zero if detection is unsupported.
@@ -139,6 +166,14 @@ public final class SystemInfo {
return Holder.GRAPHICS_CARDS;
}
/// Returns the cached desktop environment description, or `null` if detection is unsupported or no name is available.
///
/// The description includes the session type when supplied by the platform detector.
/// The result is detected once during initialization of the cached system information and is not refreshed.
public static @Nullable String getDesktopEnvironment() {
return Holder.DESKTOP;
}
/// Prevents instantiation of this utility class.
private SystemInfo() {
}
@@ -77,7 +77,8 @@ final class FastFetchUtils {
return List.of();
CentralProcessor.Builder builder = new CentralProcessor.Builder()
.setName(cpuInfo.cpu).setVendor(HardwareVendor.of(cpuInfo.vendor));
.setName(CentralProcessor.cleanName(cpuInfo.cpu))
.setVendor(HardwareVendor.of(cpuInfo.vendor));
int cores = 0;
int threads = 0;
if (cpuInfo.cores != null) {
@@ -114,7 +115,7 @@ final class FastFetchUtils {
continue;
GraphicsCard.Builder builder = new GraphicsCard.Builder()
.setName(gpuInfo.name)
.setName(GraphicsCard.cleanName(gpuInfo.name))
.setVendor(HardwareVendor.of(gpuInfo.vendor))
.setDriver(gpuInfo.driver);
@@ -3,6 +3,7 @@
package org.jackhuang.hmcl.util.platform.hardware;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.lang.management.ManagementFactory;
@@ -36,6 +37,15 @@ public class HardwareDetector {
return USE_FAST_FETCH ? FastFetchUtils.detectGraphicsCards() : List.of();
}
/// Returns a desktop environment description, or `null` if detection is unsupported or no name is available.
///
/// The description may include the session type.
///
/// @implSpec The default implementation returns `null`.
public @Nullable String detectDesktopEnvironment() {
return null;
}
/// Returns total physical memory in bytes, or zero if the required management bean is unavailable.
public long getTotalMemorySize() {
try {
@@ -48,6 +58,16 @@ public class HardwareDetector {
return 0L;
}
/// Queries current page file capacity in bytes; -1 means unsupported or failed, and zero means none.
public long getTotalPageFileSize() {
return -1;
}
/// Queries configured page file sizing policy, returning unknown if unsupported or unavailable.
public PageFileConfiguration getPageFileConfiguration() {
return PageFileConfiguration.UNKNOWN;
}
/// Returns free physical memory in bytes, or zero if the required management bean is unavailable.
public long getFreeMemorySize() {
try {
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util.platform.hardware;
import org.jetbrains.annotations.NotNullByDefault;
/// Configured page file policy, which may require a restart to take effect.
///
/// @param maximumSize the sum of configured maximum sizes in bytes, or -1 unless all files are manually sized
/// @param mode the aggregate sizing policy
@NotNullByDefault
public record PageFileConfiguration(long maximumSize, Mode mode) {
/// Configuration could not be determined completely.
public static final PageFileConfiguration UNKNOWN = new PageFileConfiguration(-1, Mode.UNKNOWN);
/// Whether configured page files have explicit or system-managed sizes.
@NotNullByDefault
public enum Mode {
/// Missing, unsupported, or invalid configuration.
UNKNOWN,
/// Every page file has an explicit maximum size.
MANUAL,
/// Every page file is managed by Windows.
SYSTEM_MANAGED,
/// Explicitly sized and system-managed page files coexist.
MIXED
}
}
@@ -2,11 +2,13 @@
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util.platform.linux;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.io.BufferedReader;
@@ -44,6 +46,27 @@ public final class LinuxHardwareDetector extends HardwareDetector {
return cards.isEmpty() ? super.detectGraphicsCards() : cards;
}
/// Reads a desktop environment description from this process's environment variables.
///
/// Uses `XDG_CURRENT_DESKTOP`, falling back to `XDG_SESSION_DESKTOP` if the former is absent or blank.
/// Returns `null` if neither supplies a nonblank name. Otherwise, strips leading and trailing whitespace
/// from the name and, if `XDG_SESSION_TYPE` is nonblank, appends its stripped value as `name (type)`.
/// Desktop names and session types are otherwise preserved without normalization or validation.
@Override
public @Nullable String detectDesktopEnvironment() {
@Nullable String desktopName = System.getenv("XDG_CURRENT_DESKTOP");
if (StringUtils.isBlank(desktopName))
desktopName = System.getenv("XDG_SESSION_DESKTOP");
if (StringUtils.isNotBlank(desktopName)) {
@Nullable String sessionType = System.getenv("XDG_SESSION_TYPE");
return StringUtils.isNotBlank(sessionType)
? "%s (%s)".formatted(desktopName.strip(), sessionType.strip())
: desktopName.strip();
} else
return null;
}
private static final Path MEMINFO = Paths.get("/proc/meminfo");
private static final Pattern MEMINFO_PATTERN = Pattern.compile("^.+:\\s*(?<value>\\d+)\\s*kB?$");
@@ -41,6 +41,11 @@ public interface Kernel32 extends StdCallLibrary {
*/
boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer);
/// Retrieves system information, including the memory page size.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo">GetSystemInfo function</a>
void GetSystemInfo(WinTypes.SYSTEM_INFO systemInfo);
/**
* @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex">GetLogicalProcessorInformationEx function</a>
*/
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util.platform.windows;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
/// Windows process status APIs.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/psapi/process-status-helper">Process Status API</a>
@NotNullByDefault
public interface Psapi extends StdCallLibrary {
/// The native binding, or null when unavailable or disabled.
@Nullable Psapi INSTANCE = NativeUtils.USE_JNA && Platform.isWindows()
? NativeUtils.load("psapi", Psapi.class) : null;
/// Enumerates active page files; returns false on failure.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumpagefilesw">EnumPageFilesW function</a>
boolean EnumPageFilesW(PageFileCallback callback, @Nullable Pointer context);
/// Receives each page file's current allocation and usage in pages.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw">PENUM_PAGE_FILE_CALLBACKW callback function</a>
@NotNullByDefault
interface PageFileCallback extends StdCallCallback {
/// Returns true to continue enumeration, or false to stop.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/psapi/nc-psapi-penum_page_file_callbackw">PENUM_PAGE_FILE_CALLBACKW callback function</a>
boolean invoke(@Nullable Pointer context, WinTypes.ENUM_PAGE_FILE_INFORMATION information, WString fileName);
}
}
@@ -5,6 +5,7 @@ package org.jackhuang.hmcl.util.platform.windows;
import com.sun.jna.*;
import com.sun.jna.ptr.ByReference;
import com.sun.jna.ptr.LongByReference;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -15,6 +16,73 @@ import java.util.List;
*/
public interface WinTypes {
/// An unsigned integer with the native pointer width.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/winprog/windows-data-types">Windows Data Types</a>
@NotNullByDefault
final class SIZE_T extends IntegerType {
/// Creates a zero value for native structure marshalling.
public SIZE_T() {
this(0);
}
/// Creates a value using the platform's pointer width.
public SIZE_T(long value) {
super(Native.SIZE_T_SIZE, value, true);
}
}
/// System information returned by `GetSystemInfo`.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info">SYSTEM_INFO structure</a>
@NotNullByDefault
@Structure.FieldOrder({"wProcessorArchitecture", "wReserved", "dwPageSize", "lpMinimumApplicationAddress",
"lpMaximumApplicationAddress", "dwActiveProcessorMask", "dwNumberOfProcessors", "dwProcessorType",
"dwAllocationGranularity", "wProcessorLevel", "wProcessorRevision"})
final class SYSTEM_INFO extends Structure {
/// Processor architecture reported to the calling process.
public short wProcessorArchitecture;
/// Reserved by Windows.
public short wReserved;
/// Memory page size in bytes.
public int dwPageSize;
/// Lowest accessible application address.
public @Nullable Pointer lpMinimumApplicationAddress;
/// Highest accessible application address.
public @Nullable Pointer lpMaximumApplicationAddress;
/// Active processor bit mask for the current group.
public SIZE_T dwActiveProcessorMask = new SIZE_T();
/// Number of logical processors in the current group.
public int dwNumberOfProcessors;
/// Legacy processor type identifier.
public int dwProcessorType;
/// Allocation address granularity in bytes.
public int dwAllocationGranularity;
/// Architecture-specific processor level.
public short wProcessorLevel;
/// Architecture-specific processor revision.
public short wProcessorRevision;
}
/// Current page file allocation and usage returned by `EnumPageFilesW`.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/psapi/ns-psapi-enum_page_file_information">ENUM_PAGE_FILE_INFORMATION structure</a>
@NotNullByDefault
@Structure.FieldOrder({"cb", "Reserved", "TotalSize", "TotalInUse", "PeakUsage"})
final class ENUM_PAGE_FILE_INFORMATION extends Structure {
/// Native structure size in bytes.
public int cb;
/// Reserved by Windows.
public int Reserved;
/// Currently allocated page file capacity in pages.
public SIZE_T TotalSize = new SIZE_T();
/// Current page file usage in pages.
public SIZE_T TotalInUse = new SIZE_T();
/// Peak page file usage in pages.
public SIZE_T PeakUsage = new SIZE_T();
}
/// A 128-bit Windows globally unique identifier.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/api/guiddef/ns-guiddef-guid">GUID structure</a>
@@ -2,15 +2,22 @@
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util.platform.windows;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor;
import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard;
import org.jackhuang.hmcl.util.platform.hardware.HardwareDetector;
import org.jackhuang.hmcl.util.platform.hardware.PageFileConfiguration;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -20,6 +27,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@NotNullByDefault
public final class WindowsHardwareDetector extends HardwareDetector {
/// A page file path followed by its initial and maximum sizes in MiB.
private static final Pattern PAGE_FILE_SETTING = Pattern.compile("^(.+)\\s+([0-9]+)\\s+([0-9]+)$");
/// An absolute drive path, including the wildcard drive used by automatic management.
private static final Pattern PAGE_FILE_PATH = Pattern.compile("(?i)^[a-z?]:\\\\[^\\r\\n]+$");
/// Detects Windows processors, using the default detector when native information is unavailable.
@Override
public @Unmodifiable List<CentralProcessor> detectCentralProcessors() {
@@ -35,6 +48,124 @@ public final class WindowsHardwareDetector extends HardwareDetector {
return WindowsGPUDetector.detect();
}
/// Queries current page file capacity in bytes; returns zero for no page files, or -1 on failure.
@Override
public long getTotalPageFileSize() {
if (!NativeUtils.USE_JNA)
return -1;
try {
@Nullable Kernel32 kernel32 = Kernel32.INSTANCE;
@Nullable Psapi psapi = Psapi.INSTANCE;
if (kernel32 == null || psapi == null)
return -1;
var systemInfo = new WinTypes.SYSTEM_INFO();
kernel32.GetSystemInfo(systemInfo);
return queryTotalPageFileSize(psapi, Integer.toUnsignedLong(systemInfo.dwPageSize));
} catch (RuntimeException | LinkageError e) {
LOG.warning("Failed to query page file capacity", e);
return -1;
}
}
/// Sums a complete enumeration in bytes; rejects invalid sizes, overflow, and partial results.
static long queryTotalPageFileSize(Psapi psapi, long pageSize) {
if (pageSize <= 0)
return -1;
long[] totalBytes = {0};
boolean success = psapi.EnumPageFilesW((@Nullable Pointer context, WinTypes.ENUM_PAGE_FILE_INFORMATION information, WString fileName) -> {
try {
long pages = information.TotalSize.longValue();
if (pages < 0)
throw new ArithmeticException("Page count exceeds the signed 64-bit range");
totalBytes[0] = Math.addExact(totalBytes[0], Math.multiplyExact(pages, pageSize));
return true;
} catch (ArithmeticException e) {
// Exceptions must not escape through the native callback boundary.
totalBytes[0] = -1;
return false;
}
}, null);
if (!success) {
LOG.warning("Failed to enumerate page files: " + Native.getLastError());
return -1;
}
return totalBytes[0];
}
/// Reads configured limits independently of active allocations; unavailable or invalid settings remain unknown.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/cimwin32prov/win32-pagefilesetting">Win32_PageFileSetting and its PagingFiles registry mapping</a>
@Override
public PageFileConfiguration getPageFileConfiguration() {
if (!NativeUtils.USE_JNA)
return PageFileConfiguration.UNKNOWN;
try {
@Nullable WinReg registry = WinReg.INSTANCE;
if (registry == null)
return PageFileConfiguration.UNKNOWN;
@Nullable Object value = registry.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management", "PagingFiles");
return value instanceof String[] entries ? parsePageFileConfiguration(entries) : PageFileConfiguration.UNKNOWN;
} catch (RuntimeException | LinkageError e) {
LOG.warning("Failed to query page file configuration", e);
return PageFileConfiguration.UNKNOWN;
}
}
/// Aggregates complete registry entries; any malformed or duplicate entry makes the result unknown.
///
/// @see <a href="https://learn.microsoft.com/windows/win32/cimwin32prov/win32-pagefilesetting">Win32_PageFileSetting</a>
static PageFileConfiguration parsePageFileConfiguration(String[] entries) {
if (entries.length == 0)
return PageFileConfiguration.UNKNOWN;
boolean hasManual = false;
boolean hasSystemManaged = false;
long maximumBytes = 0;
Set<String> paths = new HashSet<>();
try {
for (String entry : entries) {
String path = entry.strip();
long initialSize = 0;
long maximumSize = 0;
Matcher matcher = PAGE_FILE_SETTING.matcher(path);
if (matcher.matches()) {
path = matcher.group(1).strip();
initialSize = Long.parseLong(matcher.group(2));
maximumSize = Long.parseLong(matcher.group(3));
} else if (!path.equalsIgnoreCase("?:\\pagefile.sys")) {
return PageFileConfiguration.UNKNOWN;
}
if (path.startsWith("\"") && path.endsWith("\"") && path.length() >= 2)
path = path.substring(1, path.length() - 1);
if (!PAGE_FILE_PATH.matcher(path).matches() || !paths.add(path.toLowerCase(Locale.ROOT)))
return PageFileConfiguration.UNKNOWN;
// A wildcard entry without sizes and explicit 0/0 sizes both request system management.
if (initialSize == 0 && maximumSize == 0) {
hasSystemManaged = true;
} else {
if (maximumSize < initialSize || maximumSize == 0 || path.charAt(0) == '?')
return PageFileConfiguration.UNKNOWN;
maximumBytes = Math.addExact(maximumBytes, Math.multiplyExact(maximumSize, 1024L * 1024));
hasManual = true;
}
}
} catch (NumberFormatException | ArithmeticException e) {
return PageFileConfiguration.UNKNOWN;
}
if (hasSystemManaged)
return new PageFileConfiguration(-1, hasManual ? PageFileConfiguration.Mode.MIXED : PageFileConfiguration.Mode.SYSTEM_MANAGED);
return new PageFileConfiguration(maximumBytes, PageFileConfiguration.Mode.MANUAL);
}
@Override
public long getTotalMemorySize() {
if (NativeUtils.USE_JNA) {
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
package org.jackhuang.hmcl.util.platform.windows;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.WinBase;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.hardware.PageFileConfiguration;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/// Verifies page file aggregation, native layouts, and live Windows queries.
@NotNullByDefault
final class WindowsPageFileTest {
/// Aggregates all page files and uses the supplied page size rather than a fixed 4 KiB.
@Test
void sumsCurrentCapacity() {
Psapi psapi = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> {
assertTrue(callback.invoke(context, information(3), new WString("C:\\pagefile.sys")));
assertTrue(callback.invoke(context, information(5), new WString("D:\\pagefile.sys")));
return true;
};
assertEquals(8L * 16384, WindowsHardwareDetector.queryTotalPageFileSize(psapi, 16384));
}
/// Only a successful empty enumeration represents no page files.
@Test
void distinguishesEmptyFromFailure() {
assertEquals(0, WindowsHardwareDetector.queryTotalPageFileSize((Psapi.PageFileCallback callback, @Nullable Pointer context) -> true, 4096));
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize((Psapi.PageFileCallback callback, @Nullable Pointer context) -> false, 4096));
assertEquals(-1, new org.jackhuang.hmcl.util.platform.hardware.HardwareDetector().getTotalPageFileSize());
}
/// Discards partial totals if enumeration subsequently fails.
@Test
void rejectsPartialResults() {
Psapi psapi = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> {
assertTrue(callback.invoke(context, information(1024), new WString("C:\\pagefile.sys")));
return false;
};
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize(psapi, 4096));
}
/// Rejects missing page size information before calling the native API.
@Test
void rejectsInvalidPageSize() {
Psapi psapi = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> {
fail("Enumeration must not run with an invalid page size");
return false;
};
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize(psapi, 0));
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize(psapi, -1));
}
/// Stops enumeration on arithmetic overflow without throwing through the callback.
@Test
void rejectsOverflow() {
Psapi multiplicationOverflow = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> {
assertFalse(callback.invoke(context, information(2), new WString("C:\\pagefile.sys")));
return true;
};
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize(multiplicationOverflow, Long.MAX_VALUE));
Psapi additionOverflow = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> {
assertTrue(callback.invoke(context, information(1), new WString("C:\\pagefile.sys")));
assertFalse(callback.invoke(context, information(1), new WString("D:\\pagefile.sys")));
return true;
};
assertEquals(-1, WindowsHardwareDetector.queryTotalPageFileSize(additionOverflow, Long.MAX_VALUE));
}
/// Reads fresh allocations on each query so automatic page file growth is visible.
@Test
void doesNotCacheCapacity() {
long[] pages = {100};
Psapi psapi = (Psapi.PageFileCallback callback, @Nullable Pointer context) -> callback.invoke(context, information(pages[0]), new WString("C:\\pagefile.sys"));
assertEquals(100L * 4096, WindowsHardwareDetector.queryTotalPageFileSize(psapi, 4096));
pages[0] = 200;
assertEquals(200L * 4096, WindowsHardwareDetector.queryTotalPageFileSize(psapi, 4096));
}
/// Checks native structure sizes and the unsigned pointer-sized page count.
@Test
void matchesNativeLayouts() {
assertEquals(new WinBase.SYSTEM_INFO().size(), new WinTypes.SYSTEM_INFO().size());
assertEquals(8 + 3 * Native.SIZE_T_SIZE, new WinTypes.ENUM_PAGE_FILE_INFORMATION().size());
assertEquals(0xFFFFFFFFL, new WinTypes.SIZE_T(0xFFFFFFFFL).longValue());
}
/// Sums all explicit maxima in MiB, preserving paths containing spaces.
@Test
void sumsConfiguredLimits() {
PageFileConfiguration configuration = WindowsHardwareDetector.parsePageFileConfiguration(new String[]{
"C:\\pagefile.sys 16384 32768",
"\"D:\\Paging Files\\pagefile.sys\" 8192 16384"
});
assertEquals(PageFileConfiguration.Mode.MANUAL, configuration.mode());
assertEquals(48L * 1024 * 1024 * 1024, configuration.maximumSize());
}
/// Recognizes automatic drive selection and per-file system-managed sizing without a numeric limit.
@Test
void recognizesSystemManagement() {
for (String entry : new String[]{"?:\\pagefile.sys", "?:\\pagefile.sys 0 0", "C:\\pagefile.sys 0 0"}) {
PageFileConfiguration configuration = WindowsHardwareDetector.parsePageFileConfiguration(new String[]{entry});
assertEquals(PageFileConfiguration.Mode.SYSTEM_MANAGED, configuration.mode());
assertEquals(-1, configuration.maximumSize());
}
}
/// Does not present a partial manual sum as the aggregate maximum when managed files also exist.
@Test
void recognizesMixedManagement() {
PageFileConfiguration configuration = WindowsHardwareDetector.parsePageFileConfiguration(new String[]{
"C:\\pagefile.sys 4096 8192", "D:\\pagefile.sys 0 0"
});
assertEquals(PageFileConfiguration.Mode.MIXED, configuration.mode());
assertEquals(-1, configuration.maximumSize());
}
/// Missing, malformed, reversed, duplicate, and overflowing limits must not produce a partial maximum.
@Test
void rejectsIncompleteConfiguration() {
assertEquals(PageFileConfiguration.UNKNOWN, new org.jackhuang.hmcl.util.platform.hardware.HardwareDetector().getPageFileConfiguration());
assertEquals(PageFileConfiguration.UNKNOWN, WindowsHardwareDetector.parsePageFileConfiguration(new String[0]));
for (String invalid : new String[]{"", "invalid", "D:\\pagefile.sys", "D:\\pagefile.sys -1 4096",
"D:\\pagefile.sys 8192 4096", "D:\\pagefile.sys 4096 0", "D:\\pagefile.sys 1 9223372036854775807",
"D:\\pagefile.sys 1 9223372036854775808", "relative.sys 1 4096", "c:\\PAGEFILE.sys 1 4096"}) {
assertEquals(PageFileConfiguration.UNKNOWN, WindowsHardwareDetector.parsePageFileConfiguration(new String[]{
"C:\\pagefile.sys 1024 2048", invalid
}), invalid);
}
}
/// Exercises native callbacks and page size marshalling on Windows.
@Test
@EnabledOnOs(OS.WINDOWS)
void queriesLiveWindowsPageFiles() {
assumeTrue(NativeUtils.USE_JNA);
long total = new WindowsHardwareDetector().getTotalPageFileSize();
assertTrue(total >= 0, "The native page file query must succeed");
System.out.println("Current page file capacity in bytes: " + total);
System.out.println("Page file configuration: " + new WindowsHardwareDetector().getPageFileConfiguration());
}
/// Creates page file data with unrelated usage counters to verify capacity selection.
private static WinTypes.ENUM_PAGE_FILE_INFORMATION information(long pages) {
var information = new WinTypes.ENUM_PAGE_FILE_INFORMATION();
information.TotalSize = new WinTypes.SIZE_T(pages);
information.TotalInUse = new WinTypes.SIZE_T(1);
information.PeakUsage = new WinTypes.SIZE_T(2);
return information;
}
}