diff --git a/.gitignore b/.gitignore
index 34dc4e8..3f74515 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
*.user
*.suo
+*.pfx
Bin/
obj/
-packages/
-.vs/
\ No newline at end of file
+packages/
+.vs/
+.omo/
+4060.ps1
\ No newline at end of file
diff --git a/App/GpuAppManager.cs b/App/GpuAppManager.cs
index 86c7201..14c2d78 100644
--- a/App/GpuAppManager.cs
+++ b/App/GpuAppManager.cs
@@ -124,6 +124,7 @@ public static int GetMemoryClockOffset() {
}
public static int GetGraphicsBoostClock() {
+ NVIDIA.Initialize();
try {
PhysicalGPU[] gpus = PhysicalGPU.GetPhysicalGPUs();
@@ -143,12 +144,13 @@ public static int GetGraphicsBoostClock() {
}
}
} catch {
- }
+ } finally { NVIDIA.Unload(); }
return 0;
}
public static int GetMemoryBoostClock() {
+ NVIDIA.Initialize();
try {
PhysicalGPU[] gpus = PhysicalGPU.GetPhysicalGPUs();
@@ -168,7 +170,7 @@ public static int GetMemoryBoostClock() {
}
}
} catch {
- }
+ } finally { NVIDIA.Unload(); }
return 0;
}
@@ -219,7 +221,7 @@ public static void RestartGpu() {
// 2. 通过 ExecuteCommand 执行 pnputil 重启设备
string command = $"pnputil /restart-device \"{instanceId}\"";
- ProcessResult result = ExecuteCommand(command);
+ ProcessResult result = RestartGpu(instanceId);
// 可选:根据结果给出提示
if (result.ExitCode != 0) {
@@ -230,6 +232,14 @@ public static void RestartGpu() {
}
}
+ public static ProcessResult RestartGpu(string instanceId) {
+ if (string.IsNullOrWhiteSpace(instanceId))
+ return new ProcessResult { ExitCode = -1, Error = Strings.DeviceNotFound };
+ string safeId = instanceId.Replace(((char)34).ToString(), "");
+ string command = string.Format("pnputil /restart-device {0}{1}{0}", (char)34, safeId);
+ return ExecuteCommand(command);
+ }
+
///
/// 获取所有显卡名称列表(跳过 Microsoft 基本显示适配器)
///
@@ -313,14 +323,7 @@ public static string GetGpuModelFromNvidiaSmi() {
/// 是否存在 NVIDIA 独显。
public static bool HasNvidiaGpu() {
- try {
- var gpus = PhysicalGPU.GetPhysicalGPUs();
-
- return gpus != null &&
- gpus.Length > 0;
- } catch {
- return false;
- }
+ return GetNvidiaGpuInfoList().Count > 0;
}
///
diff --git a/App/GpuSleepManager.cs b/App/GpuSleepManager.cs
new file mode 100644
index 0000000..997d84d
--- /dev/null
+++ b/App/GpuSleepManager.cs
@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Management;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.RegularExpressions;
+using Microsoft.Win32;
+
+namespace OmenSuperHub {
+ public static class GpuSleepManager {
+ const string PreferencesPath = @"Software\Microsoft\DirectX\UserGpuPreferences";
+ const string TransactionsPath = @"Software\OmenSuperHub\GpuPreferenceTransactions";
+ static Guid displayClassGuid = new Guid("4d36e968-e325-11ce-bfc1-08002be10318");
+
+ public enum DevicePowerState { Unknown, D0, D1, D2, D3 }
+ public sealed class GpuDeviceInfo {
+ public string Name, InstanceId;
+ public bool IsEnabled, IsPhysical, IsNvidia, HasCurrentDisplayMode;
+ public DevicePowerState PowerState;
+ }
+ public sealed class GpuProcessInfo {
+ public int ProcessId;
+ public string ProcessName, ExecutablePath, UsageType, PreferenceRawValue, PreferenceDisplay;
+ public bool IsSystemProcess, CanConfigurePreference, PreferenceEntryExisted;
+ }
+ public sealed class PreferenceEntry {
+ public string ExecutablePath, ProcessName, OriginalValue, AppliedValue;
+ public bool OriginalEntryExisted;
+ }
+ public sealed class PreferenceTransaction {
+ public string Id;
+ public readonly List Entries = new List();
+ }
+ public sealed class RestoreResult {
+ public bool Success;
+ public string Error;
+ public readonly List RestoredPaths = new List();
+ public readonly List ConflictedPaths = new List();
+ }
+
+ public static List GetDisplayAdapters() {
+ var result = new List();
+ try {
+ using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController"))
+ using (var objects = searcher.Get()) {
+ foreach (ManagementObject item in objects) {
+ string id = item["PNPDeviceID"]?.ToString() ?? "";
+ string name = item["Name"]?.ToString() ?? "";
+ int error = -1;
+ if (item["ConfigManagerErrorCode"] != null) int.TryParse(item["ConfigManagerErrorCode"].ToString(), out error);
+ result.Add(new GpuDeviceInfo {
+ Name = name,
+ InstanceId = id,
+ IsEnabled = error == 0,
+ IsPhysical = id.StartsWith("PCI" + (char)92, StringComparison.OrdinalIgnoreCase) && !ContainsAny(name, "Microsoft", "Remote", "Virtual"),
+ IsNvidia = id.IndexOf("VEN_10DE", StringComparison.OrdinalIgnoreCase) >= 0 || ContainsAny(name, "NVIDIA"),
+ HasCurrentDisplayMode = item["CurrentHorizontalResolution"] != null,
+ PowerState = GetDevicePowerState(id)
+ });
+ }
+ }
+ } catch (Exception ex) { Logger.Error("GPU enumeration failed: " + ex.Message); }
+ return result;
+ }
+
+ public static bool TryGetNvidiaSleepTarget(out GpuDeviceInfo target, out string reason) {
+ target = null;
+ reason = null;
+ List enabled = GetDisplayAdapters().Where(g => g.IsPhysical && g.IsEnabled).ToList();
+ if (enabled.Count < 2) { reason = Strings.GpuSleepNeedsSecondGpu; return false; }
+ target = enabled.FirstOrDefault(g => g.IsNvidia);
+ if (target == null) { reason = Strings.GpuSleepTargetNotFound; return false; }
+ if (target.HasCurrentDisplayMode) { reason = Strings.GpuSleepTargetHasActiveDisplay; return false; }
+ string targetId = target.InstanceId;
+ if (!enabled.Any(g => !Same(g.InstanceId, targetId) && g.HasCurrentDisplayMode)) {
+ reason = Strings.GpuSleepNoActiveFallbackGpu;
+ return false;
+ }
+ return true;
+ }
+
+ public static List GetNvidiaProcesses() {
+ var found = new Dictionary();
+ try {
+ var command = GpuAppManager.ExecuteCommand("nvidia-smi");
+ if (command.ExitCode == 0) {
+ var rx = new Regex(@"^\|\s*\d+\s+\S+\s+\S+\s+(?\d+)\s+(?[A-Z+]+)\s+(?.+?)\s+(?:N/A|\d+MiB)\s*\|\s*$", RegexOptions.IgnoreCase);
+ foreach (string line in command.Output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) {
+ Match m = rx.Match(line);
+ if (m.Success && int.TryParse(m.Groups["pid"].Value, out int pid)) AddProcess(found, pid, m.Groups["name"].Value.Trim(), m.Groups["type"].Value);
+ }
+ }
+ } catch (Exception ex) { Logger.Error("NVIDIA process query failed: " + ex.Message); }
+ try {
+ var command = GpuAppManager.ExecuteCommand("nvidia-smi --query-compute-apps=pid,process_name --format=csv,noheader");
+ if (command.ExitCode == 0) foreach (string line in command.Output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) {
+ int comma = line.IndexOf(',');
+ if (comma > 0 && int.TryParse(line.Substring(0, comma).Trim(), out int pid)) AddProcess(found, pid, line.Substring(comma + 1).Trim(), "C");
+ }
+ } catch { }
+ foreach (GpuProcessInfo process in found.Values) PopulateProcess(process);
+ return found.Values.OrderBy(p => p.IsSystemProcess).ThenBy(p => p.ProcessName).ToList();
+ }
+
+ public static string ApplyPowerSavingPreferences(IEnumerable processes) {
+ List selected = processes.Where(p => p != null && p.CanConfigurePreference)
+ .GroupBy(p => p.ExecutablePath, StringComparer.OrdinalIgnoreCase).Select(g => g.First()).ToList();
+ if (selected.Count == 0) return null;
+ string id = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff") + "-" + Guid.NewGuid().ToString("N").Substring(0, 8);
+ using (RegistryKey transaction = Registry.CurrentUser.CreateSubKey(TransactionsPath + (char)92 + id))
+ using (RegistryKey preferences = Registry.CurrentUser.CreateSubKey(PreferencesPath)) {
+ transaction.SetValue("CreatedUtc", DateTime.UtcNow.ToString("O"));
+ int index = 0;
+ foreach (GpuProcessInfo process in selected) {
+ object originalObject = preferences.GetValue(process.ExecutablePath, null, RegistryValueOptions.DoNotExpandEnvironmentNames);
+ string original = originalObject?.ToString() ?? "";
+ string applied = SetPreference(original, 1);
+ using (RegistryKey entry = transaction.CreateSubKey((index++).ToString())) {
+ entry.SetValue("Path", process.ExecutablePath);
+ entry.SetValue("Name", process.ProcessName ?? "");
+ entry.SetValue("Existed", originalObject != null ? 1 : 0);
+ entry.SetValue("Original", original);
+ entry.SetValue("Applied", applied);
+ }
+ preferences.SetValue(process.ExecutablePath, applied, RegistryValueKind.String);
+ }
+ }
+ return id;
+ }
+
+ public static PreferenceTransaction LoadTransaction(string id) {
+ if (string.IsNullOrWhiteSpace(id)) return null;
+ using (RegistryKey transaction = Registry.CurrentUser.OpenSubKey(TransactionsPath + (char)92 + id)) {
+ if (transaction == null) return null;
+ var result = new PreferenceTransaction { Id = id };
+ foreach (string child in transaction.GetSubKeyNames().OrderBy(n => n)) using (RegistryKey entry = transaction.OpenSubKey(child)) {
+ result.Entries.Add(new PreferenceEntry {
+ ExecutablePath = entry.GetValue("Path", "").ToString(),
+ ProcessName = entry.GetValue("Name", "").ToString(),
+ OriginalEntryExisted = Convert.ToInt32(entry.GetValue("Existed", 0)) != 0,
+ OriginalValue = entry.GetValue("Original", "").ToString(),
+ AppliedValue = entry.GetValue("Applied", "").ToString()
+ });
+ }
+ return result;
+ }
+ }
+
+ public static string GetLatestTransactionId() {
+ using (RegistryKey root = Registry.CurrentUser.OpenSubKey(TransactionsPath)) return root?.GetSubKeyNames().OrderByDescending(n => n).FirstOrDefault();
+ }
+
+ public static RestoreResult RestorePreferences(string id, bool overwriteConflicts) {
+ var result = new RestoreResult();
+ try {
+ PreferenceTransaction transaction = LoadTransaction(id);
+ if (transaction == null) { result.Error = Strings.GpuPreferenceBackupMissing; return result; }
+ using (RegistryKey preferences = Registry.CurrentUser.CreateSubKey(PreferencesPath)) {
+ if (!overwriteConflicts) foreach (PreferenceEntry entry in transaction.Entries) {
+ string current = preferences.GetValue(entry.ExecutablePath, "", RegistryValueOptions.DoNotExpandEnvironmentNames)?.ToString() ?? "";
+ if (!Same(current, entry.AppliedValue)) result.ConflictedPaths.Add(entry.ExecutablePath);
+ }
+ if (result.ConflictedPaths.Count > 0) return result;
+ foreach (PreferenceEntry entry in transaction.Entries) {
+ if (entry.OriginalEntryExisted) preferences.SetValue(entry.ExecutablePath, entry.OriginalValue ?? "", RegistryValueKind.String);
+ else preferences.DeleteValue(entry.ExecutablePath, false);
+ result.RestoredPaths.Add(entry.ExecutablePath);
+ }
+ }
+ if (result.ConflictedPaths.Count == 0 || overwriteConflicts) {
+ Registry.CurrentUser.DeleteSubKeyTree(TransactionsPath + (char)92 + id, false);
+ result.Success = true;
+ }
+ } catch (Exception ex) { result.Error = ex.Message; Logger.Error("GPU preference restore failed: " + ex.Message); }
+ return result;
+ }
+
+ public static void RestartApplications(IEnumerable applications, bool force) {
+ foreach (GpuProcessInfo app in applications.GroupBy(p => p.ExecutablePath ?? p.ProcessName, StringComparer.OrdinalIgnoreCase).Select(g => g.First())) {
+ try {
+ string processName = (app.ProcessName ?? "").Replace(".exe", "").Trim();
+ if (Same(processName, "dwm")) {
+ Process dwm = app.ProcessId > 0 ? Process.GetProcessById(app.ProcessId) : Process.GetProcessesByName("dwm").FirstOrDefault();
+ dwm?.Kill();
+ continue;
+ }
+ foreach (Process process in Process.GetProcessesByName(processName)) {
+ bool closed = false;
+ try { if (process.CloseMainWindow()) closed = process.WaitForExit(3000); } catch { }
+ if (!closed && force) try { process.Kill(); process.WaitForExit(3000); } catch { }
+ }
+ if (Same(processName, "explorer")) Process.Start(new ProcessStartInfo(Environment.ExpandEnvironmentVariables(@"%WINDIR%\explorer.exe")) { UseShellExecute = true });
+ else if (!string.IsNullOrWhiteSpace(app.ExecutablePath) && System.IO.File.Exists(app.ExecutablePath)) Process.Start(new ProcessStartInfo(app.ExecutablePath) { UseShellExecute = true, WorkingDirectory = System.IO.Path.GetDirectoryName(app.ExecutablePath) });
+ } catch (Exception ex) { Logger.Error("Application restart failed for " + app.ProcessName + ": " + ex.Message); }
+ }
+ }
+
+ public static DevicePowerState GetDevicePowerState(string instanceId) {
+ if (string.IsNullOrWhiteSpace(instanceId)) return DevicePowerState.Unknown;
+ IntPtr set = SetupDiGetClassDevs(ref displayClassGuid, null, IntPtr.Zero, 2);
+ if (set == new IntPtr(-1)) return DevicePowerState.Unknown;
+ try {
+ uint index = 0;
+ var data = new SP_DEVINFO_DATA { cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA)) };
+ while (SetupDiEnumDeviceInfo(set, index++, ref data)) {
+ var id = new StringBuilder(512);
+ if (!SetupDiGetDeviceInstanceId(set, ref data, id, id.Capacity, out _) || !Same(id.ToString(), instanceId)) continue;
+ byte[] buffer = new byte[128];
+ if (!SetupDiGetDeviceRegistryProperty(set, ref data, 0x1E, out _, buffer, (uint)buffer.Length, out uint needed) || needed < 8) return DevicePowerState.Unknown;
+ int state = BitConverter.ToInt32(buffer, 4);
+ return state >= 1 && state <= 4 ? (DevicePowerState)state : DevicePowerState.Unknown;
+ }
+ } finally { SetupDiDestroyDeviceInfoList(set); }
+ return DevicePowerState.Unknown;
+ }
+
+ static void AddProcess(Dictionary found, int pid, string name, string usage) {
+ if (!found.TryGetValue(pid, out GpuProcessInfo process)) found[pid] = new GpuProcessInfo { ProcessId = pid, ProcessName = name, UsageType = usage };
+ else if ((process.UsageType ?? "").IndexOf(usage, StringComparison.OrdinalIgnoreCase) < 0) process.UsageType += "+" + usage;
+ }
+
+ static void PopulateProcess(GpuProcessInfo info) {
+ try { using (Process process = Process.GetProcessById(info.ProcessId)) { info.ProcessName = process.ProcessName + ".exe"; try { info.ExecutablePath = process.MainModule?.FileName; } catch { } } } catch { }
+ string name = (info.ProcessName ?? "").ToLowerInvariant();
+ info.IsSystemProcess = name == "dwm.exe" || name == "explorer.exe" || name == "csrss.exe" || name == "winlogon.exe" || name == "system";
+ info.CanConfigurePreference = !string.IsNullOrWhiteSpace(info.ExecutablePath) && name != "csrss.exe" && name != "winlogon.exe" && name != "system";
+ using (RegistryKey key = Registry.CurrentUser.OpenSubKey(PreferencesPath)) {
+ object raw = key?.GetValue(info.ExecutablePath ?? "", null, RegistryValueOptions.DoNotExpandEnvironmentNames);
+ info.PreferenceEntryExisted = raw != null;
+ info.PreferenceRawValue = raw?.ToString() ?? "";
+ }
+ Match match = Regex.Match(info.PreferenceRawValue, @"(?:^|;)\s*GpuPreference=(\d+)", RegexOptions.IgnoreCase);
+ info.PreferenceDisplay = !match.Success ? Strings.GpuPreferenceSystemDefault : match.Groups[1].Value == "1" ? Strings.GpuPreferencePowerSaving : match.Groups[1].Value == "2" ? Strings.GpuPreferenceHighPerformance : Strings.GpuPreferenceSystemDefault;
+ }
+
+ static string SetPreference(string raw, int preference) {
+ string value = Regex.Replace(raw ?? "", @"(?:^|;)\s*GpuPreference=\d+;?", ";", RegexOptions.IgnoreCase).Trim(';', ' ');
+ if (value.Length > 0) value += ";";
+ return value + "GpuPreference=" + preference + ";";
+ }
+ static bool ContainsAny(string value, params string[] terms) { return terms.Any(t => (value ?? "").IndexOf(t, StringComparison.OrdinalIgnoreCase) >= 0); }
+ static bool Same(string a, string b) { return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); }
+
+ [StructLayout(LayoutKind.Sequential)] struct SP_DEVINFO_DATA { public uint cbSize; public Guid ClassGuid; public uint DevInst; public IntPtr Reserved; }
+ [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr SetupDiGetClassDevs(ref Guid guid, string enumerator, IntPtr parent, uint flags);
+ [DllImport("setupapi.dll", SetLastError = true)] static extern bool SetupDiEnumDeviceInfo(IntPtr set, uint index, ref SP_DEVINFO_DATA data);
+ [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern bool SetupDiGetDeviceInstanceId(IntPtr set, ref SP_DEVINFO_DATA data, StringBuilder id, int size, out int required);
+ [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern bool SetupDiGetDeviceRegistryProperty(IntPtr set, ref SP_DEVINFO_DATA data, uint property, out uint type, byte[] buffer, uint size, out uint required);
+ [DllImport("setupapi.dll", SetLastError = true)] static extern bool SetupDiDestroyDeviceInfoList(IntPtr set);
+ }
+}
diff --git a/GpuSleepRepairForm.cs b/GpuSleepRepairForm.cs
new file mode 100644
index 0000000..a330d95
--- /dev/null
+++ b/GpuSleepRepairForm.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace OmenSuperHub {
+ public sealed class GpuSleepRepairForm : Form {
+ readonly List processes;
+ readonly ListView processList;
+ readonly Button applyButton, restartButton, retryButton, restoreButton;
+ public string TransactionId { get; private set; }
+
+ public GpuSleepRepairForm(IEnumerable detected) {
+ processes = detected?.ToList() ?? new List();
+ Text = Strings.GpuSleepRepairTitle;
+ StartPosition = FormStartPosition.CenterScreen;
+ Size = new Size(920, 520);
+ MinimizeBox = false;
+ MaximizeBox = false;
+
+ var description = new Label {
+ Dock = DockStyle.Top,
+ Height = 58,
+ Padding = new Padding(10),
+ Text = Strings.GpuSleepRepairDescription
+ };
+ processList = new ListView {
+ Dock = DockStyle.Fill,
+ CheckBoxes = true,
+ FullRowSelect = true,
+ GridLines = true,
+ View = View.Details
+ };
+ processList.Columns.Add(Strings.GpuSleepProcessColumn, 170);
+ processList.Columns.Add("PID", 70);
+ processList.Columns.Add(Strings.GpuSleepUsageColumn, 90);
+ processList.Columns.Add(Strings.GpuSleepPreferenceColumn, 130);
+ processList.Columns.Add(Strings.GpuSleepCustomListColumn, 90);
+ processList.Columns.Add(Strings.GpuSleepPathColumn, 330);
+ foreach (GpuSleepManager.GpuProcessInfo process in processes) {
+ var item = new ListViewItem(process.ProcessName ?? "?") { Tag = process, Checked = process.CanConfigurePreference && !process.IsSystemProcess };
+ item.SubItems.Add(process.ProcessId.ToString());
+ item.SubItems.Add(process.UsageType ?? "?");
+ item.SubItems.Add(process.PreferenceDisplay ?? Strings.GpuPreferenceUnavailable);
+ item.SubItems.Add(process.PreferenceEntryExisted ? Strings.Yes : Strings.No);
+ item.SubItems.Add(process.ExecutablePath ?? Strings.GpuPreferenceUnavailable);
+ if (!process.CanConfigurePreference) item.ForeColor = Color.Gray;
+ processList.Items.Add(item);
+ }
+
+ var buttons = new FlowLayoutPanel { Dock = DockStyle.Bottom, Height = 52, FlowDirection = FlowDirection.RightToLeft, Padding = new Padding(6) };
+ var closeButton = new Button { Text = Strings.Cancel, AutoSize = true };
+ closeButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
+ retryButton = new Button { Text = Strings.GpuSleepRetry, AutoSize = true, Enabled = false };
+ retryButton.Click += (s, e) => { DialogResult = DialogResult.OK; Close(); };
+ restartButton = new Button { Text = Strings.GpuSleepRestartSelected, AutoSize = true, Enabled = false };
+ restartButton.Click += RestartSelected;
+ restoreButton = new Button { Text = Strings.GpuPreferenceRestore, AutoSize = true, Enabled = false };
+ restoreButton.Click += RestorePreferences;
+ applyButton = new Button { Text = Strings.GpuPreferenceApplyPowerSaving, AutoSize = true };
+ applyButton.Click += ApplyPreferences;
+ buttons.Controls.Add(closeButton);
+ buttons.Controls.Add(retryButton);
+ buttons.Controls.Add(restartButton);
+ buttons.Controls.Add(restoreButton);
+ buttons.Controls.Add(applyButton);
+ Controls.Add(processList);
+ Controls.Add(description);
+ Controls.Add(buttons);
+ }
+
+ List SelectedProcesses() {
+ return processList.CheckedItems.Cast()
+ .Select(i => i.Tag as GpuSleepManager.GpuProcessInfo)
+ .Where(p => p != null && p.CanConfigurePreference).ToList();
+ }
+
+ void ApplyPreferences(object sender, EventArgs e) {
+ List selected = SelectedProcesses();
+ if (selected.Count == 0) { MessageBox.Show(this, Strings.GpuSleepSelectApplication, Strings.Hint); return; }
+ if (selected.Any(p => string.Equals(p.ProcessName, "dwm.exe", StringComparison.OrdinalIgnoreCase)) &&
+ MessageBox.Show(this, Strings.GpuSleepDwmWarning, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
+ try {
+ TransactionId = GpuSleepManager.ApplyPowerSavingPreferences(selected);
+ if (string.IsNullOrEmpty(TransactionId)) return;
+ applyButton.Enabled = false;
+ restartButton.Enabled = true;
+ restoreButton.Enabled = true;
+ MessageBox.Show(this, Strings.GpuPreferenceAppliedRestartRequired, Strings.Hint, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ } catch (Exception ex) {
+ MessageBox.Show(this, Strings.GpuPreferenceApplyFailed(ex.Message), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+
+ void RestartSelected(object sender, EventArgs e) {
+ List selected = SelectedProcesses();
+ if (MessageBox.Show(this, Strings.GpuSleepRestartWarning, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
+ GpuSleepManager.RestartApplications(selected, true);
+ retryButton.Enabled = true;
+ MessageBox.Show(this, Strings.GpuSleepApplicationsRestarted, Strings.Hint, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+
+ void RestorePreferences(object sender, EventArgs e) {
+ if (string.IsNullOrEmpty(TransactionId)) return;
+ GpuSleepManager.RestoreResult result = GpuSleepManager.RestorePreferences(TransactionId, false);
+ if (result.ConflictedPaths.Count > 0) {
+ if (MessageBox.Show(this, Strings.GpuPreferenceConflict, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
+ result = GpuSleepManager.RestorePreferences(TransactionId, true);
+ }
+ if (!result.Success) { MessageBox.Show(this, result.Error ?? Strings.GpuPreferenceRestoreFailed, Strings.Error); return; }
+ MessageBox.Show(this, Strings.GpuPreferenceRestoredRestartRequired, Strings.Hint);
+ if (MessageBox.Show(this, Strings.GpuSleepRestartWarning, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
+ GpuSleepManager.RestartApplications(SelectedProcesses(), true);
+ TransactionId = null;
+ restoreButton.Enabled = false;
+ restartButton.Enabled = false;
+ retryButton.Enabled = false;
+ applyButton.Enabled = true;
+ }
+ }
+}
diff --git a/OmenSuperHub.csproj b/OmenSuperHub.csproj
index 64da0a4..0661fa2 100644
--- a/OmenSuperHub.csproj
+++ b/OmenSuperHub.csproj
@@ -39,6 +39,7 @@
false
true
true
+ true
true
@@ -91,9 +92,6 @@
0FF25508E82D933D2889D9605B7F06A55C7283BB
-
- OmenSuperHub_TemporaryKey.pfx
-
false
@@ -141,6 +139,9 @@
Resources\PerformanceControl.dll
+
+ packages\System.Resources.Extensions.4.7.0\lib\netstandard2.0\System.Resources.Extensions.dll
+
packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll
True
@@ -304,6 +305,7 @@
Form
+
@@ -338,7 +340,6 @@
-
@@ -393,6 +394,10 @@
+
+
+ Form
+
@@ -414,4 +419,4 @@
-
\ No newline at end of file
+
diff --git a/OmenSuperHub_TemporaryKey.pfx b/OmenSuperHub_TemporaryKey.pfx
deleted file mode 100644
index aeb8d1c..0000000
Binary files a/OmenSuperHub_TemporaryKey.pfx and /dev/null differ
diff --git a/Program.Config.cs b/Program.Config.cs
index f458672..2fac626 100644
--- a/Program.Config.cs
+++ b/Program.Config.cs
@@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Threading;
+using System.Windows.Forms;
using Hp.Bridge.Client.SDKs.PerformanceControl.DataStructure;
using HP.Omen.Core.Common.NVidiaApi;
using HP.Omen.Core.Model.Device.Models;
@@ -341,6 +342,10 @@ public static void CleanUpAndRemoveTasks() {
static void RestoreCPUPower() {
// 恢复CPU功耗设定
+ if (currentPreset == FanLockPresetKey && fanLockCurrentPower >= 10 && fanLockCurrentPower <= 254) {
+ SetCpuPowerLimit((byte)fanLockCurrentPower);
+ return;
+ }
if (cpuPower.Contains(" W")) {
int value = int.Parse(cpuPower.Replace(" W", "").Trim());
if (isCPUPowerControlSupported && value >= 10 && value <= 254) {
@@ -349,6 +354,216 @@ static void RestoreCPUPower() {
}
}
+ static bool TryGetCurrentCpuPowerSetting(out int value) {
+ value = -1;
+ if (cpuPower == "max") {
+ value = 254;
+ return true;
+ }
+ if (!string.IsNullOrEmpty(cpuPower) && cpuPower.Contains(" W")) {
+ int parsed;
+ if (int.TryParse(cpuPower.Replace(" W", "").Trim(), out parsed) && parsed >= 10 && parsed <= 254) {
+ value = parsed;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static void LoadFanLockConfig(RegistryKey key) {
+ if (key == null) return;
+ fanLockMinimumPower = Math.Max(10, Math.Min(254, Convert.ToInt32(key.GetValue("FanLockMinimumPower", 20))));
+ fanLockMaximumPower = Math.Max(fanLockMinimumPower, Math.Min(254, Convert.ToInt32(key.GetValue("FanLockMaximumPower", 120))));
+ fanLockTargetRpm = Math.Max(0, Convert.ToInt32(key.GetValue("FanLockTargetRpm", 3000))) / 100 * 100;
+ fanLockTargetTemperature = Math.Max(40, Convert.ToInt32(key.GetValue("FanLockTargetTemperature", 80)));
+ fanLockReturnPreset = (string)key.GetValue("FanLockReturnPreset", "PresetCustom1");
+ if (fanLockReturnPreset == FanLockPresetKey || !PresetOrder.Contains(fanLockReturnPreset))
+ fanLockReturnPreset = "PresetCustom1";
+ if (fanLockCurrentPower < 0) {
+ int savedStartPower = Convert.ToInt32(key.GetValue("FanLockStartPower", -1));
+ if (savedStartPower >= 10)
+ fanLockCurrentPower = Math.Max(fanLockMinimumPower, Math.Min(fanLockMaximumPower, savedStartPower));
+ }
+ }
+
+ static void SaveFanLockConfig() {
+ try {
+ using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\OmenSuperHub")) {
+ if (key == null) return;
+ key.SetValue("FanLockMinimumPower", fanLockMinimumPower);
+ key.SetValue("FanLockMaximumPower", fanLockMaximumPower);
+ key.SetValue("FanLockTargetRpm", fanLockTargetRpm);
+ key.SetValue("FanLockTargetTemperature", fanLockTargetTemperature);
+ key.SetValue("FanLockReturnPreset", fanLockReturnPreset);
+ if (fanLockCurrentPower >= 10)
+ key.SetValue("FanLockStartPower", fanLockCurrentPower);
+ }
+ } catch (Exception ex) {
+ Logger.Error($"SaveFanLockConfig: {ex.Message}");
+ }
+ }
+
+ static bool ActivateFanLockPreset() {
+ if (!isCPUPowerControlSupported) return false;
+
+ if (currentPreset != FanLockPresetKey) {
+ int startingPower;
+ if (!TryGetCurrentCpuPowerSetting(out startingPower)) {
+ MessageBox.Show(
+ Application.OpenForms.OfType().FirstOrDefault(),
+ Strings.FanLockNeedsCpuPower,
+ Strings.Hint,
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Warning);
+ return false;
+ }
+
+ fanLockReturnPreset = currentPreset;
+ fanLockCurrentPower = Math.Max(fanLockMinimumPower, Math.Min(fanLockMaximumPower, startingPower));
+ SavePresetToRegistry(FanLockPresetKey);
+ currentPreset = FanLockPresetKey;
+ SaveConfig("CurrentPreset");
+ SaveFanLockConfig();
+ }
+
+ LoadMonitorMetricSettings(FanLockPresetKey);
+ UpdateMonitorMetricCheckedStates();
+ ApplyFanLockTargets();
+ UpdateTrayIconText();
+ UpdateFloatingText();
+ return true;
+ }
+
+ static void EnsureCpuMonitorForFanLock() {
+ if (monitorCPU && hwMonitorProcess != null && !hwMonitorProcess.HasExited) return;
+ monitorCPU = true;
+ cpuTempReady = false;
+ if (hwMonitorProcess == null || hwMonitorProcess.HasExited)
+ StartHardwareMonitor();
+ else
+ SetCpuMonitorState(true);
+ UpdateCheckedState("monitorCPUGroup", Strings.MonitorCpuOn);
+ }
+
+ static void ApplyFanLockTargets() {
+ if (currentPreset != FanLockPresetKey) return;
+ EnsureCpuMonitorForFanLock();
+
+ int maximumRpm = platformMaxFanSpeed.HasValue && platformMaxFanSpeed.Value > 0
+ ? (int)(platformMaxFanSpeed.Value * 1.1)
+ : 6400;
+ fanLockTargetRpm = Math.Max(0, Math.Min(maximumRpm, fanLockTargetRpm));
+ fanLockTargetRpm = fanLockTargetRpm / 100 * 100;
+ fanLockTargetTemperature = Math.Max(40, Math.Min((maxCPUTemp ?? 97) - 3, fanLockTargetTemperature));
+ fanLockCurrentPower = Math.Max(fanLockMinimumPower, Math.Min(fanLockMaximumPower, fanLockCurrentPower));
+
+ fanControl = fanLockTargetRpm + " RPM";
+ SetMaxFanSpeedOff();
+ fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ SetFanLevel(fanLockTargetRpm / 100, fanLockTargetRpm / 100, Is3FanNb);
+ SetCpuPowerLimit((byte)fanLockCurrentPower);
+ SetFanLockControlledMenusEnabled(false);
+ UpdateCheckedState("fanControlGroup", Strings.SetFanSpeedSlider);
+ fanLockUpdatingControls = true;
+ try {
+ if (fanTrackBar != null)
+ fanTrackBar.Value = Math.Max(fanTrackBar.Minimum, Math.Min(fanTrackBar.Maximum, fanLockTargetRpm / 100));
+ if (cpuPowerTrackBar != null)
+ cpuPowerTrackBar.Value = Math.Max(cpuPowerTrackBar.Minimum, Math.Min(cpuPowerTrackBar.Maximum, fanLockCurrentPower));
+ } finally {
+ fanLockUpdatingControls = false;
+ }
+ if (fanValueLabel != null)
+ fanValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{fanLockTargetRpm} RPM");
+ if (cpuPowerValueLabel != null)
+ cpuPowerValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{fanLockCurrentPower} W");
+ }
+
+ static void AdjustFanLockPower() {
+ if (currentPreset != FanLockPresetKey || !monitorCPU || !cpuTempReady || fanLockCurrentPower < 0)
+ return;
+
+ int nextPower = fanLockCurrentPower;
+ if (smoothedCPUTemp > fanLockTargetTemperature + 1f)
+ nextPower--;
+ else if (smoothedCPUTemp < fanLockTargetTemperature - 1f)
+ nextPower++;
+
+ nextPower = Math.Max(fanLockMinimumPower, Math.Min(fanLockMaximumPower, nextPower));
+ if (nextPower == fanLockCurrentPower) return;
+
+ fanLockCurrentPower = nextPower;
+ SetCpuPowerLimit((byte)fanLockCurrentPower);
+ if (cpuPowerValueLabel != null && uiContext != null) {
+ uiContext.Post(_ => {
+ fanLockUpdatingControls = true;
+ try {
+ if (cpuPowerTrackBar != null)
+ cpuPowerTrackBar.Value = Math.Max(cpuPowerTrackBar.Minimum, Math.Min(cpuPowerTrackBar.Maximum, fanLockCurrentPower));
+ } finally {
+ fanLockUpdatingControls = false;
+ }
+ if (cpuPowerValueLabel != null)
+ cpuPowerValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{fanLockCurrentPower} W");
+ }, null);
+ }
+ }
+
+ static void LeaveFanLockPresetForSafety() {
+ if (currentPreset != FanLockPresetKey) return;
+ string targetPreset = fanLockReturnPreset;
+ if (targetPreset == FanLockPresetKey || !PresetOrder.Contains(targetPreset))
+ targetPreset = "PresetCustom1";
+ // 使用进入模式时保存的快照恢复全部设置,避免内置预设重新生成默认值。
+ LoadPresetFields(FanLockPresetKey);
+ LoadMonitorMetricSettings(FanLockPresetKey);
+ currentPreset = targetPreset;
+ SaveConfig("CurrentPreset");
+ SetFanLockControlledMenusEnabled(true);
+
+ var item = FindMenuItemByName(trayIcon.ContextMenuStrip.Items, currentPreset);
+ if (item != null)
+ UpdateCheckedState("presetsGroup", null, item);
+ ApplyPresetSettings(FanLockPresetKey);
+ UpdateMonitorMetricCheckedStates();
+ UpdateTrayIconText();
+ UpdateFloatingText();
+ }
+
+ // 返回 true 表示功耗已经处于下限,可以进入风扇安全切换阶段。
+ static bool PrepareFanLockOverheatProtection() {
+ if (currentPreset != FanLockPresetKey || fanLockCurrentPower <= fanLockMinimumPower)
+ return true;
+
+ int distanceToMinimum = fanLockCurrentPower - fanLockMinimumPower;
+ int reduction = Math.Max(5, (distanceToMinimum + 1) / 2);
+ fanLockCurrentPower = Math.Max(fanLockMinimumPower, fanLockCurrentPower - reduction);
+ SetCpuPowerLimit((byte)fanLockCurrentPower);
+
+ if (uiContext != null) {
+ uiContext.Post(_ => {
+ fanLockUpdatingControls = true;
+ try {
+ if (cpuPowerTrackBar != null)
+ cpuPowerTrackBar.Value = Math.Max(cpuPowerTrackBar.Minimum, Math.Min(cpuPowerTrackBar.Maximum, fanLockCurrentPower));
+ } finally {
+ fanLockUpdatingControls = false;
+ }
+ if (cpuPowerValueLabel != null)
+ cpuPowerValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{fanLockCurrentPower} W");
+ }, null);
+ }
+ return false;
+ }
+
+ static void SetFanLockControlledMenusEnabled(bool enabled) {
+ if (trayIcon == null || trayIcon.ContextMenuStrip == null) return;
+ var fanMenu = FindMenuItemByName(trayIcon.ContextMenuStrip.Items, "FanControlMenu");
+ var cpuPowerMenu = FindMenuItemByName(trayIcon.ContextMenuStrip.Items, "CpuPowerMenu");
+ if (fanMenu != null) fanMenu.Enabled = enabled;
+ if (cpuPowerMenu != null) cpuPowerMenu.Enabled = enabled;
+ }
+
static void RestorePowerConfig() {
SetUnleashMode();
System.Threading.Tasks.Task.Delay(1000).ContinueWith(_ => {
@@ -675,12 +890,6 @@ static bool IsMonitorMetricConfig(string configName) {
static void SaveConfig(string configName = null) {
// 内置预设下调整设置时,不再强制切换到 Custom1,直接保存注册表(不关联任何预设子键)
try {
- // 六项监控显示开关在自定义预设下只写入当前预设,避免污染内置预设的全局值。
- if (!IsBuiltInPreset(currentPreset) && IsMonitorMetricConfig(configName)) {
- SavePresetToRegistry(currentPreset);
- return;
- }
-
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\OmenSuperHub")) {
if (key != null) {
if (configName == null) {
@@ -716,14 +925,12 @@ static void SaveConfig(string configName = null) {
key.SetValue("MonitorFan", monitorFan);
key.SetValue("MonitorRefreshRate", monitorRefreshRate);
key.SetValue("TempDisplayMode", tempDisplayMode);
- if (IsBuiltInPreset(currentPreset)) {
- key.SetValue("ShowCPUTemp", showCPUTemp);
- key.SetValue("ShowCPUPower", showCPUPower);
- key.SetValue("ShowCPUFrequency", showCPUFrequency);
- key.SetValue("ShowGPUTemp", showGPUTemp);
- key.SetValue("ShowGPUPower", showGPUPower);
- key.SetValue("ShowGPUFrequency", showGPUFrequency);
- }
+ key.SetValue("ShowCPUTemp", showCPUTemp);
+ key.SetValue("ShowCPUPower", showCPUPower);
+ key.SetValue("ShowCPUFrequency", showCPUFrequency);
+ key.SetValue("ShowGPUTemp", showGPUTemp);
+ key.SetValue("ShowGPUPower", showGPUPower);
+ key.SetValue("ShowGPUFrequency", showGPUFrequency);
key.SetValue("FloatingBarLoc", floatingBarLoc);
key.SetValue("FloatingBar", floatingBar);
key.SetValue("FloatingBarScreen", floatingBarScreen);
@@ -956,6 +1163,11 @@ static void LoadMonitorMetricSettings(string presetKey) {
try {
using (RegistryKey globalKey = Registry.CurrentUser.OpenSubKey(@"Software\OmenSuperHub")) {
if (globalKey != null) {
+ monitorCPU = Convert.ToBoolean(globalKey.GetValue("MonitorCPU", true));
+ monitorGPU = hasNVIDIAGpu && Convert.ToBoolean(globalKey.GetValue("MonitorGPU", true));
+ monitorFan = Convert.ToBoolean(globalKey.GetValue("MonitorFan", false));
+ monitorRefreshRate = (string)globalKey.GetValue("MonitorRefreshRate", "low");
+ tempDisplayMode = (string)globalKey.GetValue("TempDisplayMode", "smoothed");
globalShowCPUTemp = Convert.ToBoolean(globalKey.GetValue("ShowCPUTemp", true));
globalShowCPUPower = Convert.ToBoolean(globalKey.GetValue("ShowCPUPower", true));
globalShowCPUFrequency = Convert.ToBoolean(globalKey.GetValue("ShowCPUFrequency", false));
@@ -965,24 +1177,12 @@ static void LoadMonitorMetricSettings(string presetKey) {
}
}
- if (IsBuiltInPreset(presetKey)) {
- showCPUTemp = globalShowCPUTemp;
- showCPUPower = globalShowCPUPower;
- showCPUFrequency = globalShowCPUFrequency;
- showGPUTemp = globalShowGPUTemp;
- showGPUPower = globalShowGPUPower;
- showGPUFrequency = globalShowGPUFrequency;
- return;
- }
-
- using (RegistryKey presetKeyHandle = Registry.CurrentUser.OpenSubKey($@"Software\OmenSuperHub\{presetKey}")) {
- showCPUTemp = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowCPUTemp", globalShowCPUTemp) ?? globalShowCPUTemp);
- showCPUPower = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowCPUPower", globalShowCPUPower) ?? globalShowCPUPower);
- showCPUFrequency = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowCPUFrequency", globalShowCPUFrequency) ?? globalShowCPUFrequency);
- showGPUTemp = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowGPUTemp", globalShowGPUTemp) ?? globalShowGPUTemp);
- showGPUPower = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowGPUPower", globalShowGPUPower) ?? globalShowGPUPower);
- showGPUFrequency = Convert.ToBoolean(presetKeyHandle?.GetValue("ShowGPUFrequency", globalShowGPUFrequency) ?? globalShowGPUFrequency);
- }
+ showCPUTemp = globalShowCPUTemp;
+ showCPUPower = globalShowCPUPower;
+ showCPUFrequency = globalShowCPUFrequency;
+ showGPUTemp = globalShowGPUTemp;
+ showGPUPower = globalShowGPUPower;
+ showGPUFrequency = globalShowGPUFrequency;
} catch (Exception ex) {
Logger.Error($"LoadMonitorMetricSettings({presetKey}): {ex.Message}");
}
@@ -1003,7 +1203,7 @@ static void UpdateMonitorMetricCheckedStates() {
///
static void ApplyPresetSettings(string presetKey) {
// 自定义预设特有字段:监控项、温度显示模式等
- if (presetKey == "Restore" || presetKey == "PresetCustom1" || presetKey == "PresetCustom2" || presetKey == "PresetCustom3") {
+ if (presetKey == "Restore" || presetKey == FanLockPresetKey || presetKey == "PresetCustom1" || presetKey == "PresetCustom2" || presetKey == "PresetCustom3") {
if (presetKey == "Restore") {
try {
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\OmenSuperHub")) {
@@ -1036,9 +1236,13 @@ static void ApplyPresetSettings(string presetKey) {
if (!wasMonitorRunning) {
cpuTempReady = gpuTempReady = tempReady = false;
StartHardwareMonitor();
+ } else if (!monitorGPU) {
+ gpuTempReady = false;
+ rawPowerGPU = rawFrequencyGPU = GPUPower = GPUFrequency = 0f;
+ StopHardwareMonitorAndWait(5000);
+ if (monitorCPU) StartHardwareMonitor();
} else {
if (!monitorCPU) { cpuTempReady = false; rawPowerCPU = 0f; CPUPower = 0f; }
- if (!monitorGPU) { gpuTempReady = false; rawPowerGPU = 0f; GPUPower = 0f; }
SetCpuMonitorState(monitorCPU);
SetGpuMonitorState(monitorGPU);
}
@@ -1077,22 +1281,24 @@ static void ApplyPresetSettings(string presetKey) {
UpdateCheckedState("fanTableGroup", Strings.FanCustomMode);
}
- // 风扇控制模式
- if (fanControl == "auto") {
- SetMaxFanSpeedOff();
- fanControlTimer.Change(0, 1000);
- UpdateCheckedState("fanControlGroup", Strings.FanAuto);
- } else if (fanControl.Contains("max")) {
- SetMaxFanSpeedOn();
- fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
- UpdateCheckedState("fanControlGroup", Strings.FanMax);
- } else if (fanControl.Contains(" RPM")) {
- SetMaxFanSpeedOff();
- fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
- int rpmValue = int.Parse(fanControl.Replace(" RPM", "").Trim());
- SetFanLevel(rpmValue / 100, rpmValue / 100, Is3FanNb);
- if (fanTrackBar != null) fanTrackBar.Value = rpmValue / 100;
- UpdateCheckedState("fanControlGroup", Strings.SetFanSpeedSlider);
+ // 风扇控制模式;锁定风扇预设在本方法末尾单独应用。
+ if (currentPreset != FanLockPresetKey) {
+ if (fanControl == "auto") {
+ SetMaxFanSpeedOff();
+ fanControlTimer.Change(0, 1000);
+ UpdateCheckedState("fanControlGroup", Strings.FanAuto);
+ } else if (fanControl.Contains("max")) {
+ SetMaxFanSpeedOn();
+ fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ UpdateCheckedState("fanControlGroup", Strings.FanMax);
+ } else if (fanControl.Contains(" RPM")) {
+ SetMaxFanSpeedOff();
+ fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
+ int rpmValue = int.Parse(fanControl.Replace(" RPM", "").Trim());
+ SetFanLevel(rpmValue / 100, rpmValue / 100, Is3FanNb);
+ if (fanTrackBar != null) fanTrackBar.Value = rpmValue / 100;
+ UpdateCheckedState("fanControlGroup", Strings.SetFanSpeedSlider);
+ }
}
// 风扇响应速度
@@ -1104,7 +1310,7 @@ static void ApplyPresetSettings(string presetKey) {
}
// CPU 功耗
- if (isCPUPowerControlSupported) {
+ if (isCPUPowerControlSupported && currentPreset != FanLockPresetKey) {
if (cpuPower == "null") {
UpdateCheckedState("cpuPowerGroup", Strings.NotSet);
} else if (cpuPower == "max") {
@@ -1214,6 +1420,9 @@ static void ApplyPresetSettings(string presetKey) {
}
}
});
+
+ if (currentPreset == FanLockPresetKey)
+ ApplyFanLockTargets();
}
///
@@ -1221,6 +1430,11 @@ static void ApplyPresetSettings(string presetKey) {
/// 保存到注册表,然后应用到硬件。
///
static void applyPresetLogic(string targetPreset) {
+ if (targetPreset == FanLockPresetKey) {
+ ActivateFanLockPreset();
+ return;
+ }
+ SetFanLockControlledMenusEnabled(true);
currentPreset = targetPreset;
if (targetPreset == "PresetExtreme" || targetPreset == "PresetGpuPriority" || targetPreset == "PresetLightUse") {
@@ -1289,6 +1503,7 @@ static void RestoreConfig() {
presetCustom1Name = (string)key.GetValue("PresetCustom1Name", Strings.PresetCustom1);
presetCustom2Name = (string)key.GetValue("PresetCustom2Name", Strings.PresetCustom2);
presetCustom3Name = (string)key.GetValue("PresetCustom3Name", Strings.PresetCustom3);
+ LoadFanLockConfig(key);
// 旧版升级兼容:不存在 CurrentPreset 键时迁移
if (key.GetValue("CurrentPreset") == null) {
@@ -1337,6 +1552,13 @@ static void RestoreConfig() {
} else {
LoadPresetFields(currentPreset);
}
+ if (currentPreset == FanLockPresetKey && fanLockCurrentPower < 10) {
+ int restoredPower;
+ if (TryGetCurrentCpuPowerSetting(out restoredPower))
+ fanLockCurrentPower = Math.Max(fanLockMinimumPower, Math.Min(fanLockMaximumPower, restoredPower));
+ else
+ fanLockCurrentPower = fanLockMinimumPower;
+ }
LoadMonitorMetricSettings(currentPreset);
var item = FindMenuItemByName(trayIcon.ContextMenuStrip.Items, currentPreset);
@@ -1438,6 +1660,8 @@ static void RestoreConfig() {
///
static void SavePresetToRegistry(string presetKey) {
if (presetKey == "PresetExtreme" || presetKey == "PresetGpuPriority" || presetKey == "PresetLightUse") return;
+ // 此子键在进入锁定模式前保存完整快照;模式运行期间不得被后续菜单修改覆盖。
+ if (presetKey == FanLockPresetKey && currentPreset == FanLockPresetKey) return;
try {
using (RegistryKey key = Registry.CurrentUser.CreateSubKey($@"Software\OmenSuperHub\{presetKey}")) {
if (key == null) return;
diff --git a/Program.GpuSleep.cs b/Program.GpuSleep.cs
new file mode 100644
index 0000000..f72c485
--- /dev/null
+++ b/Program.GpuSleep.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using static OmenSuperHub.GpuAppManager;
+
+namespace OmenSuperHub {
+ static partial class Program {
+ static bool gpuSleepOperationRunning;
+ static volatile bool gpuSleepGuardActive;
+
+ static bool ShouldBlockBackgroundGpuQueries {
+ get { return gpuSleepGuardActive || !monitorGPU; }
+ }
+
+ static async void StartGpuSleepAttempt() {
+ if (gpuSleepOperationRunning) return;
+ if (string.Equals(NvGraphicsMode.ToString(), "Discrete", StringComparison.OrdinalIgnoreCase)) {
+ MessageBox.Show(Strings.GpuSleepDiscreteModeBlocked, Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+ if (!GpuSleepManager.TryGetNvidiaSleepTarget(out GpuSleepManager.GpuDeviceInfo target, out string reason)) {
+ MessageBox.Show(reason, Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+ if (MessageBox.Show(Strings.GpuSleepConfirm, Strings.GpuSleepMenu, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
+
+ gpuSleepOperationRunning = true;
+ bool oldMonitorGpu = monitorGPU;
+ bool oldSleepGuard = gpuSleepGuardActive;
+ try {
+ await DisableGpuMonitoringForSleep();
+ bool slept = await RestartAndWaitForD3(target);
+ if (slept) { FinishGpuSleepSuccess(); return; }
+
+ List processes = await Task.Run(() => GpuSleepManager.GetNvidiaProcesses());
+ if (!processes.Any(p => p.CanConfigurePreference)) {
+ MessageBox.Show(Strings.GpuSleepFailedNoProcesses, Strings.GpuSleepMenu, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ RestoreGpuMonitoringAfterCancelledAttempt(oldMonitorGpu, oldSleepGuard);
+ return;
+ }
+
+ using (var repair = new GpuSleepRepairForm(processes)) {
+ if (repair.ShowDialog() != DialogResult.OK) {
+ RestoreGpuMonitoringAfterCancelledAttempt(oldMonitorGpu, oldSleepGuard);
+ return;
+ }
+ }
+
+ slept = await RestartAndWaitForD3(target);
+ if (slept) FinishGpuSleepSuccess();
+ else {
+ List remaining = await Task.Run(() => GpuSleepManager.GetNvidiaProcesses());
+ string details = remaining.Count == 0 ? "" : Environment.NewLine + Environment.NewLine + string.Join(Environment.NewLine, remaining.Select(p => $"{p.ProcessName} (PID {p.ProcessId}, {p.UsageType})"));
+ MessageBox.Show(Strings.GpuSleepStillFailed + details, Strings.GpuSleepMenu, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ RestoreGpuMonitoringAfterCancelledAttempt(oldMonitorGpu, oldSleepGuard);
+ }
+ } catch (Exception ex) {
+ Logger.Error("GPU sleep attempt failed: " + ex);
+ MessageBox.Show(ex.Message, Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ RestoreGpuMonitoringAfterCancelledAttempt(oldMonitorGpu, oldSleepGuard);
+ } finally {
+ gpuSleepOperationRunning = false;
+ }
+ }
+
+ static async Task DisableGpuMonitoringForSleep() {
+ gpuSleepGuardActive = true;
+ monitorGPU = false;
+ gpuTempReady = false;
+ rawGotGPU = false;
+ rawPowerGPU = rawFrequencyGPU = GPUPower = GPUFrequency = 0f;
+ bool stopped = await Task.Run(() => StopHardwareMonitorAndWait(5000));
+ if (!stopped) throw new InvalidOperationException(Strings.GpuSleepMonitorStopFailed);
+ if (monitorCPU) StartHardwareMonitor();
+ UpdateCheckedState("monitorGPUGroup", Strings.MonitorGpuOff);
+ }
+
+ static async Task RestartAndWaitForD3(GpuSleepManager.GpuDeviceInfo target) {
+ ProcessResult result = await Task.Run(() => RestartGpu(target.InstanceId));
+ if (result.ExitCode != 0) throw new InvalidOperationException(Strings.GpuSleepRestartFailed(result.Error));
+ await Task.Delay(3000);
+ for (int i = 0; i < 8; i++) {
+ GpuSleepManager.DevicePowerState state = await Task.Run(() => GpuSleepManager.GetDevicePowerState(target.InstanceId));
+ if (state == GpuSleepManager.DevicePowerState.D3) return true;
+ await Task.Delay(2000);
+ }
+ return false;
+ }
+
+ static void FinishGpuSleepSuccess() {
+ SaveConfig("MonitorGPU");
+ MessageBox.Show(Strings.GpuSleepSucceeded, Strings.GpuSleepMenu, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+
+ static void RestoreGpuMonitoringAfterCancelledAttempt(bool oldValue, bool oldGuardValue) {
+ gpuSleepGuardActive = oldGuardValue;
+ if (!oldValue) return;
+ monitorGPU = true;
+ gpuTempReady = false;
+ SetGpuMonitorState(true);
+ UpdateCheckedState("monitorGPUGroup", Strings.MonitorGpuOn);
+ }
+
+ static void RestoreLatestGpuPreferences() {
+ string id = GpuSleepManager.GetLatestTransactionId();
+ if (string.IsNullOrEmpty(id)) { MessageBox.Show(Strings.GpuPreferenceBackupMissing, Strings.Hint); return; }
+ GpuSleepManager.PreferenceTransaction transaction = GpuSleepManager.LoadTransaction(id);
+ GpuSleepManager.RestoreResult result = GpuSleepManager.RestorePreferences(id, false);
+ if (result.ConflictedPaths.Count > 0 && MessageBox.Show(Strings.GpuPreferenceConflict, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
+ result = GpuSleepManager.RestorePreferences(id, true);
+ if (!result.Success) { MessageBox.Show(result.Error ?? Strings.GpuPreferenceRestoreFailed, Strings.Error); return; }
+ MessageBox.Show(Strings.GpuPreferenceRestoredRestartRequired, Strings.Hint);
+ if (transaction != null && MessageBox.Show(Strings.GpuSleepRestartWarning, Strings.Warning, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) {
+ var apps = transaction.Entries.Select(e => new GpuSleepManager.GpuProcessInfo { ProcessName = e.ProcessName, ExecutablePath = e.ExecutablePath }).ToList();
+ GpuSleepManager.RestartApplications(apps, true);
+ }
+ }
+ }
+}
diff --git a/Program.Menu.cs b/Program.Menu.cs
index 86bd8c9..bf7a0f1 100644
--- a/Program.Menu.cs
+++ b/Program.Menu.cs
@@ -105,9 +105,13 @@ static void BuildTrayMenu(ContextMenuStrip menu) {
gpuPowerLimitsMenu = new ToolStripMenuItem($"{Strings.SysNvidiaPower}: --W / --W") { Enabled = false };
sysInfoMenu.DropDownItems.Add(gpuPowerLimitsMenu);
System.Threading.Tasks.Task.Run(() => {
- string gpuModel = GetGpuModelFromNvidiaSmi();
- var limits = GetGpuPowerLimits();
- string limitsText = limits[0] == -2f ? "--W / --W" : $"{limits[0]:F0}W / {limits[1]:F0}W";
+ string gpuModel = GetNvidiaGpuInfoList().FirstOrDefault().Name ?? Strings.GpuSleepTargetNotFound;
+ string limitsText = "--W / --W";
+ if (!ShouldBlockBackgroundGpuQueries) {
+ gpuModel = GetGpuModelFromNvidiaSmi();
+ var limits = GetGpuPowerLimits();
+ limitsText = limits[0] == -2f ? "--W / --W" : $"{limits[0]:F0}W / {limits[1]:F0}W";
+ }
Thread.Sleep(2000);
uiContext.Post(_ => {
gpuPowerLimitsMenu.Text = $"{Strings.SysNvidiaPower}: {limitsText}";
@@ -144,7 +148,7 @@ static void BuildTrayMenu(ContextMenuStrip menu) {
// 订阅 DropDownOpening 和 DropDownClosed 事件来控制是否更新信息
sysInfoMenu.DropDownOpening += (s, e) => {
- if (hasNVIDIAGpu) {
+ if (hasNVIDIAGpu && !ShouldBlockBackgroundGpuQueries) {
System.Threading.Tasks.Task.Run(() => {
var limits = GetGpuPowerLimits();
string limitsText = limits[0] == -2f ? "--W / --W" : $"{limits[0]:F0}W / {limits[1]:F0}W";
@@ -194,6 +198,23 @@ static void BuildTrayMenu(ContextMenuStrip menu) {
lightUseItem.Name = "PresetLightUse";
presetsMenu.DropDownItems.Add(lightUseItem);
presetsMenu.DropDownItems.Add(new ToolStripSeparator());
+
+ var fanLockItem = new ToolStripMenuItem(Strings.PresetFanLock) {
+ Name = FanLockPresetKey,
+ Tag = "presetsGroup",
+ Checked = currentPreset == FanLockPresetKey,
+ ToolTipText = Strings.PresetFanLockTooltip
+ };
+ fanLockItem.MouseUp += (s, e) => {
+ if (e.Button == MouseButtons.Left) {
+ if (ActivateFanLockPreset())
+ UpdateCheckedState("presetsGroup", null, fanLockItem);
+ } else if (e.Button == MouseButtons.Right) {
+ ShowFanLockEditor();
+ }
+ };
+ presetsMenu.DropDownItems.Add(fanLockItem);
+ presetsMenu.DropDownItems.Add(new ToolStripSeparator());
}
var custom1Item = CreateMenuItem(presetCustom1Name, "presetsGroup", (s, e) => applyPresetLogic("PresetCustom1"), currentPreset == "PresetCustom1");
@@ -344,7 +365,7 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
menu.Items.Add(fanConfigMenu);
- ToolStripMenuItem fanControlMenu = new ToolStripMenuItem(Strings.FanControl);
+ ToolStripMenuItem fanControlMenu = new ToolStripMenuItem(Strings.FanControl) { Name = "FanControlMenu" };
if (isFanCleanSupported || isFanLegacyCleanSupported) {
string menuText = Strings.CleanCreekMenuItem;
if (!isFanCleanSupported && isFanLegacyCleanSupported)
@@ -403,6 +424,7 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
fanValueLabel = new ToolStripMenuItem(string.Format(Strings.CurrentSliderValueTemp, $"{fanTrackBar.Value * 100} RPM")) { Enabled = false };
fanTrackBar.ValueChanged += (sender, e) => {
+ if (fanLockUpdatingControls) return;
fanControl = fanTrackBar.Value * 100 + " RPM";
fanValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{fanTrackBar.Value * 100} RPM");
fanControlTimer.Change(Timeout.Infinite, Timeout.Infinite);
@@ -514,24 +536,14 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
ToolStripMenuItem gpuAppsMenu = new ToolStripMenuItem(Strings.GpuAppsMenu);
gpuAppsMenu.DropDownOpening += (s, e) => {
gpuAppsMenu.DropDownItems.Clear();
- var apps = GetGpuApps();
- if (apps.Count == 0) {
- gpuAppsMenu.DropDownItems.Add(new ToolStripMenuItem(Strings.GpuAppsNone) { Enabled = false });
- } else {
- foreach (var app in apps) {
- var appItem = new ToolStripMenuItem($"{app.ProcessName} (PID: {app.ProcessId})");
- appItem.Click += (sender, args) => {
- if (MessageBox.Show(Application.OpenForms.OfType().FirstOrDefault(), Strings.GpuCloseConfirm(app.ProcessName), Strings.GpuCloseTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) {
- try {
- Process.GetProcessById(app.ProcessId).Kill();
- } catch (Exception ex) {
- MessageBox.Show(Application.OpenForms.OfType().FirstOrDefault(), Strings.GpuCloseError(ex.Message), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- }
- };
- gpuAppsMenu.DropDownItems.Add(appItem);
- }
+ if (ShouldBlockBackgroundGpuQueries) {
+ gpuAppsMenu.DropDownItems.Add(new ToolStripMenuItem(Strings.GpuAppsQueryMayWake) { Enabled = false });
+ var queryItem = new ToolStripMenuItem(Strings.GpuAppsQueryNow);
+ queryItem.Click += (sender, args) => PopulateGpuAppsMenu(gpuAppsMenu);
+ gpuAppsMenu.DropDownItems.Add(queryItem);
+ return;
}
+ PopulateGpuAppsMenu(gpuAppsMenu);
};
performanceControlMenu.DropDownItems.Add(gpuAppsMenu);
@@ -543,6 +555,16 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
}
};
performanceControlMenu.DropDownItems.Add(restartGpuMenu);
+
+ ToolStripMenuItem gpuSleepMenu = new ToolStripMenuItem(Strings.GpuSleepMenu);
+ gpuSleepMenu.ToolTipText = Strings.GpuSleepTooltip;
+ gpuSleepMenu.Click += (s, e) => StartGpuSleepAttempt();
+ performanceControlMenu.DropDownItems.Add(gpuSleepMenu);
+
+ ToolStripMenuItem restoreGpuPreferencesMenu = new ToolStripMenuItem(Strings.GpuPreferenceRestore);
+ restoreGpuPreferencesMenu.Enabled = !string.IsNullOrEmpty(GpuSleepManager.GetLatestTransactionId());
+ restoreGpuPreferencesMenu.Click += (s, e) => RestoreLatestGpuPreferences();
+ performanceControlMenu.DropDownItems.Add(restoreGpuPreferencesMenu);
}
performanceControlMenu.DropDownItems.Add(new ToolStripSeparator()); // Separator between groups
//ToolStripMenuItem pl4Menu = new ToolStripMenuItem("PL4");
@@ -609,7 +631,7 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
}
if (isCPUPowerControlSupported) {
- ToolStripMenuItem cpuPowerMenu = new ToolStripMenuItem(Strings.CpuPowerMenu);
+ ToolStripMenuItem cpuPowerMenu = new ToolStripMenuItem(Strings.CpuPowerMenu) { Name = "CpuPowerMenu" };
cpuPowerMenu.DropDownItems.Add(new ToolStripMenuItem(Strings.PerfCpuPowerTip) { Enabled = false });
cpuPowerMenu.DropDownItems.Add(new ToolStripSeparator());
@@ -638,6 +660,7 @@ void attachRename(ToolStripMenuItem item, string presetKey) {
// 滑块值改变时更新标签并应用设置
cpuPowerTrackBar.ValueChanged += (sender, e) => {
+ if (fanLockUpdatingControls) return;
int val = cpuPowerTrackBar.Value;
cpuPowerValueLabel.Text = string.Format(Strings.CurrentSliderValueTemp, $"{val} W");
cpuPower = cpuPowerTrackBar.Value + " W";
@@ -1092,17 +1115,13 @@ ToolStripMenuItem CreateMonitorMetricItem(string text, string group, Func
ToolStripMenuItem monitorGPUMenu = new ToolStripMenuItem(Strings.MonitorGpuLabel);
monitorGPUMenu.DropDownItems.Add(CreateMenuItem(Strings.MonitorGpuOn, "monitorGPUGroup", (s, e) => {
bool wasAllOff = !monitorCPU && !monitorGPU;
+ gpuSleepGuardActive = false;
monitorGPU = true;
gpuTempReady = false; // 等待获取到温度后再参与风扇控制
rawPowerGPU = 0f; // 清除可能残留的脏功率值
rawFrequencyGPU = 0f;
GPUPower = 0f;
GPUFrequency = 0f;
- if (hasStopAuto)
- autoStopMonitorGPU = false;
- //重置自动开启标志
- hasStartAuto = false;
- autoStartMonitorGPU = true;
if (wasAllOff) {
// 从全关状态重启监控进程
tempReady = false;
@@ -1117,7 +1136,7 @@ ToolStripMenuItem CreateMonitorMetricItem(string text, string group, Func
}
SaveConfig("MonitorGPU");
}, true));
- monitorGPUMenu.DropDownItems.Add(CreateMenuItem(Strings.MonitorGpuOff, "monitorGPUGroup", (s, e) => {
+ monitorGPUMenu.DropDownItems.Add(CreateMenuItem(Strings.MonitorGpuOff, "monitorGPUGroup", async (s, e) => {
// 自动转速模式下禁止彻底关闭监控
if (!monitorCPU && fanControl == "auto") {
MessageBox.Show(Application.OpenForms.OfType().FirstOrDefault(), Strings.MonitorAutoFanWarning, Strings.Hint, MessageBoxButtons.OK, MessageBoxIcon.Warning);
@@ -1130,17 +1149,17 @@ ToolStripMenuItem CreateMonitorMetricItem(string text, string group, Func
rawPowerGPU = 0f; // 关闭时清零,避免重新开启时读到旧值
rawFrequencyGPU = 0f;
GPUPower = 0f;
- GPUFrequency = 0f;
- if (hasStartAuto)
- autoStartMonitorGPU = false;
- //重置自动关闭标志
- hasStopAuto = false;
- autoStopMonitorGPU = true;
- SetGpuMonitorState(false);
- // 若CPU和GPU均已关闭,停止监控进程
- if (!monitorCPU && !monitorGPU) {
- StopHardwareMonitor();
+GPUFrequency = 0f;
+ bool stopped = await Task.Run(() => StopHardwareMonitorAndWait(5000));
+ if (!stopped) {
+ monitorGPU = true;
+ SetGpuMonitorState(true);
+ UpdateCheckedState("monitorGPUGroup", Strings.MonitorGpuOn);
+ MessageBox.Show(Strings.MonitorGpuStopFailed, Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ SaveConfig("MonitorGPU");
+ return;
}
+ if (monitorCPU) StartHardwareMonitor();
SaveConfig("MonitorGPU");
}, false));
monitorGPUMenu.DropDownItems.Add(new ToolStripSeparator());
@@ -2166,6 +2185,85 @@ ex is UnauthorizedAccessException ||
}
}
+ static void ShowFanLockEditor() {
+ int maximumRpm = platformMaxFanSpeed.HasValue && platformMaxFanSpeed.Value > 0
+ ? (int)(platformMaxFanSpeed.Value * 1.1)
+ : 6400;
+ maximumRpm = Math.Max(100, maximumRpm / 100 * 100);
+ int maximumTemperature = Math.Max(40, (maxCPUTemp ?? 97) - 3);
+
+ using (Form form = new Form {
+ Width = 430,
+ Height = 310,
+ FormBorderStyle = FormBorderStyle.FixedDialog,
+ MaximizeBox = false,
+ MinimizeBox = false,
+ StartPosition = FormStartPosition.CenterScreen,
+ Text = Strings.FanLockEditorTitle
+ }) {
+ Label minimumPowerLabel = new Label { Left = 15, Top = 22, Width = 230, Text = Strings.FanLockMinimumPower };
+ NumericUpDown minimumPowerInput = new NumericUpDown {
+ Left = 270, Top = 18, Width = 120, Minimum = 10, Maximum = 254,
+ Value = Math.Max(10, Math.Min(254, fanLockMinimumPower))
+ };
+
+ Label maximumPowerLabel = new Label { Left = 15, Top = 66, Width = 230, Text = Strings.FanLockMaximumPower };
+ NumericUpDown maximumPowerInput = new NumericUpDown {
+ Left = 270, Top = 62, Width = 120, Minimum = 10, Maximum = 254,
+ Value = Math.Max(10, Math.Min(254, fanLockMaximumPower))
+ };
+
+ minimumPowerInput.ValueChanged += (s, e) => {
+ if (minimumPowerInput.Value > maximumPowerInput.Value)
+ maximumPowerInput.Value = minimumPowerInput.Value;
+ };
+ maximumPowerInput.ValueChanged += (s, e) => {
+ if (maximumPowerInput.Value < minimumPowerInput.Value)
+ minimumPowerInput.Value = maximumPowerInput.Value;
+ };
+
+ Label rpmLabel = new Label { Left = 15, Top = 110, Width = 230, Text = Strings.FanLockTargetRpm };
+ NumericUpDown rpmInput = new NumericUpDown {
+ Left = 270, Top = 106, Width = 120, Minimum = 0, Maximum = maximumRpm,
+ Increment = 100,
+ Value = Math.Max(0, Math.Min(maximumRpm, fanLockTargetRpm))
+ };
+
+ Label temperatureLabel = new Label { Left = 15, Top = 154, Width = 230, Text = Strings.FanLockTargetTemperature };
+ NumericUpDown temperatureInput = new NumericUpDown {
+ Left = 270, Top = 150, Width = 120, Minimum = 40, Maximum = maximumTemperature,
+ Value = Math.Max(40, Math.Min(maximumTemperature, fanLockTargetTemperature))
+ };
+
+ Button okButton = new Button {
+ Text = Strings.OK, Left = 110, Top = 205, Width = 95,
+ DialogResult = DialogResult.OK
+ };
+ Button cancelButton = new Button {
+ Text = Strings.Cancel, Left = 220, Top = 205, Width = 95,
+ DialogResult = DialogResult.Cancel
+ };
+
+ form.Controls.AddRange(new Control[] {
+ minimumPowerLabel, minimumPowerInput, maximumPowerLabel, maximumPowerInput,
+ rpmLabel, rpmInput, temperatureLabel, temperatureInput, okButton, cancelButton
+ });
+ form.AcceptButton = okButton;
+ form.CancelButton = cancelButton;
+
+ if (form.ShowDialog() != DialogResult.OK) return;
+
+ fanLockMinimumPower = (int)minimumPowerInput.Value;
+ fanLockMaximumPower = (int)maximumPowerInput.Value;
+ fanLockTargetRpm = (int)rpmInput.Value / 100 * 100;
+ fanLockTargetTemperature = (int)temperatureInput.Value;
+ SaveFanLockConfig();
+
+ if (currentPreset == FanLockPresetKey)
+ ApplyFanLockTargets();
+ }
+ }
+
static bool ApplyCustomFanConfig() {
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
@@ -2197,6 +2295,27 @@ ex is UnauthorizedAccessException ||
}
}
+ static void PopulateGpuAppsMenu(ToolStripMenuItem gpuAppsMenu) {
+ gpuAppsMenu.DropDownItems.Clear();
+ var apps = GpuSleepManager.GetNvidiaProcesses();
+ if (apps.Count == 0) {
+ gpuAppsMenu.DropDownItems.Add(new ToolStripMenuItem(Strings.GpuAppsNone) { Enabled = false });
+ return;
+ }
+ foreach (var app in apps) {
+ var appItem = new ToolStripMenuItem($"{app.ProcessName} (PID: {app.ProcessId})");
+ appItem.Click += (sender, args) => {
+ if (MessageBox.Show(Application.OpenForms.OfType().FirstOrDefault(), Strings.GpuCloseConfirm(app.ProcessName), Strings.GpuCloseTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) return;
+ try {
+ Process.GetProcessById(app.ProcessId).Kill();
+ } catch (Exception ex) {
+ MessageBox.Show(Application.OpenForms.OfType().FirstOrDefault(), Strings.GpuCloseError(ex.Message), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ };
+ gpuAppsMenu.DropDownItems.Add(appItem);
+ }
+ }
+
static ToolStripMenuItem CreateMenuItem(string text, string group, EventHandler action, bool isChecked, string toolTip = null) {
var item = new ToolStripMenuItem(text) {
Tag = group,
diff --git a/Program.OmenKey.cs b/Program.OmenKey.cs
index 00112e7..6e7bee7 100644
--- a/Program.OmenKey.cs
+++ b/Program.OmenKey.cs
@@ -253,6 +253,7 @@ static bool IsPresetAvailable(string presetKey) {
case "PresetExtreme":
case "PresetGpuPriority":
case "PresetLightUse":
+ case FanLockPresetKey:
return isCPUPowerControlSupported;
case "PresetCustom1":
case "PresetCustom2":
@@ -268,7 +269,8 @@ static List GetAvailablePresetKeys() {
}
static string GetDefaultOmenKeyPresetCandidates() {
- return string.Join(";", GetAvailablePresetKeys());
+ // 锁定风扇模式会接管风扇与CPU功耗,不默认加入快捷键轮换,用户仍可手动勾选。
+ return string.Join(";", GetAvailablePresetKeys().Where(key => key != FanLockPresetKey));
}
static List GetOmenKeyPresetCandidateKeys() {
diff --git a/Program.cs b/Program.cs
index 469f29c..06b12d5 100644
--- a/Program.cs
+++ b/Program.cs
@@ -100,7 +100,8 @@ struct MSLLHOOKSTRUCT {
static int textSize = 40;
static int countRestore = 0, gpuClock = 0, gpuCoreOverclock = -1, gpuMemoryOverclock = -1, maxFrameRate = -1, graphicsBoostClock = 0;
static int alreadyRead = 0, alreadyReadCode = 1000;
- static readonly string[] PresetOrder = { "PresetExtreme", "PresetGpuPriority", "PresetLightUse", "PresetCustom1", "PresetCustom2", "PresetCustom3" };
+ const string FanLockPresetKey = "PresetFanLock";
+ static readonly string[] PresetOrder = { "PresetExtreme", "PresetGpuPriority", "PresetLightUse", FanLockPresetKey, "PresetCustom1", "PresetCustom2", "PresetCustom3" };
static string currentPreset = "PresetCustom1", presetCustom1Name = Strings.PresetCustom1, presetCustom2Name = Strings.PresetCustom2, presetCustom3Name = Strings.PresetCustom3;
static string fanTable = "cool", fanControl = "auto", tempSensitivity = "high", tppPower = "null", iccMax = "null", acLoadline = "null", cpuPower = "null", tgpPower = "on", ppabPower = "on", dState = "normal", autoStart = "off", customIcon = "original", floatingBar = "off", floatingBarLoc = "left", floatingBarScreen = "", omenKey = OmenKeyActions.Default, omenKeyAppPath = "", omenKeyAppName = "", omenKeyShortcut = "", omenKeyPresetCandidates = "", dataLocalize = "off", appLanguage = "zh-CN", autoFanProtect = "on";
static volatile bool monitorFan = false;
@@ -117,11 +118,16 @@ struct MSLLHOOKSTRUCT {
static int? maxGPUTemp = null;
static float CPUTemp = 50, GPUTemp = 40, rawTempCPU = 50f, rawTempGPU = 40f;
static float CPUPower = 0, GPUPower = 0, CPUFrequency = 0f, GPUFrequency = 0f, rawPowerCPU = 0f, rawPowerGPU = 0f, rawFrequencyCPU = 0f, rawFrequencyGPU = 0f;
+ static int fanLockMinimumPower = 20, fanLockMaximumPower = 120, fanLockTargetRpm = 3000, fanLockTargetTemperature = 80;
+ static int fanLockCurrentPower = -1;
+ static string fanLockReturnPreset = "PresetCustom1";
+ static bool fanLockUpdatingControls = false;
static bool rawGotGPU = false;
static volatile bool tempReady = false; // 子进程首次输出有效温度后置 true
static volatile bool cpuTempReady = false; // CPU 温度已初始化给平滑值,允许参与风扇控制
static volatile bool gpuTempReady = false; // GPU 温度已初始化给平滑值,允许参与风扇控制
- static volatile bool hwMonitorStopping = false; // 主动停止时置 true,阻止 Exited 自动重启
+ static readonly object hwMonitorLifecycleLock = new object();
+ static readonly HashSet hwMonitorStoppingProcesses = new HashSet(); // 精确标记主动停止的实例
static Process hwMonitorProcess;
static StreamWriter hwMonitorIn;
@@ -633,9 +639,10 @@ static void RunHardwareMonitor() {
}
static void StartHardwareMonitor() {
- if (hwMonitorProcess != null && !hwMonitorProcess.HasExited) return;
-
- hwMonitorProcess = new Process {
+ Process process;
+ lock (hwMonitorLifecycleLock) {
+ if (hwMonitorProcess != null && !hwMonitorProcess.HasExited) return;
+ process = new Process {
StartInfo = new ProcessStartInfo {
FileName = Application.ExecutablePath,
Arguments = "--hwmonitor",
@@ -646,9 +653,11 @@ static void StartHardwareMonitor() {
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
- };
+ };
+ hwMonitorProcess = process;
+ }
- hwMonitorProcess.OutputDataReceived += (s, e) => {
+ process.OutputDataReceived += (s, e) => {
if (string.IsNullOrEmpty(e.Data)) return;
//Debug.WriteLine("[HWMonitor OUT] " + e.Data); // 将子进程输出重定向到VS的输出窗口
if (e.Data.StartsWith("CRASH:")) return;
@@ -699,35 +708,52 @@ static void StartHardwareMonitor() {
}
};
- hwMonitorProcess.ErrorDataReceived += (s, e) => {
+ process.ErrorDataReceived += (s, e) => {
if (string.IsNullOrEmpty(e.Data)) return;
Logger.Error("HardwareMonitor [HWMonitor ERR] " + e.Data);
};
- hwMonitorProcess.EnableRaisingEvents = true;
- hwMonitorProcess.Exited += (s, e) => {
- if (hwMonitorStopping) {
- hwMonitorStopping = false;
- return;
+ process.EnableRaisingEvents = true;
+ process.Exited += (s, e) => {
+ bool intentional;
+ lock (hwMonitorLifecycleLock) {
+ intentional = hwMonitorStoppingProcesses.Remove(process);
+ if (ReferenceEquals(hwMonitorProcess, process)) {
+ hwMonitorProcess = null;
+ hwMonitorIn = null;
+ }
}
+ if (intentional) return;
//Logger.Info("StartHardwareMonitor [HWMonitor] 进程退出,准备重启...");
System.Threading.Tasks.Task.Delay(3000).ContinueWith(_ => {
+ if (!monitorCPU && !monitorGPU) return;
try { StartHardwareMonitor(); } catch { }
});
};
try {
- hwMonitorProcess.Start();
- hwMonitorIn = hwMonitorProcess.StandardInput;
- hwMonitorProcess.BeginOutputReadLine();
- hwMonitorProcess.BeginErrorReadLine(); // 必须读取错误流避免死锁
+ process.Start();
+ lock (hwMonitorLifecycleLock) {
+ if (ReferenceEquals(hwMonitorProcess, process)) hwMonitorIn = process.StandardInput;
+ }
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine(); // 必须读取错误流避免死锁
SetGpuMonitorState(monitorGPU);
SetCpuMonitorState(monitorCPU);
SetMonitorInterval(monitorRefreshRate == "high" ? 250 : 1000);
- } catch (Exception) { }
+ } catch (Exception) {
+ lock (hwMonitorLifecycleLock) {
+ if (ReferenceEquals(hwMonitorProcess, process)) {
+ hwMonitorProcess = null;
+ hwMonitorIn = null;
+ }
+ }
+ try { process.Dispose(); } catch { }
+ }
}
static void SetGpuMonitorState(bool enable) {
+ if (enable && gpuSleepGuardActive) return;
if (hwMonitorIn != null && hwMonitorProcess != null && !hwMonitorProcess.HasExited) {
try { hwMonitorIn.WriteLine(enable ? "GPU:ON" : "GPU:OFF"); } catch { }
}
@@ -746,10 +772,54 @@ static void SetMonitorInterval(int ms) {
}
static void StopHardwareMonitor() {
- if (hwMonitorProcess != null && !hwMonitorProcess.HasExited) {
- hwMonitorStopping = true;
- try { hwMonitorProcess.Kill(); } catch { hwMonitorStopping = false; }
+ StopHardwareMonitorAndWait(5000);
+ }
+
+ static bool StopHardwareMonitorAndWait(int timeoutMs) {
+ Process process;
+ lock (hwMonitorLifecycleLock) {
+ process = hwMonitorProcess;
+ if (process == null) {
+ hwMonitorIn = null;
+ return true;
+ }
+ try {
+ if (process.HasExited) {
+ hwMonitorProcess = null;
+ hwMonitorIn = null;
+ return true;
+ }
+ } catch {
+ hwMonitorProcess = null;
+ hwMonitorIn = null;
+ return true;
+ }
+ hwMonitorStoppingProcesses.Add(process);
+ hwMonitorIn = null;
}
+
+ bool exited = false;
+ try {
+ process.Kill();
+ exited = process.WaitForExit(timeoutMs);
+ if (!exited) return false;
+ } catch (InvalidOperationException) {
+ exited = true;
+ } catch {
+ return false;
+ } finally {
+ lock (hwMonitorLifecycleLock) {
+ if (exited) {
+ if (ReferenceEquals(hwMonitorProcess, process)) hwMonitorProcess = null;
+ } else {
+ hwMonitorStoppingProcesses.Remove(process);
+ if (ReferenceEquals(hwMonitorProcess, process)) {
+ try { hwMonitorIn = process.StandardInput; } catch { hwMonitorIn = null; }
+ }
+ }
+ }
+ }
+ return true;
}
static int flagStart = 0;
@@ -1147,8 +1217,6 @@ static void SyncDataToTxt() {
// 硬件传感器查询
private static int _isQuerying = 0; // 防重入标志,支持 Interlocked 原子操作
static int countQuery = 0;
- static bool autoStartMonitorGPU = true, autoStopMonitorGPU = true;//是否自动根据情况开/关GPU温度监测以节约能源
- static bool hasStartAuto = false, hasStopAuto = false;//是否已经自动开/关过GPU温度监测,在手动开/关时重置
// 用于风扇查表的平滑温度(受高中低档影响)
static float smoothedCPUTemp = 50f;
static float smoothedGPUTemp = 40f;
@@ -1186,6 +1254,8 @@ static void QueryHardware() {
smoothedGPUTemp = rawTempGPU * respondSpeed + smoothedGPUTemp * (1.0f - respondSpeed);
}
+ bool fanLockPowerHandledByProtection = false;
+
// 根据显示方式决定展示原始值或平滑值
if (monitorCPU && cpuTempReady)
CPUTemp = (tempDisplayMode == "raw") ? tempCPU : smoothedCPUTemp;
@@ -1203,26 +1273,47 @@ static void QueryHardware() {
}
if (fanSpeedCondition) {
- // 先切换为降温模式(cool配置)
- fanTable = "cool";
- LoadFanConfig("cool.txt");
- UpdateCheckedState("fanTableGroup", Strings.FanCoolMode);
- SaveConfig("FanTable");
-
- // 再切换为自动风扇控制
- fanControl = "auto";
- SetMaxFanSpeedOff();
- fanControlTimer.Change(0, 1000);
- UpdateCheckedState("fanControlGroup", Strings.FanAuto);
- SaveConfig("FanControl");
-
- trayIcon.BalloonTipTitle = Strings.HighTempBalloonTitle;
- trayIcon.BalloonTipText = Strings.HighTempBalloonText(currentMaxCPUTemp, smoothedCPUTemp);
- trayIcon.BalloonTipIcon = ToolTipIcon.Warning;
- trayIcon.ShowBalloonTip(3000);
+ bool exitedFanLockForSafety = false;
+ bool shouldSwitchFan = true;
+ if (currentPreset == FanLockPresetKey) {
+ shouldSwitchFan = PrepareFanLockOverheatProtection();
+ if (shouldSwitchFan) {
+ LeaveFanLockPresetForSafety();
+ exitedFanLockForSafety = true;
+ } else {
+ fanLockPowerHandledByProtection = true;
+ trayIcon.BalloonTipTitle = Strings.HighTempBalloonTitle;
+ trayIcon.BalloonTipText = Strings.FanLockPowerReducedBalloonText(currentMaxCPUTemp, smoothedCPUTemp, fanLockCurrentPower, fanLockMinimumPower);
+ trayIcon.BalloonTipIcon = ToolTipIcon.Warning;
+ trayIcon.ShowBalloonTip(3000);
+ }
+ }
+
+ if (shouldSwitchFan) {
+ // 先切换为降温模式(cool配置)
+ fanTable = "cool";
+ LoadFanConfig("cool.txt");
+ UpdateCheckedState("fanTableGroup", Strings.FanCoolMode);
+ if (!exitedFanLockForSafety) SaveConfig("FanTable");
+
+ // 再切换为自动风扇控制
+ fanControl = "auto";
+ SetMaxFanSpeedOff();
+ fanControlTimer.Change(0, 1000);
+ UpdateCheckedState("fanControlGroup", Strings.FanAuto);
+ if (!exitedFanLockForSafety) SaveConfig("FanControl");
+
+ trayIcon.BalloonTipTitle = Strings.HighTempBalloonTitle;
+ trayIcon.BalloonTipText = Strings.HighTempBalloonText(currentMaxCPUTemp, smoothedCPUTemp);
+ trayIcon.BalloonTipIcon = ToolTipIcon.Warning;
+ trayIcon.ShowBalloonTip(3000);
+ }
}
}
+ if (!fanLockPowerHandledByProtection)
+ AdjustFanLockPower();
+
//通过countQuery延时来确保温度正常读取
if (countQuery <= 5 && monitorGPU)
countQuery++;
@@ -1428,6 +1519,7 @@ static string GetPresetDisplayName(string presetKey) {
case "PresetExtreme": return Strings.PresetExtreme;
case "PresetGpuPriority": return Strings.PresetGpuPriority;
case "PresetLightUse": return Strings.PresetLightUse;
+ case FanLockPresetKey: return Strings.PresetFanLock;
case "PresetCustom1": return presetCustom1Name;
case "PresetCustom2": return presetCustom2Name;
case "PresetCustom3": return presetCustom3Name;
diff --git a/Strings.cs b/Strings.cs
index cb2c8b1..0663fb9 100644
--- a/Strings.cs
+++ b/Strings.cs
@@ -59,6 +59,20 @@ public static class Strings {
public static string PresetExtreme => T("极致性能", "極致性能", "Extreme Performance");
public static string PresetGpuPriority => T("GPU优先", "GPU優先", "GPU Priority");
public static string PresetLightUse => T("轻度使用", "輕度使用", "Light Use");
+ public static string PresetFanLock => T("锁定风扇转速模式", "鎖定風扇轉速模式", "Locked Fan Speed Mode");
+ public static string PresetFanLockTooltip => T(
+ "左键进入锁定风扇转速模式;右键修改目标转速、目标温度和CPU功耗范围。",
+ "左鍵進入鎖定風扇轉速模式;右鍵修改目標轉速、目標溫度和CPU功耗範圍。",
+ "Left-click to activate locked fan speed mode; right-click to edit its fan, temperature, and CPU power range targets.");
+ public static string FanLockEditorTitle => T("锁定风扇转速模式", "鎖定風扇轉速模式", "Locked Fan Speed Mode");
+ public static string FanLockMinimumPower => T("CPU功耗墙下限 (W)", "CPU功耗牆下限 (W)", "Minimum CPU power (W)");
+ public static string FanLockMaximumPower => T("CPU最大功率目标 (W)", "CPU最大功率目標 (W)", "Maximum CPU power target (W)");
+ public static string FanLockTargetRpm => T("目标风扇转速 (RPM)", "目標風扇轉速 (RPM)", "Target fan speed (RPM)");
+ public static string FanLockTargetTemperature => T("目标CPU温度 (℃)", "目標CPU溫度 (℃)", "Target CPU temperature (°C)");
+ public static string FanLockNeedsCpuPower => T(
+ "当前预设没有设置CPU功率,无法确定锁定模式的PL1起点。请先设置一个CPU功率数值。",
+ "目前預設沒有設定CPU功率,無法確定鎖定模式的PL1起點。請先設定一個CPU功率數值。",
+ "The current preset has no CPU power setting, so the starting PL1 cannot be determined. Set a CPU power value first.");
public static string PresetCustom1 => T("自定义预设1", "自定義預設1", "Custom 1");
public static string PresetCustom2 => T("自定义预设2", "自定義預設2", "Custom 2");
public static string PresetCustom3 => T("自定义预设3", "自定義預設3", "Custom 3");
@@ -209,6 +223,9 @@ public static string GfxSwitchedTo(string mode) => T(
// ─────────────────────────────────────────────────────────────────────────
public static string GpuAppsMenu => T("占用GPU的程序", "佔用GPU的程式", "GPU Processes");
public static string GpuAppsNone => T("无", "無", "None");
+ public static string GpuAppsQueryMayWake => T("GPU 查询已暂停(可能唤醒独显)",
+ "GPU 查詢已暫停(可能喚醒獨顯)", "GPU query paused (may wake dGPU)");
+ public static string GpuAppsQueryNow => T("立即查询一次", "立即查詢一次", "Query once now");
public static string GpuRestartMenu => T("重启显卡", "重啟顯示卡", "Restart GPU");
public static string GpuRestartTooltip => T("通过重启独立 GPU 减少不必要的占用 GPU 情况。",
"透過重啟獨立 GPU 減少不必要的 GPU 佔用情況。",
@@ -288,9 +305,13 @@ public static string DbUnlockFailed(float w) => T(
// 高温警告(气泡)
public static string HighTempBalloonTitle => T("温度过高警告", "溫度過高警告", "High Temperature Warning");
public static string HighTempBalloonText(int limit, float temp) => T(
- $"检测到CPU温度高于{limit - 5}℃ ({temp:F1}℃),且风扇处于固定转速状态,OSH已自动切换为降温模式并将风扇控制切换为自动模式。",
- $"偵測到CPU溫度高於{limit - 5}℃ ({temp:F1}℃),且風扇處於固定轉速狀態,OSH已自動切換至降溫模式並將風扇控制改為自動。",
- $"CPU temperature exceeded {limit - 5}°C ({temp:F1}°C) with a fixed fan speed. OSH has switched to Cool mode and Auto fan control.");
+ $"检测到CPU温度高于{limit - 2}℃ ({temp:F1}℃),且风扇处于固定转速状态,OSH已自动切换为降温模式并将风扇控制切换为自动模式。",
+ $"偵測到CPU溫度高於{limit - 2}℃ ({temp:F1}℃),且風扇處於固定轉速狀態,OSH已自動切換至降溫模式並將風扇控制改為自動。",
+ $"CPU temperature exceeded {limit - 2}°C ({temp:F1}°C) with a fixed fan speed. OSH has switched to Cool mode and Auto fan control.");
+ public static string FanLockPowerReducedBalloonText(int limit, float temp, int power, int minimum) => T(
+ $"检测到CPU温度高于{limit - 2}℃ ({temp:F1}℃)。锁定风扇转速模式已优先将CPU功耗墙降低至{power} W,下限为{minimum} W;到达下限后若仍过热才会切换风扇控制。",
+ $"偵測到CPU溫度高於{limit - 2}℃ ({temp:F1}℃)。鎖定風扇轉速模式已優先將CPU功耗牆降低至{power} W,下限為{minimum} W;到達下限後若仍過熱才會切換風扇控制。",
+ $"CPU temperature exceeded {limit - 2}°C ({temp:F1}°C). Locked fan speed mode reduced CPU power to {power} W first (minimum {minimum} W); fan control will change only if overheating continues at the minimum.");
// ─────────────────────────────────────────────────────────────────────────
// 性能控制 — 提示文本
@@ -455,6 +476,7 @@ public static string SysNvidiaPowerLimitText(string limitsText) => T(
public static string MonitorCpuOff => T("关闭CPU监控", "關閉CPU監控", "Disable CPU Monitor");
public static string MonitorGpuOn => T("开启GPU监控", "開啟GPU監控", "Enable GPU Monitor");
public static string MonitorGpuOff => T("关闭GPU监控", "關閉GPU監控", "Disable GPU Monitor");
+ public static string MonitorGpuStopFailed => T("无法完全停止旧的硬件监控进程,GPU监控仍保持开启。", "無法完全停止舊的硬體監控程序,GPU 監控仍保持開啟。", "The previous hardware monitor could not be stopped completely. GPU monitoring remains enabled.");
public static string MonitorFanOn => T("开启风扇监控", "開啟風扇監控", "Enable Fan Monitor");
public static string MonitorFanOff => T("关闭风扇监控", "關閉風扇監控", "Disable Fan Monitor");
public static string MonitorRefresh => T("刷新频率", "更新頻率", "Refresh Rate");
@@ -609,7 +631,7 @@ public static string OmenKeyShortcutSendFailed(int error) => T(
public static string MonitorGpuPowerLabel => T("GPU功率", "GPU功率", "GPU Power");
public static string MonitorGpuFrequencyLabel => T("GPU频率", "GPU頻率", "GPU Frequency");
public static string MonitorFanLabel => T("风扇", "風扇", "Fan");
- public static string GpuPoweredOff => T("节能", "節能", "PoweredOff");
+ public static string GpuPoweredOff => T("遥测不可用", "遙測無法使用", "Telemetry unavailable");
public static string MonitorPrepareLabel => T("数据获取中...", "數據獲取中...", "Retrieving data...");
// ─────────────────────────────────────────────────────────────────────────
@@ -675,5 +697,46 @@ private static string T(string zh, string tw, string en) {
public static string DeviceNotFound => T("未找到描述包含 NVIDIA 的显示适配器!", "未找到描述包含 NVIDIA 的顯示卡!", "Display adapter containing 'NVIDIA' not found!");
public static string RestartGPUSuccess => T("重启显卡成功!", "重啟顯示卡成功!", "Restart GPU successful!");
public static string RestartGPUFailed => T("重启显卡失败!", "重啟顯示卡失敗!", "Failed to restart GPU!");
+ public static string Yes => T("是", "是", "Yes");
+ public static string No => T("否", "否", "No");
+ public static string GpuSleepMenu => T("强制尝试休眠独显", "強制嘗試休眠獨顯", "Force dGPU Sleep Attempt");
+ public static string GpuSleepTooltip => T("清理独显占用、重启设备并尝试进入D3,失败时提供占用诊断和快捷修复。", "清理獨顯佔用、重新啟動裝置並嘗試進入 D3,失敗時提供診斷與快速修復。", "Clear dGPU clients, restart the device, and attempt D3 sleep with diagnostics and repair actions.");
+ public static string GpuSleepNeedsSecondGpu => T("未检测到可用的第二块物理显卡,禁止执行独显休眠。", "未偵測到可用的第二張實體顯示卡,禁止執行獨顯休眠。", "No usable second physical GPU was detected.");
+ public static string GpuSleepTargetNotFound => T("未找到可安全操作的NVIDIA独显。", "找不到可安全操作的 NVIDIA 獨顯。", "No eligible NVIDIA dGPU was found.");
+ public static string GpuSleepNoActiveFallbackGpu => T("第二块显卡没有活动显示模式,无法确认它能接管桌面。", "第二張顯示卡沒有作用中的顯示模式,無法確認可接管桌面。", "The fallback GPU has no active display mode.");
+ public static string GpuSleepTargetHasActiveDisplay => T("独显当前仍有活动显示模式,可能正在驱动内屏或外接显示器,已阻止休眠操作。", "獨顯目前仍有作用中的顯示模式,可能正在驅動內建或外接顯示器,已阻止休眠操作。", "The dGPU still has an active display mode and may be driving a display.");
+ public static string GpuSleepDiscreteModeBlocked => T("当前为独显直连模式,不能尝试休眠独显。", "目前為獨顯直連模式,不能嘗試休眠獨顯。", "dGPU sleep is unavailable in discrete graphics mode.");
+ public static string GpuSleepConfirm => T("该操作会停止GPU监控并重启独显,应用可能崩溃。是否继续?", "此操作會停止 GPU 監控並重新啟動獨顯,應用程式可能當機。是否繼續?", "This stops GPU monitoring and restarts the dGPU. Applications may crash. Continue?");
+ public static string GpuSleepWorking => T("正在尝试让独显休眠,请稍候。", "正在嘗試讓獨顯休眠,請稍候。", "Attempting to put the dGPU to sleep.");
+ public static string GpuSleepSucceeded => T("独显已进入Windows报告的D3状态。GPU监控将保持关闭以免将其唤醒。", "獨顯已進入 Windows 回報的 D3 狀態。GPU 監控將保持關閉以避免喚醒。", "The dGPU entered the Windows-reported D3 state. GPU monitoring remains off.");
+ public static string GpuSleepMonitorStopFailed => T("无法完全停止硬件监控进程。为避免监控继续唤醒独显,本次休眠尝试已取消。", "無法完全停止硬體監控程序。為避免監控繼續喚醒獨顯,本次休眠嘗試已取消。", "The hardware monitor could not be stopped completely. The sleep attempt was cancelled to avoid waking the dGPU.");
+ public static string GpuSleepRestartFailed(string error) => T($"重启独显失败:{error}", $"重新啟動獨顯失敗:{error}", $"Failed to restart the dGPU: {error}");
+ public static string GpuSleepFailedNoProcesses => T("独显仍未进入D3,但没有检测到可处理的普通应用。可能是显示链路、驱动服务或平台电源管理阻止了休眠。", "獨顯仍未進入 D3,但未偵測到可處理的一般應用程式。可能是顯示鏈路、驅動服務或平台電源管理所致。", "The dGPU did not enter D3 and no configurable application was found. Display routing, driver services, or platform power management may be responsible.");
+ public static string GpuSleepStillFailed => T("调整应用后独显仍未进入D3。请检查仍存在的进程、外接显示器、独显直连模式和其他硬件监控程序。", "調整應用程式後獨顯仍未進入 D3。請檢查剩餘程序、外接顯示器、獨顯直連模式與其他硬體監控程式。", "The dGPU still did not enter D3. Check remaining processes, external displays, discrete mode, and monitoring tools.");
+ public static string GpuSleepRepairTitle => T("独显休眠失败修复", "獨顯休眠失敗修復", "dGPU Sleep Repair");
+ public static string GpuSleepRepairDescription => T("以下程序在休眠失败后仍被检测为使用独显。可将选中程序设为节能GPU,重启后再次尝试。所有修改均保存原始值并可回滚。", "以下程序在休眠失敗後仍被偵測為使用獨顯。可將選取程序設為節能 GPU,重新啟動後再次嘗試。所有修改皆可回復。", "These processes still use the dGPU. Set selected apps to the power-saving GPU, restart them, then retry. Changes are backed up and reversible.");
+ public static string GpuSleepProcessColumn => T("程序", "程序", "Process");
+ public static string GpuSleepUsageColumn => T("GPU调用", "GPU 使用", "GPU use");
+ public static string GpuSleepPreferenceColumn => T("当前偏好", "目前偏好", "Preference");
+ public static string GpuSleepCustomListColumn => T("已在自定列表", "已在自訂清單", "Customized");
+ public static string GpuSleepPathColumn => T("可执行文件路径", "執行檔路徑", "Executable path");
+ public static string GpuPreferenceSystemDefault => T("系统决定", "系統決定", "System default");
+ public static string GpuPreferencePowerSaving => T("节能GPU", "節能 GPU", "Power saving GPU");
+ public static string GpuPreferenceHighPerformance => T("高性能GPU", "高效能 GPU", "High performance GPU");
+ public static string GpuPreferenceUnavailable => T("不可用", "無法使用", "Unavailable");
+ public static string GpuPreferenceApplyPowerSaving => T("设为节能GPU", "設為節能 GPU", "Use power-saving GPU");
+ public static string GpuPreferenceRestore => T("恢复之前设置", "還原先前設定", "Restore previous settings");
+ public static string GpuSleepRestartSelected => T("确认并重启应用", "確認並重新啟動應用程式", "Restart selected apps");
+ public static string GpuSleepRetry => T("再次尝试休眠", "再次嘗試休眠", "Retry sleep");
+ public static string GpuSleepSelectApplication => T("请至少选择一个可以设置GPU偏好的应用。", "請至少選擇一個可設定 GPU 偏好的應用程式。", "Select at least one configurable application.");
+ public static string GpuSleepDwmWarning => T("你选择了dwm.exe。重启桌面合成器时屏幕可能黑屏或闪烁,确认继续吗?", "你選擇了 dwm.exe。重新啟動桌面合成器時畫面可能黑屏或閃爍,確定繼續嗎?", "dwm.exe is selected. Restarting Desktop Window Manager may blank or flicker the display. Continue?");
+ public static string GpuPreferenceAppliedRestartRequired => T("GPU偏好已写入。必须重启这些应用才能生效。", "GPU 偏好已寫入,必須重新啟動應用程式才會生效。", "GPU preferences were saved. Restart the applications to apply them.");
+ public static string GpuPreferenceApplyFailed(string error) => T($"设置GPU偏好失败:{error}", $"設定 GPU 偏好失敗:{error}", $"Failed to set GPU preference: {error}");
+ public static string GpuSleepRestartWarning => T("将先尝试正常关闭,必要时强制结束选中的应用。未保存的数据可能丢失,确认继续吗?", "將先嘗試正常關閉,必要時強制結束選取的應用程式。未儲存資料可能遺失,確定繼續嗎?", "Selected apps will be closed and may be forcibly terminated. Unsaved data may be lost. Continue?");
+ public static string GpuSleepApplicationsRestarted => T("应用已重启。现在可以再次尝试独显休眠。", "應用程式已重新啟動,現在可以再次嘗試獨顯休眠。", "Applications were restarted. You can retry dGPU sleep now.");
+ public static string GpuPreferenceBackupMissing => T("找不到GPU偏好设置备份。", "找不到 GPU 偏好設定備份。", "GPU preference backup was not found.");
+ public static string GpuPreferenceConflict => T("部分应用的设置在OSH修改后又发生变化。是否仍覆盖并恢复到最初状态?", "部分應用程式設定在 OSH 修改後又有變更。是否仍覆寫並還原最初狀態?", "Some preferences changed after OSH modified them. Overwrite those changes and restore the original values?");
+ public static string GpuPreferenceRestoreFailed => T("恢复GPU偏好失败。", "還原 GPU 偏好失敗。", "Failed to restore GPU preferences.");
+ public static string GpuPreferenceRestoredRestartRequired => T("原始GPU偏好已恢复。相关应用需要再次重启。", "原始 GPU 偏好已還原,相關應用程式需要再次重新啟動。", "Original GPU preferences were restored. Restart the affected applications again.");
}
}