diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java index e885e637c..59551fafb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java @@ -650,8 +650,8 @@ public final class JavaManager { if (reg == null) return; - for (String java : reg.queryKeys(hkey, location)) { - if (!reg.queryKeys(hkey, java).contains(java + "\\MSI")) + for (String java : reg.querySubKeys(hkey, location)) { + if (!reg.querySubKeys(hkey, java).contains(java + "\\MSI")) continue; Object home = reg.queryValue(hkey, java, "JavaHome"); if (home instanceof String) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 7b6692823..1190c4774 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -83,7 +83,6 @@ import java.lang.ref.WeakReference; import java.net.*; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -411,22 +410,6 @@ public final class FXUtils { }); } - private static String which(String command) { - String path = System.getenv("PATH"); - if (path == null) - return null; - - for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) { - try { - Path program = Paths.get(item, command); - if (Files.isExecutable(program)) - return program.toRealPath().toString(); - } catch (Throwable ignored) { - } - } - return null; - } - public static void showFileInExplorer(Path file) { String path = file.toAbsolutePath().toString(); @@ -435,7 +418,7 @@ public final class FXUtils { openCommands = new String[]{"explorer.exe", "/select,", path}; else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) openCommands = new String[]{"/usr/bin/open", "-R", path}; - else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && which("dbus-send") != null) + else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && SystemUtils.which("dbus-send") != null) openCommands = new String[]{ "dbus-send", "--print-reply", @@ -501,10 +484,10 @@ public final class FXUtils { } if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { for (String browser : linuxBrowsers) { - String path = which(browser); + Path path = SystemUtils.which(browser); if (path != null) { try { - Runtime.getRuntime().exec(new String[]{browser, link}); + Runtime.getRuntime().exec(new String[]{path.toString(), link}); return; } catch (Throwable ignored) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java index 5efd87630..dc10d91b0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/java/JavaInfo.java @@ -18,7 +18,7 @@ package org.jackhuang.hmcl.java; import kala.compress.archivers.ArchiveEntry; -import org.jackhuang.hmcl.util.KeyValuePairProperties; +import org.jackhuang.hmcl.util.KeyValuePairUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.OperatingSystem; @@ -33,6 +33,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; /** * @author Glavo @@ -60,7 +61,7 @@ public final class JavaInfo { } public static JavaInfo fromReleaseFile(BufferedReader reader) throws IOException { - KeyValuePairProperties properties = KeyValuePairProperties.load(reader); + Map properties = KeyValuePairUtils.loadProperties(reader); String osName = properties.get("OS_NAME"); String osArch = properties.get("OS_ARCH"); String vendor = properties.get("IMPLEMENTOR"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairUtils.java similarity index 50% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairUtils.java index 042527227..60e0f8fc9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairProperties.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/KeyValuePairUtils.java @@ -19,25 +19,34 @@ package org.jackhuang.hmcl.util; import java.io.BufferedReader; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.LinkedHashMap; +import java.util.*; /** * @author Glavo */ -public final class KeyValuePairProperties extends LinkedHashMap { - public static KeyValuePairProperties load(Path file) throws IOException { +public final class KeyValuePairUtils { + public static Map loadProperties(Path file) throws IOException { try (BufferedReader reader = Files.newBufferedReader(file)) { - return load(reader); + return loadProperties(reader); } } - public static KeyValuePairProperties load(BufferedReader reader) throws IOException { - KeyValuePairProperties result = new KeyValuePairProperties(); + public static Map loadProperties(BufferedReader reader) throws IOException { + try { + return loadProperties(reader.lines().iterator()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + public static Map loadProperties(Iterator lineIterator) { + Map result = new LinkedHashMap<>(); + while (lineIterator.hasNext()) { + String line = lineIterator.next(); - String line; - while ((line = reader.readLine()) != null) { if (line.startsWith("#")) continue; @@ -91,4 +100,78 @@ public final class KeyValuePairProperties extends LinkedHashMap } return result; } + + public static Map loadPairs(Path file) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return loadPairs(reader); + } + } + + public static Map loadPairs(BufferedReader reader) throws IOException { + try { + return loadPairs(reader.lines().iterator()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + public static Map loadPairs(Iterator lineIterator) { + Map result = new LinkedHashMap<>(); + while (lineIterator.hasNext()) { + String line = lineIterator.next(); + + int idx = line.indexOf(':'); + if (idx > 0) { + String name = line.substring(0, idx).trim(); + String value = line.substring(idx + 1).trim(); + result.put(name, value); + } + } + return result; + } + + public static List> loadList(Path file) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return loadList(reader); + } + } + + public static List> loadList(BufferedReader reader) throws IOException { + try { + return loadList(reader.lines().iterator()); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + public static List> loadList(Iterator lineIterator) { + ArrayList> result = new ArrayList<>(); + Map current = new LinkedHashMap<>(); + + while (lineIterator.hasNext()) { + String line = lineIterator.next(); + int idx = line.indexOf(':'); + + if (idx < 0) { + if (!current.isEmpty()) { + result.add(current); + current = new LinkedHashMap<>(); + } + continue; + } + + String name = line.substring(0, idx).trim(); + String value = line.substring(idx + 1).trim(); + + current.put(name, value); + } + + if (!current.isEmpty()) + result.add(current); + + return result; + } + + private KeyValuePairUtils() { + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java index ccd6bd173..7ae6e7f32 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/StringUtils.java @@ -54,6 +54,13 @@ public final class StringUtils { return !isBlank(str); } + public static String capitalizeFirst(String str) { + if (str == null || str.isEmpty()) + return str; + + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } + public static String substringBeforeLast(String str, char delimiter) { return substringBeforeLast(str, delimiter, str); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java index b225598a9..69f5a3263 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.util.platform; -import org.jackhuang.hmcl.util.KeyValuePairProperties; +import org.jackhuang.hmcl.util.KeyValuePairUtils; import org.jackhuang.hmcl.util.platform.windows.Kernel32; import org.jackhuang.hmcl.util.platform.windows.WinTypes; @@ -225,7 +225,7 @@ public enum OperatingSystem { Path osReleaseFile = Paths.get("/etc/os-release"); if (Files.exists(osReleaseFile)) { try { - osRelease = KeyValuePairProperties.load(osReleaseFile); + osRelease = KeyValuePairUtils.loadProperties(osReleaseFile); } catch (IOException e) { e.printStackTrace(System.err); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemInfo.java index f716b9f9a..db074f63f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemInfo.java @@ -18,12 +18,13 @@ package org.jackhuang.hmcl.util.platform; 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.PhysicalMemoryStatus; import org.jackhuang.hmcl.util.platform.linux.LinuxHardwareDetector; import org.jackhuang.hmcl.util.platform.macos.MacOSHardwareDetector; -import org.jackhuang.hmcl.util.platform.windows.WindowsHardwareDetector; +import org.jackhuang.hmcl.util.platform.windows.*; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -47,12 +48,18 @@ public final class SystemInfo { } public static final long TOTAL_MEMORY = DETECTOR.getTotalMemorySize(); + public static final @Nullable CentralProcessor CENTRAL_PROCESSOR = DETECTOR.detectCentralProcessor(); public static final @Nullable List GRAPHICS_CARDS = DETECTOR.detectGraphicsCards(); } public static void initialize() { StringBuilder builder = new StringBuilder("System Info:"); + // CPU + CentralProcessor cpu = getCentralProcessor(); + if (cpu != null) + builder.append("\n - CPU: ").append(cpu); + // Graphics Card List graphicsCards = getGraphicsCards(); if (graphicsCards != null) { @@ -108,6 +115,10 @@ public final class SystemInfo { return Long.max(0, totalMemorySize - getFreeMemorySize()); } + public static @Nullable CentralProcessor getCentralProcessor() { + return Holder.CENTRAL_PROCESSOR; + } + public static @Nullable List getGraphicsCards() { return Holder.GRAPHICS_CARDS; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java index d5e956ced..a78e2e2ae 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/SystemUtils.java @@ -18,15 +18,47 @@ package org.jackhuang.hmcl.util.platform; import org.jackhuang.hmcl.java.JavaRuntime; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.function.ExceptionalFunction; +import org.jetbrains.annotations.Nullable; +import java.io.File; import java.io.IOException; -import java.util.Arrays; -import java.util.List; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class SystemUtils { - private SystemUtils() {} + private SystemUtils() { + } + + public static @Nullable Path which(String command) { + String path = System.getenv("PATH"); + if (path == null) + return null; + + try { + for (String item : path.split(OperatingSystem.PATH_SEPARATOR)) { + try { + Path program = Paths.get(item, command); + if (Files.isExecutable(program)) + return program.toRealPath(); + } catch (Throwable ignored) { + } + } + } catch (Throwable ignored) { + } + + return null; + } public static int callExternalProcess(String... command) throws IOException, InterruptedException { return callExternalProcess(Arrays.asList(command)); @@ -43,6 +75,34 @@ public final class SystemUtils { return managedProcess.getProcess().waitFor(); } + public static T run(List command, ExceptionalFunction convert) throws Exception { + File nul = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS + ? new File("NUL") + : new File("/dev/null"); + + Process process = new ProcessBuilder(command) + .redirectInput(nul) + .redirectError(nul) + .start(); + try { + InputStream inputStream = process.getInputStream(); + CompletableFuture future = CompletableFuture.supplyAsync( + Lang.wrap(() -> convert.apply(inputStream)), + Schedulers.io()); + + if (!process.waitFor(15, TimeUnit.SECONDS)) + throw new TimeoutException(); + + if (process.exitValue() != 0) + throw new IOException("Bad exit code: " + process.exitValue()); + + return future.get(); + } finally { + if (process.isAlive()) + process.destroy(); + } + } + public static boolean supportJVMAttachment() { return JavaRuntime.CURRENT_VERSION >= 9 && Thread.currentThread().getContextClassLoader().getResource("com/sun/tools/attach/VirtualMachine.class") != null; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/CentralProcessor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/CentralProcessor.java new file mode 100644 index 000000000..04df38776 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/CentralProcessor.java @@ -0,0 +1,132 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.platform.hardware; + +import org.jetbrains.annotations.Nullable; + +/** + * @author Glavo + */ +public final class CentralProcessor { + private final String name; + private final @Nullable HardwareVendor vendor; + private final @Nullable Cores cores; + + private CentralProcessor(String name, @Nullable HardwareVendor vendor, @Nullable Cores cores) { + this.name = name; + this.vendor = vendor; + this.cores = cores; + } + + public String getName() { + return name; + } + + public @Nullable HardwareVendor getVendor() { + return vendor; + } + + public @Nullable Cores getCores() { + return cores; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(128); + if (cores != null && cores.packages > 1) + builder.append(cores.packages).append(" x "); + + builder.append(name); + + if (cores != null) { + builder.append(" ("); + builder.append(cores.physical).append(" Cores"); + if (cores.logical > 0 && cores.logical != cores.physical) + builder.append(" / ").append(cores.logical).append(" Threads"); + builder.append(")"); + } + + return builder.toString(); + } + + public static final class Cores { + public final int physical; + public final int logical; + public final int packages; + + public Cores(int logical) { + this(logical, logical, 1); + } + + public Cores(int physical, int logical, int packages) { + this.physical = physical; + this.logical = logical; + this.packages = packages; + } + + @Override + public String toString() { + return String.format("Cores[physical=%d, logical=%d, packages=%d]", physical, logical, packages); + } + } + + public static final class Builder { + private String name; + private @Nullable HardwareVendor vendor; + private @Nullable Cores cores; + + public CentralProcessor build() { + String name = this.name; + if (name == null) { + if (vendor != null) + name = vendor + " Processor"; + else + name = "Unknown"; + } + + return new CentralProcessor(name, vendor, cores); + } + + public String getName() { + return name; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public HardwareVendor getVendor() { + return vendor; + } + + public Builder setVendor(HardwareVendor vendor) { + this.vendor = vendor; + return this; + } + + public Cores getCores() { + return cores; + } + + public Builder setCores(Cores cores) { + this.cores = cores; + return this; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/FastFetchUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/FastFetchUtils.java new file mode 100644 index 000000000..3c5cd24f0 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/FastFetchUtils.java @@ -0,0 +1,156 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.platform.hardware; + +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.IOUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +final class FastFetchUtils { + private FastFetchUtils() { + } + + private static T get(String type, TypeToken resultType) { + Path fastfetch = SystemUtils.which("fastfetch"); + if (fastfetch == null) + return null; + + String json; + try { + json = SystemUtils.run(Arrays.asList(fastfetch.toString(), "--structure", type, "--format", "json"), + inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET)); + } catch (Throwable e) { + LOG.warning("Failed to get result from fastfetch", e); + return null; + } + + try { + List> list = JsonUtils.GSON.fromJson(json, JsonUtils.listTypeOf(Result.typeOf(resultType))); + + Result result; + if (list == null + || list.size() != 1 + || (result = list.get(0)) == null + || !type.equalsIgnoreCase(result.type) + || result.result == null) { + throw new IOException("Illegal output format"); + } + + return result.result; + } catch (Throwable e) { + LOG.warning("Failed to parse fastfetch output: " + json, e); + return null; + } + } + + static @Nullable CentralProcessor detectCentralProcessor() { + CPUInfo cpuInfo = get("CPU", TypeToken.get(CPUInfo.class)); + + if (cpuInfo == null) + return null; + + CentralProcessor.Builder builder = new CentralProcessor.Builder() + .setName(cpuInfo.cpu) + .setVendor(HardwareVendor.of(cpuInfo.vendor)); + + if (cpuInfo.cores != null) { + try { + String physical = cpuInfo.cores.get("physical"); + String logical = cpuInfo.cores.get("logical"); + + int cores = physical != null ? Integer.parseInt(physical) : 0; + int threads = logical != null ? Integer.parseInt(logical) : 0; + int packages = Integer.max(cpuInfo.packages, 1); + + if (cores > 0 && threads == 0) + threads = cores; + else if (threads > 0 && cores == 0) + cores = threads; + + builder.setCores(new CentralProcessor.Cores(cores, threads, packages)); + } catch (Throwable ignored) { + } + } + + return builder.build(); + } + + static @Nullable List detectGraphicsCards() { + List gpuInfos = get("GPU", JsonUtils.listTypeOf(GPUInfo.class)); + if (gpuInfos == null) + return null; + + ArrayList result = new ArrayList<>(gpuInfos.size()); + for (GPUInfo gpuInfo : gpuInfos) { + if (gpuInfo == null) + continue; + + GraphicsCard.Builder builder = new GraphicsCard.Builder() + .setName(gpuInfo.name) + .setVendor(HardwareVendor.of(gpuInfo.vendor)) + .setDriver(gpuInfo.driver); + + if ("Discrete".equalsIgnoreCase(gpuInfo.type)) + builder.setType(GraphicsCard.Type.Discrete); + else if ("Integrated".equalsIgnoreCase(gpuInfo.type)) + builder.setType(GraphicsCard.Type.Integrated); + + result.add(builder.build()); + } + return result; + } + + @SuppressWarnings("unchecked") + private static final class Result { + static TypeToken> typeOf(TypeToken type) { + return (TypeToken>) TypeToken.getParameterized(Result.class, type.getType()); + } + + String type; + T result; + } + + private static final class CPUInfo { + String cpu; + String vendor; + int packages; + Map cores; + } + + private static final class GPUInfo { + String name; + String vendor; + String type; + String driver; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java index 715f7c207..6e69d39bf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/GraphicsCard.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.util.platform.hardware; -import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -32,12 +31,12 @@ public final class GraphicsCard { } private final String name; - private final @Nullable Vendor vendor; + private final @Nullable HardwareVendor vendor; private final @Nullable Type type; private final @Nullable String driver; private final @Nullable String driverVersion; - private GraphicsCard(String name, @Nullable Vendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) { + private GraphicsCard(String name, @Nullable HardwareVendor vendor, @Nullable Type type, @Nullable String driver, @Nullable String driverVersion) { this.name = Objects.requireNonNull(name); this.vendor = vendor; this.type = type; @@ -49,7 +48,7 @@ public final class GraphicsCard { return name; } - public @Nullable Vendor getVendor() { + public @Nullable HardwareVendor getVendor() { return vendor; } @@ -68,132 +67,6 @@ public final class GraphicsCard { return builder.toString(); } - public static final class Vendor { - public static final Vendor INTEL = new Vendor("Intel"); - public static final Vendor NVIDIA = new Vendor("NVIDIA"); - public static final Vendor AMD = new Vendor("AMD"); - public static final Vendor APPLE = new Vendor("Apple"); - public static final Vendor QUALCOMM = new Vendor("Qualcomm"); - public static final Vendor MTK = new Vendor("MTK"); - public static final Vendor VMWARE = new Vendor("VMware"); - public static final Vendor PARALLEL = new Vendor("Parallel"); - public static final Vendor MICROSOFT = new Vendor("Microsoft"); - public static final Vendor MOORE_THREADS = new Vendor("Moore Threads"); - public static final Vendor BROADCOM = new Vendor("Broadcom"); - public static final Vendor IMG = new Vendor("Imagination"); - public static final Vendor LOONGSON = new Vendor("Loongson"); - public static final Vendor JINGJIA_MICRO = new Vendor("Jingjia Micro"); - public static final Vendor HUAWEI = new Vendor("Huawei"); - public static final Vendor ZHAOXIN = new Vendor("Zhaoxin"); - - public static @Nullable Vendor getKnown(String name) { - if (name == null) - return null; - - String lower = name.toLowerCase(Locale.ROOT); - if (lower.startsWith("intel")) return INTEL; - if (lower.startsWith("nvidia")) return NVIDIA; - if (lower.startsWith("advanced micro devices") - || (lower.startsWith("amd") && !(lower.length() > 3 && Character.isAlphabetic(lower.charAt(3))))) - return AMD; - if (lower.equals("brcm") || lower.startsWith("broadcom")) return BROADCOM; - if (lower.startsWith("mediatek")) return MTK; - if (lower.startsWith("qualcomm")) return QUALCOMM; - if (lower.startsWith("apple")) return APPLE; - if (lower.startsWith("microsoft")) return MICROSOFT; - if (lower.startsWith("imagination") || lower.equals("img")) return IMG; - - if (lower.startsWith("loongson")) return LOONGSON; - if (lower.startsWith("moore threads")) return MOORE_THREADS; - if (lower.startsWith("jingjia")) return JINGJIA_MICRO; - if (lower.startsWith("huawei")) return HUAWEI; - if (lower.startsWith("zhaoxin")) return ZHAOXIN; - - return null; - } - - @Contract("null -> null; !null -> !null") - public static Vendor of(String name) { - if (name == null) - return null; - - Vendor known = getKnown(name); - return known != null ? known : new Vendor(name); - } - - public static @Nullable Vendor ofId(int vendorId) { - // https://devicehunt.com/all-pci-vendors - switch (vendorId) { - case 0x106b: - return APPLE; - case 0x1002: - case 0x1022: - case 0x1dd8: // AMD Pensando Systems - case 0x1924: // AMD Solarflare - return AMD; - case 0x8086: - case 0x8087: - case 0x03e7: - return INTEL; - case 0x0955: - case 0x10de: - case 0x12d2: - return NVIDIA; - case 0x1ed5: - return MOORE_THREADS; - case 0x168c: - case 0x5143: - return QUALCOMM; - case 0x14c3: - return MTK; - case 0x15ad: - return VMWARE; - case 0x1ab8: - return PARALLEL; - case 0x1414: - return MICROSOFT; - case 0x182f: - case 0x14e4: - return BROADCOM; - case 0x0014: - return LOONGSON; - case 0x0731: - return JINGJIA_MICRO; - case 0x19e5: - return HUAWEI; - case 0x1d17: - return ZHAOXIN; - default: - return null; - } - } - - private final String name; - - public Vendor(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public boolean equals(Object o) { - return o instanceof Vendor && name.equals(((Vendor) o).name); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public String toString() { - return name; - } - } - public enum Type { Integrated, Discrete @@ -201,14 +74,19 @@ public final class GraphicsCard { public static final class Builder { private String name; - private Vendor vendor; + private HardwareVendor vendor; private Type type; private String driver; private String driverVersion; public GraphicsCard build() { - if (name == null) - throw new IllegalStateException("Name not set"); + String name = this.name; + if (name == null) { + if (vendor != null) + name = vendor + " Graphics"; + else + name = "Unknown"; + } return new GraphicsCard(name, vendor, type, driver, driverVersion); } @@ -222,11 +100,11 @@ public final class GraphicsCard { return this; } - public Vendor getVendor() { + public HardwareVendor getVendor() { return vendor; } - public Builder setVendor(Vendor vendor) { + public Builder setVendor(HardwareVendor vendor) { this.vendor = vendor; return this; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareDetector.java index 542982fb9..644796e82 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareDetector.java @@ -28,8 +28,12 @@ import java.util.List; */ @SuppressWarnings("ALL") public class HardwareDetector { + public @Nullable CentralProcessor detectCentralProcessor() { + return FastFetchUtils.detectCentralProcessor(); + } + public @Nullable List detectGraphicsCards() { - return null; + return FastFetchUtils.detectGraphicsCards(); } public long getTotalMemorySize() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareVendor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareVendor.java new file mode 100644 index 000000000..0f961e624 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/hardware/HardwareVendor.java @@ -0,0 +1,199 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jackhuang.hmcl.util.platform.hardware; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + +public final class HardwareVendor { + public static final HardwareVendor INTEL = new HardwareVendor("Intel"); + public static final HardwareVendor NVIDIA = new HardwareVendor("NVIDIA"); + public static final HardwareVendor AMD = new HardwareVendor("AMD"); + public static final HardwareVendor APPLE = new HardwareVendor("Apple"); + public static final HardwareVendor ARM = new HardwareVendor("ARM"); + public static final HardwareVendor QUALCOMM = new HardwareVendor("Qualcomm"); + public static final HardwareVendor MTK = new HardwareVendor("MTK"); + public static final HardwareVendor VMWARE = new HardwareVendor("VMware"); + public static final HardwareVendor PARALLEL = new HardwareVendor("Parallel"); + public static final HardwareVendor MICROSOFT = new HardwareVendor("Microsoft"); + public static final HardwareVendor MOORE_THREADS = new HardwareVendor("Moore Threads"); + public static final HardwareVendor BROADCOM = new HardwareVendor("Broadcom"); + public static final HardwareVendor IMG = new HardwareVendor("Imagination"); + public static final HardwareVendor LOONGSON = new HardwareVendor("Loongson"); + public static final HardwareVendor JINGJIA_MICRO = new HardwareVendor("Jingjia Micro"); + public static final HardwareVendor HUAWEI = new HardwareVendor("Huawei"); + public static final HardwareVendor ZHAOXIN = new HardwareVendor("Zhaoxin"); + public static final HardwareVendor SAMSUNG = new HardwareVendor("Samsung"); + public static final HardwareVendor MARVELL = new HardwareVendor("Marvell"); + public static final HardwareVendor AMPERE = new HardwareVendor("Ampere"); + public static final HardwareVendor ROCKCHIP = new HardwareVendor("Rockchip"); + + // RISC-V + public static final HardwareVendor THEAD = new HardwareVendor("T-Head"); + public static final HardwareVendor STARFIVE = new HardwareVendor("StarFive"); + public static final HardwareVendor ESWIN = new HardwareVendor("ESWIN"); + public static final HardwareVendor SPACEMIT = new HardwareVendor("SpacemiT"); + + public static @Nullable HardwareVendor getKnown(String name) { + if (name == null) + return null; + + String lower = name.toLowerCase(Locale.ROOT); + if (lower.startsWith("intel") || lower.startsWith("genuineintel")) return INTEL; + if (lower.startsWith("nvidia")) return NVIDIA; + if (lower.startsWith("advanced micro devices") + || lower.startsWith("authenticamd") + || (lower.startsWith("amd") && !(lower.length() > 3 && Character.isAlphabetic(lower.charAt(3))))) + return AMD; + if (lower.equals("brcm") || lower.startsWith("broadcom")) return BROADCOM; + if (lower.startsWith("mediatek")) return MTK; + if (lower.equals("qcom") || lower.startsWith("qualcomm")) return QUALCOMM; + if (lower.startsWith("apple")) return APPLE; + if (lower.startsWith("microsoft")) return MICROSOFT; + if (lower.startsWith("imagination") || lower.equals("img")) return IMG; + if (lower.startsWith("loongson")) return LOONGSON; + if (lower.startsWith("moore threads")) return MOORE_THREADS; + if (lower.startsWith("jingjia")) return JINGJIA_MICRO; + if (lower.startsWith("huawei") || lower.startsWith("hisilicon")) return HUAWEI; + if (lower.startsWith("zhaoxin")) return ZHAOXIN; + if (lower.startsWith("marvell")) return MARVELL; + if (lower.startsWith("samsung")) return SAMSUNG; + if (lower.startsWith("ampere")) return AMPERE; + if (lower.startsWith("rockchip")) return ROCKCHIP; + if (lower.startsWith("thead") || lower.startsWith("t-head")) return THEAD; + if (lower.startsWith("starfive")) return STARFIVE; + if (lower.startsWith("eswin")) return ESWIN; + if (lower.startsWith("spacemit")) return SPACEMIT; + + return null; + } + + @Contract("null -> null; !null -> !null") + public static HardwareVendor of(String name) { + if (name == null) + return null; + + HardwareVendor known = getKnown(name); + return known != null ? known : new HardwareVendor(name); + } + + public static @Nullable HardwareVendor ofPciVendorId(int vendorId) { + // https://devicehunt.com/all-pci-vendors + switch (vendorId) { + case 0x106b: + return APPLE; + case 0x1002: + case 0x1022: + case 0x1dd8: // AMD Pensando Systems + case 0x1924: // AMD Solarflare + return AMD; + case 0x8086: + case 0x8087: + case 0x03e7: + return INTEL; + case 0x0955: + case 0x10de: + case 0x12d2: + return NVIDIA; + case 0x1ed5: + return MOORE_THREADS; + case 0x168c: + case 0x5143: + return QUALCOMM; + case 0x14c3: + return MTK; + case 0x15ad: + return VMWARE; + case 0x1ab8: + return PARALLEL; + case 0x1414: + return MICROSOFT; + case 0x182f: + case 0x14e4: + return BROADCOM; + case 0x0014: + return LOONGSON; + case 0x0731: + return JINGJIA_MICRO; + case 0x19e5: + return HUAWEI; + case 0x1d17: + return ZHAOXIN; + default: + return null; + } + } + + public static @Nullable HardwareVendor ofArmImplementerId(int implementerId) { + // https://github.com/util-linux/util-linux/blob/0a21358af3e50fcb13a9bf3702779f11a4739667/sys-utils/lscpu-arm.c#L301 + switch (implementerId) { + case 0x41: + return ARM; + case 0x42: + return BROADCOM; + case 0x48: + return HUAWEI; + case 0x4e: + return NVIDIA; + case 0x51: + return QUALCOMM; + case 0x53: + return SAMSUNG; + case 0x56: + return MARVELL; + case 0x61: + return APPLE; + case 0x69: + return INTEL; + case 0x6D: + return MICROSOFT; + case 0xc0: + return AMPERE; + default: + return null; + } + } + + private final String name; + + public HardwareVendor(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + return o instanceof HardwareVendor && name.equals(((HardwareVendor) o).name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxCPUDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxCPUDetector.java new file mode 100644 index 000000000..a948358b2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxCPUDetector.java @@ -0,0 +1,218 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.platform.linux; + +import org.jackhuang.hmcl.util.KeyValuePairUtils; +import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor; +import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor; +import org.jetbrains.annotations.Nullable; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + * @see cpu_linux.c + */ +final class LinuxCPUDetector { + + private static final String CPUINFO_PATH = "/proc/cpuinfo"; + + private static TreeMap> loadCPUInfo() { + try { + List> list = KeyValuePairUtils.loadList(Paths.get(CPUINFO_PATH)); + TreeMap> result = new TreeMap<>(); + for (Map map : list) { + String id = map.get("processor"); + if (id != null) { + result.put(Integer.parseInt(id), map); + } + } + return result; + } catch (Throwable e) { + LOG.warning("Failed to load /proc/cpuinfo", e); + return null; + } + } + + // https://asahilinux.org/docs/hw/soc/soc-codenames/ + private static String appleCodeToName(int code) { + switch (code) { + case 8103: + return "Apple M1"; + case 6000: + return "Apple M1 Pro"; + case 6001: + return "Apple M1 Max"; + case 6002: + return "Apple M1 Ultra"; + case 8112: + return "Apple M2"; + case 6020: + return "Apple M2 Pro"; + case 6021: + return "Apple M2 Max"; + case 6022: + return "Apple M2 Ultra"; + case 8122: + return "Apple M3"; + case 6030: + return "Apple M3 Pro"; + case 6031: + case 6034: + return "Apple M3 Max"; + case 8132: + return "Apple M4"; + case 6040: + return "Apple M4 Pro"; + case 6041: + return "Apple M4 Max"; + default: + return null; + } + } + + private static void detectName(CentralProcessor.Builder builder, TreeMap> cpuInfo) { + // assert !cpuInfo.isEmpty(); + Map firstCore = cpuInfo.firstEntry().getValue(); + + String modelName = firstCore.get("model name"); + if (modelName == null) + modelName = firstCore.get("Model Name"); + if (modelName == null) + modelName = firstCore.get("cpu model"); + + if (modelName != null) { + builder.setName(modelName); + builder.setVendor(HardwareVendor.of(firstCore.get("vendor_id"))); + + if (builder.getVendor() == null && modelName.startsWith("Loongson")) + builder.setVendor(HardwareVendor.LOONGSON); + + return; + } + + try { + Path compatiblePath = Paths.get("/proc/device-tree/compatible"); + if (Files.isRegularFile(compatiblePath)) { + // device-vendor,device-model\0soc-vendor,soc-model\0 + String[] data = FileUtils.readText(compatiblePath).split("\0"); + + for (int i = data.length - 1; i >= 0; i--) { + String device = data[i]; + int idx = device.indexOf(','); + if (idx <= 0 || idx >= device.length() - 1) + continue; + + String vendor = device.substring(0, idx); + String model = device.substring(idx + 1); + + if (model.startsWith("generic-")) + continue; + + builder.setVendor(HardwareVendor.getKnown(vendor)); + if (builder.getVendor() == null) + builder.setVendor(HardwareVendor.of(StringUtils.capitalizeFirst(vendor))); + + if (builder.getVendor() == HardwareVendor.APPLE) { + // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/apple + if (model.matches("t[0-9]+")) + builder.setName(appleCodeToName(Integer.parseInt(model.substring(1)))); + + if (builder.getName() == null) + builder.setName("Apple Silicon " + model); + } else if (builder.getVendor() == HardwareVendor.QUALCOMM) { + // https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/boot/dts/qcom + if (model.startsWith("x")) + builder.setName("Qualcomm Snapdragon X Elite " + model.toUpperCase(Locale.ROOT)); + } else if (builder.getVendor() == HardwareVendor.BROADCOM) + // Raspberry Pi + builder.setName("Broadcom " + model.toUpperCase(Locale.ROOT)); + + if (builder.getName() == null) + builder.setName(builder.getVendor() + " " + model.toUpperCase(Locale.ROOT)); + + return; + } + } + } catch (Throwable e) { + LOG.warning("Failed to detect CPU name from /proc/device-tree/compatible", e); + } + } + + private static void detectCores(CentralProcessor.Builder builder, TreeMap> cpuInfo) { + // assert !cpuInfo.isEmpty(); + + int logical = cpuInfo.size(); + + try { + Map firstCore = cpuInfo.firstEntry().getValue(); + if (firstCore.containsKey("cpu cores") && firstCore.containsKey("physical id")) { + TreeMap cpuCores = new TreeMap<>(); + TreeSet physicalIds = new TreeSet<>(); + + for (Map core : cpuInfo.values()) { + int cores = Integer.parseInt(core.get("cpu cores")); + int physicalId = Integer.parseInt(core.get("physical id")); + + cpuCores.put(physicalId, cores); + physicalIds.add(physicalId); + } + + int physical = 0; + for (Integer value : cpuCores.values()) { + physical += value; + } + + builder.setCores(new CentralProcessor.Cores(physical, logical, physicalIds.size())); + return; + } + } catch (Throwable e) { + LOG.warning("Failed to detect CPU cores", e); + } + + // We can check /sys/devices/system/cpu, but I don't think it's necessary. + builder.setCores(new CentralProcessor.Cores(logical)); + } + + static @Nullable CentralProcessor detect() { + TreeMap> cpuInfo = loadCPUInfo(); + if (cpuInfo == null || cpuInfo.isEmpty()) return null; + + CentralProcessor.Builder builder = new CentralProcessor.Builder(); + detectName(builder, cpuInfo); + if (builder.getName() == null) + return null; + + detectCores(builder, cpuInfo); + return builder.build(); + } + + private LinuxCPUDetector() { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxGPUDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxGPUDetector.java index 38657f4fc..3f4053b96 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxGPUDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxGPUDetector.java @@ -21,8 +21,10 @@ import org.glavo.pci.ids.PCIIDsDatabase; import org.glavo.pci.ids.model.Device; import org.glavo.pci.ids.model.Vendor; import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.hardware.GraphicsCard; +import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor; import java.io.*; import java.lang.ref.SoftReference; @@ -52,7 +54,7 @@ final class LinuxGPUDetector { private static final Pattern PCI_DEVICE_PATTERN = Pattern.compile("(?\\p{XDigit}+):(?\\p{XDigit}+):(?\\p{XDigit}+)\\.(?\\p{XDigit}+)"); private static final Pattern OF_DEVICE_PATTERN = - Pattern.compile("of:NgpuT[^C]*C(?.*)"); + Pattern.compile("of:N(img)?gpuT[^C]*C(?.*)"); private static PCIIDsDatabase getPCIIDsDatabase() { SoftReference databaseWeakReference = LinuxGPUDetector.databaseCache; @@ -136,12 +138,11 @@ final class LinuxGPUDetector { int pciDevice = Integer.parseInt(matcher.group("pciDevice"), 16); int pciFunc = Integer.parseInt(matcher.group("pciFunc"), 16); - builder.setVendor(GraphicsCard.Vendor.ofId(vendorId)); - + builder.setVendor(HardwareVendor.ofPciVendorId(vendorId)); detectDriver(builder, deviceDir); try { - if (builder.getVendor() == GraphicsCard.Vendor.AMD) { + if (builder.getVendor() == HardwareVendor.AMD) { Path hwmon = deviceDir.resolve("hwmon"); try (Stream subDirs = Files.list(hwmon)) { for (Path subDir : Lang.toIterable(subDirs)) { @@ -173,7 +174,7 @@ final class LinuxGPUDetector { } } - } else if (builder.getVendor() == GraphicsCard.Vendor.INTEL) { + } else if (builder.getVendor() == HardwareVendor.INTEL) { builder.setType(pciDevice == 20 ? GraphicsCard.Type.Integrated : GraphicsCard.Type.Discrete); } } catch (Throwable ignored) { @@ -185,7 +186,7 @@ final class LinuxGPUDetector { Vendor vendor = database.findVendor(vendorId); if (vendor != null) { if (builder.getVendor() == null) - builder.setVendor(GraphicsCard.Vendor.of(vendor.getName())); + builder.setVendor(HardwareVendor.of(vendor.getName())); if (builder.getName() == null) { Device device = vendor.getDevices().get(deviceId); @@ -223,13 +224,13 @@ final class LinuxGPUDetector { } if (builder.getType() == null) { - if (builder.getVendor() == GraphicsCard.Vendor.NVIDIA) { + if (builder.getVendor() == HardwareVendor.NVIDIA) { if (builder.getName().startsWith("GeForce") || builder.getName().startsWith("Quadro") || builder.getName().startsWith("Tesla")) builder.setType(GraphicsCard.Type.Discrete); - } else if (builder.getVendor() == GraphicsCard.Vendor.MOORE_THREADS) { + } else if (builder.getVendor() == HardwareVendor.MOORE_THREADS) { if (builder.getName().startsWith("MTT ")) builder.setType(GraphicsCard.Type.Discrete); } @@ -248,15 +249,25 @@ final class LinuxGPUDetector { String compatible = matcher.group("compatible"); int idx = compatible.indexOf(','); if (idx < 0) { - builder.setName(compatible.trim()); + String name = compatible.trim().toUpperCase(Locale.ROOT); + if (name.equals("IMG-GPU")) // Fucking Imagination + builder.setVendor(HardwareVendor.IMG); + else + builder.setName(name); } else { String vendorName = compatible.substring(0, idx).trim(); - GraphicsCard.Vendor vendor = GraphicsCard.Vendor.getKnown(vendorName); + HardwareVendor vendor = HardwareVendor.getKnown(vendorName); if (vendor == null) - vendor = new GraphicsCard.Vendor(vendorName.toUpperCase(Locale.ROOT)); + vendor = new HardwareVendor(StringUtils.capitalizeFirst(vendorName)); - builder.setName(vendor + " " + compatible.substring(idx + 1).trim()); builder.setVendor(vendor); + + String name = compatible.substring(idx + 1).trim().toUpperCase(Locale.ROOT); + if (vendor == HardwareVendor.IMG) { + if (!name.equals("GPU")) + builder.setName(vendor + " " + name); + } else + builder.setName(vendor + " " + name); } builder.setType(GraphicsCard.Type.Integrated); @@ -265,7 +276,7 @@ final class LinuxGPUDetector { return builder.build(); } - public static List detectAll() { + static List detect() { Path drm = Paths.get("/sys/class/drm"); if (!Files.isDirectory(drm)) return Collections.emptyList(); @@ -304,7 +315,6 @@ final class LinuxGPUDetector { LOG.warning("Failed to get graphics card info", e); } finally { databaseCache = null; - System.gc(); } return Collections.unmodifiableList(cards); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxHardwareDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxHardwareDetector.java index 831dea62f..486c78da1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxHardwareDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/linux/LinuxHardwareDetector.java @@ -18,8 +18,10 @@ package org.jackhuang.hmcl.util.platform.linux; 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.Nullable; import java.io.BufferedReader; import java.io.IOException; @@ -37,11 +39,18 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; */ public final class LinuxHardwareDetector extends HardwareDetector { + @Override + public @Nullable CentralProcessor detectCentralProcessor() { + if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX) + return null; + return LinuxCPUDetector.detect(); + } + @Override public List detectGraphicsCards() { if (OperatingSystem.CURRENT_OS != OperatingSystem.LINUX) return null; - return LinuxGPUDetector.detectAll(); + return LinuxGPUDetector.detect(); } private static final Path MEMINFO = Paths.get("/proc/meminfo"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/MacOSHardwareDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/MacOSHardwareDetector.java index 4214d62f7..ed6233738 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/MacOSHardwareDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/macos/MacOSHardwareDetector.java @@ -20,24 +20,21 @@ package org.jackhuang.hmcl.util.platform.macos; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.KeyValuePairUtils; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +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.HardwareVendor; +import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.*; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -46,34 +43,68 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; */ public final class MacOSHardwareDetector extends HardwareDetector { + @Override + public @Nullable CentralProcessor detectCentralProcessor() { + if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX) + return null; + + try { + Map values = SystemUtils.run(Arrays.asList("/usr/sbin/sysctl", "machdep.cpu"), + inputStream -> KeyValuePairUtils.loadProperties( + new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET)))); + + String brandString = values.get("machdep.cpu.brand_string"); + String coreCount = values.get("machdep.cpu.core_count"); + String threadCount = values.get("machdep.cpu.thread_count"); + String coresPerPackage = values.get("machdep.cpu.cores_per_package"); + + CentralProcessor.Builder builder = new CentralProcessor.Builder(); + + if (brandString != null) { + builder.setName(brandString); + + String lower = brandString.toLowerCase(Locale.ROOT); + if (lower.startsWith("apple")) + builder.setVendor(HardwareVendor.APPLE); + else if (lower.startsWith("intel")) + builder.setVendor(HardwareVendor.INTEL); + } else + builder.setName("Unknown"); + + if (coreCount != null || threadCount != null) { + int cores = coreCount != null ? Integer.parseInt(coreCount) : 0; + int threads = threadCount != null ? Integer.parseInt(threadCount) : 0; + int coresPerPackageCount = coresPerPackage != null ? Integer.parseInt(coresPerPackage) : 0; + + if (cores > 0 && threads == 0) + threads = cores; + else if (threads > 0 && cores == 0) + cores = threads; + + int packages = 1; + if (cores > 0 && coresPerPackageCount > 0) + packages = Integer.max(cores / coresPerPackageCount, 1); + + builder.setCores(new CentralProcessor.Cores(cores, threads, packages)); + } else + builder.setCores(new CentralProcessor.Cores(Runtime.getRuntime().availableProcessors())); + + return builder.build(); + } catch (Throwable e) { + LOG.warning("Failed to get CPU info", e); + return null; + } + } + @Override public List detectGraphicsCards() { if (OperatingSystem.CURRENT_OS != OperatingSystem.OSX) return null; - Process process = null; String json = null; try { - File devNull = new File("/dev/null"); - - Process finalProcess = process = new ProcessBuilder("/usr/sbin/system_profiler", - "SPDisplaysDataType", - "-json") - .redirectInput(devNull) - .redirectError(devNull) - .start(); - - CompletableFuture future = CompletableFuture.supplyAsync(Lang.wrap(() -> - IOUtils.readFullyAsString(finalProcess.getInputStream(), OperatingSystem.NATIVE_CHARSET)), - Schedulers.io()); - - if (!process.waitFor(15, TimeUnit.SECONDS)) - throw new TimeoutException(); - - if (process.exitValue() != 0) - throw new IOException("Bad exit code: " + process.exitValue()); - - json = future.get(); + json = SystemUtils.run(Arrays.asList("/usr/sbin/system_profiler", "SPDisplaysDataType", "-json"), + inputStream -> IOUtils.readFullyAsString(inputStream, OperatingSystem.NATIVE_CHARSET)); JsonObject object = JsonUtils.GSON.fromJson(json, JsonObject.class); JsonArray spDisplaysDataType = object.getAsJsonArray("SPDisplaysDataType"); @@ -98,7 +129,7 @@ public final class MacOSHardwareDetector extends HardwareDetector { .setName(model.getAsString()); if (vendor != null) - builder.setVendor(GraphicsCard.Vendor.of(StringUtils.removePrefix(vendor.getAsString(), "sppci_vendor_"))); + builder.setVendor(HardwareVendor.of(StringUtils.removePrefix(vendor.getAsString(), "sppci_vendor_"))); GraphicsCard.Type type = GraphicsCard.Type.Integrated; if (bus != null) { @@ -114,9 +145,6 @@ public final class MacOSHardwareDetector extends HardwareDetector { return Collections.unmodifiableList(cards); } } catch (Throwable e) { - if (process != null && process.isAlive()) - process.destroy(); - LOG.warning("Failed to get graphics card info" + (json != null ? ": " + json : ""), e); return Collections.emptyList(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Kernel32.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Kernel32.java index 854d58305..133cc46ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Kernel32.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/Kernel32.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.util.platform.windows; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; import com.sun.jna.win32.StdCallLibrary; import org.jackhuang.hmcl.util.platform.NativeUtils; @@ -53,4 +55,9 @@ public interface Kernel32 extends StdCallLibrary { * @see GlobalMemoryStatusEx function */ boolean GlobalMemoryStatusEx(WinTypes.MEMORYSTATUSEX lpBuffer); + + /** + * @see GetLogicalProcessorInformationEx function + */ + boolean GetLogicalProcessorInformationEx(int relationshipType, Pointer buffer, IntByReference returnedLength); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java index c44b929a4..e76a6c38d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinConstants.java @@ -81,7 +81,19 @@ public interface WinConstants { long HKEY_DYN_DATA = 0x80000006L; long HKEY_CURRENT_USER_LOCAL_SETTINGS = 0x80000007L; + // https://learn.microsoft.com/windows/win32/api/winnls/ne-winnls-sysgeoclass int GEOCLASS_NATION = 16; int GEOCLASS_REGION = 14; int GEOCLASS_ALL = 0; + + // https://learn.microsoft.com/windows/win32/api/winnt/ne-winnt-logical_processor_relationship + int RelationProcessorCore = 0; + int RelationNumaNode = 1; + int RelationCache = 2; + int RelationProcessorPackage = 3; + int RelationGroup = 4; + int RelationProcessorDie = 5; + int RelationNumaNodeEx = 6; + int RelationProcessorModule = 7; + int RelationAll = 0xffff; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java index dd2a894f7..afb15bd64 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinReg.java @@ -72,7 +72,20 @@ public abstract class WinReg { public abstract Object queryValue(HKEY root, String key, String valueName); - public abstract List queryKeys(HKEY root, String key); + public abstract List querySubKeyNames(HKEY root, String key); + + public List querySubKeys(HKEY root, String key) { + List list = querySubKeyNames(root, key); + if (list.isEmpty()) + return list; + + if (!(list instanceof ArrayList)) + list = new ArrayList<>(list); + + String prefix = key.endsWith("\\") ? key : key + "\\"; + list.replaceAll(str -> prefix + str); + return list; + } private static final class JNAWinReg extends WinReg { @@ -176,14 +189,13 @@ public abstract class WinReg { } @Override - public List queryKeys(HKEY root, String key) { + public List querySubKeyNames(HKEY root, String key) { PointerByReference phkKey = new PointerByReference(); if (advapi32.RegOpenKeyExW(root.toPointer(), new WString(key), 0, WinConstants.KEY_READ, phkKey) != WinConstants.ERROR_SUCCESS) return Collections.emptyList(); Pointer hkey = phkKey.getValue(); try { - String prefix = key.endsWith("\\") ? key : key + "\\"; ArrayList res = new ArrayList<>(); int maxKeyLength = 256; try (Memory lpName = new Memory(maxKeyLength * 2)) { @@ -194,7 +206,7 @@ public abstract class WinReg { lpcchName.setValue(maxKeyLength); int status = advapi32.RegEnumKeyExW(hkey, i, lpName, lpcchName, null, null, null, null); if (status == WinConstants.ERROR_SUCCESS) { - res.add(prefix + lpName.getWideString(0L)); + res.add(lpName.getWideString(0L)); i++; } else { if (status != WinConstants.ERROR_NO_MORE_ITEMS) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinTypes.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinTypes.java index 8f1b55c0d..1f639842a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinTypes.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WinTypes.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.util.platform.windows; import com.sun.jna.*; +import com.sun.jna.ptr.LongByReference; import java.util.Arrays; import java.util.List; @@ -87,4 +88,111 @@ public interface WinTypes { "ullTotalVirtual", "ullAvailVirtual", "ullAvailExtendedVirtual"); } } + + final class GROUP_AFFINITY extends Structure { + public LongByReference mask; + public short group; + public short[] reserved = new short[3]; + + public GROUP_AFFINITY(Pointer memory) { + super(memory); + } + + public GROUP_AFFINITY() { + super(); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList( + "mask", "group", "reserved" + ); + } + } + + /** + * @see PROCESSOR_GROUP_INFO structure + */ + final class PROCESSOR_GROUP_INFO extends Structure { + public byte maximumProcessorCount; + public byte activeProcessorCount; + public byte[] reserved = new byte[38]; + public LongByReference activeProcessorMask; + + public PROCESSOR_GROUP_INFO(Pointer memory) { + super(memory); + } + + public PROCESSOR_GROUP_INFO() { + super(); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList("maximumProcessorCount", "activeProcessorCount", "reserved", "activeProcessorMask"); + } + } + + /** + * @see PROCESSOR_RELATIONSHIP structure + */ + final class PROCESSOR_RELATIONSHIP extends Structure { + + public byte flags; + public byte efficiencyClass; + public byte[] reserved = new byte[20]; + public short groupCount; + public GROUP_AFFINITY[] groupMask = new GROUP_AFFINITY[1]; + + public PROCESSOR_RELATIONSHIP() { + } + + public PROCESSOR_RELATIONSHIP(Pointer memory) { + super(memory); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList("flags", "efficiencyClass", "reserved", "groupCount", "groupMask"); + } + + @Override + public void read() { + readField("groupCount"); + if (groupCount != groupMask.length) { + groupMask = new GROUP_AFFINITY[groupCount]; + } + super.read(); + } + } + + /** + * @see GROUP_RELATIONSHIP structure + */ + final class GROUP_RELATIONSHIP extends Structure { + public short maximumGroupCount; + public short activeGroupCount; + public byte[] reserved = new byte[20]; + public PROCESSOR_GROUP_INFO[] groupInfo = new PROCESSOR_GROUP_INFO[1]; + + public GROUP_RELATIONSHIP() { + } + + public GROUP_RELATIONSHIP(Pointer memory) { + super(memory); + } + + @Override + protected List getFieldOrder() { + return Arrays.asList("maximumGroupCount", "activeGroupCount", "reserved", "groupInfo"); + } + + @Override + public void read() { + readField("activeGroupCount"); + if (activeGroupCount != groupInfo.length) + groupInfo = new PROCESSOR_GROUP_INFO[activeGroupCount]; + super.read(); + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsCPUDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsCPUDetector.java new file mode 100644 index 000000000..589b95617 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsCPUDetector.java @@ -0,0 +1,94 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.platform.windows; + +import com.sun.jna.Memory; +import com.sun.jna.ptr.IntByReference; +import org.jackhuang.hmcl.util.platform.hardware.CentralProcessor; +import org.jackhuang.hmcl.util.platform.hardware.HardwareVendor; +import org.jetbrains.annotations.Nullable; + +/** + * @author Glavo + */ +final class WindowsCPUDetector { + + private static void detectName(CentralProcessor.Builder builder, WinReg reg) { + Object name = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString"); + Object vendor = reg.queryValue(WinReg.HKEY.HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "VendorIdentifier"); + + if (name instanceof String) + builder.setName((String) name); + + if (vendor instanceof String) + builder.setVendor(HardwareVendor.of((String) vendor)); + } + + private static void detectCores(CentralProcessor.Builder builder, Kernel32 kernel32) { + int coresLogical = 0; + int coresPhysical = 0; + int packages = 0; + + IntByReference length = new IntByReference(); + if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, null, length) && length.getValue() == 0) + throw new AssertionError("Failed to get logical processor information length: " + kernel32.GetLastError()); + + try (Memory pProcessorInfo = new Memory(Integer.toUnsignedLong(length.getValue()))) { + if (!kernel32.GetLogicalProcessorInformationEx(WinConstants.RelationAll, pProcessorInfo, length)) + throw new AssertionError("Failed to get logical processor information length: " + kernel32.GetLastError()); + + for (long offset = 0L; offset < pProcessorInfo.size(); ) { + int relationship = pProcessorInfo.getInt(offset); + long size = Integer.toUnsignedLong(pProcessorInfo.getInt(offset + 4L)); + + if (relationship == WinConstants.RelationGroup) { + WinTypes.GROUP_RELATIONSHIP groupRelationship = new WinTypes.GROUP_RELATIONSHIP(pProcessorInfo.share(offset + 8L, size - 8L)); + groupRelationship.read(); + + int activeGroupCount = Short.toUnsignedInt(groupRelationship.activeGroupCount); + for (int i = 0; i < activeGroupCount; i++) { + coresLogical += Short.toUnsignedInt(groupRelationship.groupInfo[i].maximumProcessorCount); + } + } else if (relationship == WinConstants.RelationProcessorCore) + coresPhysical++; + else if (relationship == WinConstants.RelationProcessorPackage) + packages++; + + offset += size; + } + } + + builder.setCores(new CentralProcessor.Cores(coresPhysical, coresLogical, packages)); + } + + static @Nullable CentralProcessor detect() { + WinReg reg = WinReg.INSTANCE; + Kernel32 kernel32 = Kernel32.INSTANCE; + if (reg == null) + return null; + + CentralProcessor.Builder builder = new CentralProcessor.Builder(); + detectName(builder, reg); + if (kernel32 != null) + detectCores(builder, kernel32); + return builder.build(); + } + + private WindowsCPUDetector() { + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java index 30d1bdeba..332eb89c4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/windows/WindowsHardwareDetector.java @@ -17,21 +17,20 @@ */ package org.jackhuang.hmcl.util.platform.windows; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.KeyValuePairUtils; import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.platform.NativeUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.platform.SystemUtils; +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.HardwareVendor; +import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -40,31 +39,11 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; */ public final class WindowsHardwareDetector extends HardwareDetector { - private static List> parsePowerShellFormatList(Iterable lines) { - ArrayList> result = new ArrayList<>(); - Map current = new LinkedHashMap<>(); - - for (String line : lines) { - int idx = line.indexOf(':'); - - if (idx < 0) { - if (!current.isEmpty()) { - result.add(current); - current = new LinkedHashMap<>(); - } - continue; - } - - String key = line.substring(0, idx).trim(); - String value = line.substring(idx + 1).trim(); - - current.put(key, value); - } - - if (!current.isEmpty()) - result.add(current); - - return result; + @Override + public @Nullable CentralProcessor detectCentralProcessor() { + if (!OperatingSystem.isWindows7OrLater()) + return null; + return WindowsCPUDetector.detect(); } @Override @@ -72,39 +51,21 @@ public final class WindowsHardwareDetector extends HardwareDetector { if (!OperatingSystem.isWindows7OrLater()) return null; - Process process = null; - String list = null; try { - File nul = new File("NUL"); - String getCimInstance = OperatingSystem.SYSTEM_VERSION.startsWith("6.1") ? "Get-WmiObject" : "Get-CimInstance"; - Process finalProcess = process = new ProcessBuilder("powershell.exe", - "-Command", - String.join(" | ", - getCimInstance + " -Class Win32_VideoController", - "Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType", - "Format-List" - )) - .redirectInput(nul) - .redirectError(nul) - .start(); + List> videoControllers = SystemUtils.run(Arrays.asList( + "powershell.exe", + "-Command", + String.join(" | ", + getCimInstance + " -Class Win32_VideoController", + "Select-Object Name,AdapterCompatibility,DriverVersion,AdapterDACType", + "Format-List" + )), + inputStream -> KeyValuePairUtils.loadList(new BufferedReader(new InputStreamReader(inputStream, OperatingSystem.NATIVE_CHARSET)))); - CompletableFuture future = CompletableFuture.supplyAsync(Lang.wrap(() -> - IOUtils.readFullyAsString(finalProcess.getInputStream(), OperatingSystem.NATIVE_CHARSET)), - Schedulers.io()); - - if (!process.waitFor(15, TimeUnit.SECONDS)) - throw new TimeoutException(); - - if (process.exitValue() != 0) - throw new IOException("Bad exit code: " + process.exitValue()); - - list = future.get(); - - List> videoControllers = parsePowerShellFormatList(Arrays.asList(list.split("\\R"))); ArrayList cards = new ArrayList<>(videoControllers.size()); for (Map videoController : videoControllers) { String name = videoController.get("Name"); @@ -114,7 +75,7 @@ public final class WindowsHardwareDetector extends HardwareDetector { if (StringUtils.isNotBlank(name)) { cards.add(GraphicsCard.builder().setName(name) - .setVendor(GraphicsCard.Vendor.of(adapterCompatibility)) + .setVendor(HardwareVendor.of(adapterCompatibility)) .setDriverVersion(driverVersion) .setType(StringUtils.isBlank(adapterDACType) || "Internal".equalsIgnoreCase(adapterDACType) @@ -128,9 +89,7 @@ public final class WindowsHardwareDetector extends HardwareDetector { return cards; } catch (Throwable e) { - if (process != null && process.isAlive()) - process.destroy(); - LOG.warning("Failed to get graphics card info" + (list != null ? ": " + list : ""), e); + LOG.warning("Failed to get graphics card info", e); return Collections.emptyList(); } } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairUtilsTest.java similarity index 78% rename from HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java rename to HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairUtilsTest.java index 5a04f7cb9..8f567aea1 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairPropertiesTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/KeyValuePairUtilsTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; +import java.util.Map; import static org.jackhuang.hmcl.util.Pair.pair; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -12,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Glavo */ -public final class KeyValuePairPropertiesTest { +public final class KeyValuePairUtilsTest { @Test public void test() throws IOException { String content = "#test: key0=value0\n \n" + @@ -20,7 +21,7 @@ public final class KeyValuePairPropertiesTest { "key2=\"value2\"\n" + "key3=\"\\\" \\n\"\n"; - KeyValuePairProperties properties = KeyValuePairProperties.load(new BufferedReader(new StringReader(content))); + Map properties = KeyValuePairUtils.loadProperties(new BufferedReader(new StringReader(content))); assertEquals(Lang.mapOf( pair("key1", "value1"), diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java index 80e5dc706..897fea5f2 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/platform/windows/WinRegTest.java @@ -99,15 +99,15 @@ public final class WinRegTest { } @Test - public void testQueryKeys() { + public void testQuerySubKeys() { WinReg.HKEY hkey = WinReg.HKEY.HKEY_CURRENT_USER; WinReg reg = WinReg.INSTANCE; assertEquals(Arrays.asList(SUBKEYS).stream().map(it -> key + "\\" + it).collect(Collectors.toList()), - reg.queryKeys(hkey, key).stream().sorted().collect(Collectors.toList())); + reg.querySubKeys(hkey, key).stream().sorted().collect(Collectors.toList())); for (String subkey : SUBKEYS) { - assertEquals(Collections.emptyList(), reg.queryKeys(hkey, key + "\\" + subkey)); + assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + "\\" + subkey)); } - assertEquals(Collections.emptyList(), reg.queryKeys(hkey, key + "\\NOT_EXIST")); + assertEquals(Collections.emptyList(), reg.querySubKeys(hkey, key + "\\NOT_EXIST")); } }