优化关于页面运行环境信息的排版 (#7151)
This commit is contained in:
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() {
|
||||
}
|
||||
|
||||
+3
-2
@@ -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);
|
||||
|
||||
|
||||
+20
@@ -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 {
|
||||
|
||||
+29
@@ -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
|
||||
}
|
||||
}
|
||||
+23
@@ -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>
|
||||
|
||||
+131
@@ -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) {
|
||||
|
||||
+163
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user