diff --git a/LiteMonitor.csproj b/LiteMonitor.csproj index 9241a9b..093db9a 100644 --- a/LiteMonitor.csproj +++ b/LiteMonitor.csproj @@ -9,6 +9,11 @@ x64 resources\assets\app.ico app.manifest + true + true + false + + true none false @@ -18,11 +23,18 @@ - - + + + + + + + + + diff --git a/Properties/Resources.resx b/Properties/Resources.resx index c6835ff..50322d5 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -119,30 +119,30 @@ - ..\resources\assets\app.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\app.ico;System.Drawing.Icon, System.Drawing.Common - ..\resources\assets\CleanMem.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\CleanMem.png;System.Drawing.Bitmap, System.Drawing.Common CleanMem - ..\resources\assets\HardwareInfo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\HardwareInfo.png;System.Drawing.Bitmap, System.Drawing.Common HardwareInfo.png - ..\resources\assets\NetworkTest.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\NetworkTest.png;System.Drawing.Bitmap, System.Drawing.Common 网络图标 - ..\resources\assets\Settings.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\Settings.png;System.Drawing.Bitmap, System.Drawing.Common Settings - ..\resources\assets\ThemeIcon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\ThemeIcon.png;System.Drawing.Bitmap, System.Drawing.Common 主题图标 - ..\resources\assets\Traffic.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\resources\assets\Traffic.png;System.Drawing.Bitmap, System.Drawing.Common 历史流量统计 - \ No newline at end of file + diff --git a/build/LiteMonitor.iss b/build/LiteMonitor.iss new file mode 100644 index 0000000..8baf2c5 --- /dev/null +++ b/build/LiteMonitor.iss @@ -0,0 +1,41 @@ +#define MyAppName "LiteMonitor" +#define MyAppVersion "1.3.4" +#define MyAppPublisher "Diorser" +#define MyAppExeName "LiteMonitor.exe" + +[Setup] +AppId={{8A6A4E34-36AF-430E-B42E-54793F51DE79} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +DefaultDirName={autopf}\{#MyAppName} +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +PrivilegesRequired=admin +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +OutputDir=..\artifacts\installer +OutputBaseFilename=LiteMonitor_Setup_{#MyAppVersion}_x64 +SetupIconFile=..\resources\assets\app.ico +Compression=lzma2/ultra64 +SolidCompression=yes +WizardStyle=modern +UninstallDisplayIcon={app}\{#MyAppExeName} +CloseApplications=yes + +[Languages] +Name: "chinesesimp"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加任务:"; Flags: unchecked + +[Files] +Source: "..\artifacts\publish-stable\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" +Name: "{group}\卸载 {#MyAppName}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "启动 {#MyAppName}"; Flags: nowait postinstall skipifsilent diff --git a/resources/lang/en.json b/resources/lang/en.json index 5dd9fb5..4499f66 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -148,6 +148,9 @@ "TaskbarHoverShowAll": "Hover Details", "TaskbarStyle": "Style", "TaskbarMonitor": "Monitor", + "TaskbarMonitorSettings": "Screen Selection", + "TaskbarMonitorTip": "Choose which screens should show the taskbar widget. Auto mode follows the primary taskbar only.", + "TaskbarAllScreens": "Show On All Screens", "TaskbarStyleBold": "Big", "TaskbarStyleRegular": "Small", "TaskbarAlign": "Position", @@ -240,4 +243,4 @@ "PluginAddTarget": "+ Add Target", "PluginDeleteConfirm": "Delete this plugin copy?" } -} \ No newline at end of file +} diff --git a/resources/lang/zh-tw.json b/resources/lang/zh-tw.json index e22fff5..65a58b9 100644 --- a/resources/lang/zh-tw.json +++ b/resources/lang/zh-tw.json @@ -145,6 +145,9 @@ "TaskbarHoverShowAll": "滑鼠懸停顯示詳情", "TaskbarStyle": "工作列樣式", "TaskbarMonitor": "顯示器螢幕", + "TaskbarMonitorSettings": "螢幕選擇", + "TaskbarMonitorTip": "可勾選工作列顯示在哪些螢幕上;勾選「自動模式」時,將只跟隨主螢幕工作列。", + "TaskbarAllScreens": "所有螢幕都顯示", "TaskbarStyleBold": "大字模式", "TaskbarStyleRegular": "小字模式", "TaskbarAlign": "顯示位置", @@ -238,4 +241,4 @@ "PluginAddTarget": "+ 新增監控目標", "PluginDeleteConfirm": "確定要刪除此插件副本嗎?" } -} \ No newline at end of file +} diff --git a/resources/lang/zh.json b/resources/lang/zh.json index cc3abe1..ccaab56 100644 --- a/resources/lang/zh.json +++ b/resources/lang/zh.json @@ -148,6 +148,9 @@ "TaskbarHoverShowAll": "鼠标悬停显示详情", "TaskbarStyle": "任务栏样式", "TaskbarMonitor": "显示器屏幕", + "TaskbarMonitorSettings": "显示屏选择", + "TaskbarMonitorTip": "可勾选任务栏显示在哪些屏幕上;勾选“自动模式”时,将只跟随主屏任务栏。", + "TaskbarAllScreens": "所有屏幕都显示", "TaskbarStyleBold": "大字模式", "TaskbarStyleRegular": "小字模式", "TaskbarAlign": "显示位置", @@ -240,4 +243,4 @@ "PluginAddTarget": "+ 添加新监控目标", "PluginDeleteConfirm": "确定要删除此插件副本吗?" } -} \ No newline at end of file +} diff --git a/src/Core/Actions/SettingsChanger.cs b/src/Core/Actions/SettingsChanger.cs index dcde593..14eb521 100644 --- a/src/Core/Actions/SettingsChanger.cs +++ b/src/Core/Actions/SettingsChanger.cs @@ -57,6 +57,11 @@ public static void Merge(Settings live, Settings draft) live.PluginInstances = System.Text.Json.JsonSerializer.Deserialize>(json) ?? new List(); continue; } + if (p.Name == "TaskbarMonitorDevices") + { + live.TaskbarMonitorDevices = new List(draft.TaskbarMonitorDevices ?? new List()); + continue; + } if (p.Name == "Thresholds") { // 阈值是 Class 类型 (ThresholdsSet),必须深拷贝 diff --git a/src/Core/Settings.cs b/src/Core/Settings.cs index a21252d..b3eccf1 100644 --- a/src/Core/Settings.cs +++ b/src/Core/Settings.cs @@ -85,6 +85,8 @@ public class Settings // ★★★ 新增:指定任务栏显示的屏幕设备名 ("" = 自动/主屏) ★★★ public string TaskbarMonitorDevice { get; set; } = ""; + public List TaskbarMonitorDevices { get; set; } = new List(); + public bool TaskbarShowOnAllScreens { get; set; } = false; // 任务栏行为配置 public bool TaskbarClickThrough { get; set; } = false; // 鼠标穿透 diff --git a/src/System/HardwareMonitor.cs b/src/System/HardwareMonitor.cs index bf27da6..9301ccb 100644 --- a/src/System/HardwareMonitor.cs +++ b/src/System/HardwareMonitor.cs @@ -301,7 +301,7 @@ private void InitializeAsync() _perfCounterManager.InitializeAsync(); // 这句耗时 4-5 秒,但在执行过程中,硬件会陆续添加到 _computer.Hardware - _computer.Open(); + OpenComputerSafe(); // ★★★ T0+级修复:彻底禁用历史记录,解决 SensorValue[] 飙升 ★★★ // 必须在 Open() 之后调用,此时传感器才被创建 @@ -442,7 +442,7 @@ private void ReloadComputerSafe() _computer.Hardware.Clear(); } - _computer.Open(); + OpenComputerSafe(); DisableSensorHistory(); } @@ -467,6 +467,44 @@ private void DisableSensorHistory() catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"[MemoryFix] Failed: {ex.Message}"); } } + private void OpenComputerSafe() + { + try + { + _computer.Open(); + } + catch (ArgumentNullException ex) when (IsMutexBootstrapFailure(ex)) + { + System.Diagnostics.Debug.WriteLine($"[HardwareMonitor] Computer.Open mutex bootstrap failed, retrying in compatibility mode: {ex.Message}"); + OpenComputerWithoutMutex(); + } + } + + private void OpenComputerWithoutMutex() + { + var type = typeof(Computer); + var addGroups = type.GetMethod("AddGroups", BindingFlags.Instance | BindingFlags.NonPublic); + var openField = type.GetField("_open", BindingFlags.Instance | BindingFlags.NonPublic); + + if (addGroups == null || openField == null) + throw new MissingMemberException("LibreHardwareMonitor compatibility entry points were not found."); + + bool isOpen = openField.GetValue(_computer) as bool? ?? false; + if (!isOpen) + { + addGroups.Invoke(_computer, null); + openField.SetValue(_computer, true); + } + } + + private static bool IsMutexBootstrapFailure(ArgumentNullException ex) + { + if (!string.Equals(ex.ParamName, "identity", StringComparison.OrdinalIgnoreCase)) + return false; + + return ex.StackTrace?.Contains("LibreHardwareMonitor.Hardware.Mutexes", StringComparison.Ordinal) == true; + } + // 递归更新子硬件,确保 SuperIO 刷新 private void UpdateWithSubHardware(IHardware hw) { diff --git a/src/System/HardwareServices/HardwareRules.cs b/src/System/HardwareServices/HardwareRules.cs index 97e2dfe..db96417 100644 --- a/src/System/HardwareServices/HardwareRules.cs +++ b/src/System/HardwareServices/HardwareRules.cs @@ -34,9 +34,11 @@ public static int GetHwPriority(IHardware hw) // 3. AMD 显卡 if (hw.HardwareType == HardwareType.GpuAmd) { - // 通用名 "AMD Radeon(TM) Graphics" 通常是核显 -> 优先级 2 - if (name.Equals("AMD Radeon(TM) Graphics", StringComparison.OrdinalIgnoreCase)) return 2; - // 其他具体型号 (如 RX 7900 XTX) 视为独显 -> 优先级 0 + // AMD 核显常见命名:Radeon(TM) Graphics / 610M / 780M / Vega 等 + // 这类设备应低于独显,避免覆盖独显的 GPU.Load 映射 + if (IsAmdIntegratedGpu(name)) return 2; + + // 其他具体型号 (如 RX 7900 XTX / RX 7600M) 视为独显 -> 优先级 0 return 0; } @@ -77,6 +79,37 @@ public static bool IsDiscreteArc(string name) return Has(name, " A") || Has(name, " B") || Has(name, " Pro"); } + private static bool IsAmdIntegratedGpu(string name) + { + if (string.IsNullOrEmpty(name)) return false; + + if (name.Equals("AMD Radeon(TM) Graphics", StringComparison.OrdinalIgnoreCase)) + return true; + + // 独显关键字:RX / PRO / FIREPRO 优先视作独显 + if (Has(name, "rx") || Has(name, "pro") || Has(name, "firepro")) + return false; + + // 常见核显命名关键字 + if (Has(name, "vega")) return true; + if (Has(name, "radeon") && Has(name, "graphics")) return true; + + if (Has(name, "radeon")) + { + // 典型 RDNA 核显尾缀(610M/660M/680M/760M/780M/880M/890M) + string[] igpuTokens = { "610m", "660m", "680m", "760m", "780m", "880m", "890m" }; + foreach (var token in igpuTokens) + { + if (Has(name, token)) return true; + } + + // 没有 RX/PRO/FIREPRO 且标记为 Radeon(TM),大概率为核显 + if (Has(name, "radeon(tm)")) return true; + } + + return false; + } + /// /// 判断是否应该使用共享内存 (Shared Memory) /// diff --git a/src/System/HardwareServices/HardwareValueProvider.cs b/src/System/HardwareServices/HardwareValueProvider.cs index 4dd9541..2f7570a 100644 --- a/src/System/HardwareServices/HardwareValueProvider.cs +++ b/src/System/HardwareServices/HardwareValueProvider.cs @@ -169,6 +169,131 @@ public void PreCacheAllSensors(SensorMap map) return null; } + private static bool IsGpuLoadSensorCandidate(string name) + { + bool include = + SensorMap.Has(name, "core") || + SensorMap.Has(name, "d3d 3d") || + SensorMap.Has(name, "3d") || + SensorMap.Has(name, "graphics") || + SensorMap.Has(name, "gpu") || + SensorMap.Has(name, "render") || + SensorMap.Has(name, "utilization") || + SensorMap.Has(name, "global") || + name.Equals("load", StringComparison.OrdinalIgnoreCase); + + if (!include) return false; + + bool exclude = + SensorMap.Has(name, "memory") || + SensorMap.Has(name, "video") || + SensorMap.Has(name, "encode") || + SensorMap.Has(name, "decode") || + SensorMap.Has(name, "copy") || + SensorMap.Has(name, "bus") || + SensorMap.Has(name, "pcie") || + SensorMap.Has(name, "media"); + + return !exclude; + } + + private static int ScoreGpuLoadSensor(ISensor sensor) + { + string name = sensor.Name ?? ""; + if (!IsGpuLoadSensorCandidate(name)) return int.MinValue; + + int score = 0; + + if (SensorMap.Has(name, "core")) score += 60; + if (SensorMap.Has(name, "gpu")) score += 45; + if (SensorMap.Has(name, "graphics")) score += 40; + if (SensorMap.Has(name, "d3d 3d")) score += 35; + else if (SensorMap.Has(name, "3d")) score += 25; + if (SensorMap.Has(name, "utilization")) score += 20; + if (SensorMap.Has(name, "global")) score += 20; + if (SensorMap.Has(name, "render")) score += 15; + if (name.Equals("load", StringComparison.OrdinalIgnoreCase)) score += 10; + if (!SensorMap.Has(name, "d3d")) score += 5; + + return score; + } + + private ISensor? FindBestGpuLoadSensor() + { + var gpu = _sensorMap.CachedGpu; + if (gpu == null) return null; + + ISensor? best = null; + int bestScore = int.MinValue; + float bestValue = float.MinValue; + + foreach (var sensor in gpu.Sensors) + { + if (sensor.SensorType != SensorType.Load) continue; + + int score = ScoreGpuLoadSensor(sensor); + if (score == int.MinValue) continue; + + float value = sensor.Value ?? float.MinValue; + + if (best == null || + score > bestScore || + (score == bestScore && value > bestValue)) + { + best = sensor; + bestScore = score; + bestValue = value; + } + } + + return best; + } + + private static IEnumerable EnumerateSensors(IHardware hardware) + { + foreach (var sensor in hardware.Sensors) + { + yield return sensor; + } + + foreach (var sub in hardware.SubHardware) + { + foreach (var sensor in EnumerateSensors(sub)) + { + yield return sensor; + } + } + } + + private ISensor? FindBestGpuTempSensor() + { + var gpu = _sensorMap.CachedGpu; + if (gpu == null) return null; + + ISensor? best = null; + int bestScore = int.MinValue; + float bestValue = float.MinValue; + + foreach (var sensor in EnumerateSensors(gpu)) + { + int score = SensorMatcher.ScoreGpuTempSensor(sensor); + if (score == int.MinValue) continue; + + float value = sensor.Value ?? float.MinValue; + + if (best == null || + score > bestScore || + (score == bestScore && value > bestValue)) + { + best = sensor; + bestScore = score; + bestValue = value; + } + } + + return best; + } + public void ClearCache() { lock (_lock) @@ -337,7 +462,60 @@ public void OnUpdateTickStarted() } break; - // 7. 显存 + // 7. GPU 负载 + case "GPU.Load": + if (_manualSensorCache.TryGetValue("GPU.Load", out var gpuLoadSensor) && + gpuLoadSensor.Value.HasValue && + !float.IsNaN(gpuLoadSensor.Value.Value)) + { + result = gpuLoadSensor.Value.Value; + } + + // 如果当前映射缺失或长期停在 0,自动从当前 GPU 重新挑选更合理的负载传感器 + if (result == null || result.Value <= 0.01f) + { + var bestGpuLoad = FindBestGpuLoadSensor(); + if (bestGpuLoad != null && bestGpuLoad.Value.HasValue && !float.IsNaN(bestGpuLoad.Value.Value)) + { + result = bestGpuLoad.Value.Value; + _manualSensorCache["GPU.Load"] = bestGpuLoad; + } + } + + if (result == null && _lastValidMap.TryGetValue("GPU.Load", out var lastGpuLoad)) + { + result = lastGpuLoad; + } + + if (result == null) result = 0f; + result = Math.Clamp(result.Value, 0f, 100f); + break; + + // 7. GPU 温度 / 显存 + case "GPU.Temp": + if (_manualSensorCache.TryGetValue("GPU.Temp", out var gpuTempSensor) && + gpuTempSensor.Value.HasValue && + !float.IsNaN(gpuTempSensor.Value.Value)) + { + result = gpuTempSensor.Value.Value; + } + + if (result == null || result.Value <= 0.01f) + { + var bestGpuTemp = FindBestGpuTempSensor(); + if (bestGpuTemp != null && bestGpuTemp.Value.HasValue && !float.IsNaN(bestGpuTemp.Value.Value)) + { + result = bestGpuTemp.Value.Value; + _manualSensorCache["GPU.Temp"] = bestGpuTemp; + } + } + + if (result == null && _lastValidMap.TryGetValue("GPU.Temp", out var lastGpuTemp)) + { + result = lastGpuTemp; + } + break; + case "GPU.VRAM": float? used = GetValue("GPU.VRAM.Used"); float? total = GetValue("GPU.VRAM.Total"); @@ -479,4 +657,4 @@ public void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/System/HardwareServices/SensorMap.cs b/src/System/HardwareServices/SensorMap.cs index 4e1e891..400262e 100644 --- a/src/System/HardwareServices/SensorMap.cs +++ b/src/System/HardwareServices/SensorMap.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using LibreHardwareMonitor.Hardware; using LiteMonitor.src.Core; @@ -185,6 +187,17 @@ void RegisterTo(IHardware hw) // 2. 如果优先级相同 (通常是同一张显卡的不同传感器,或者是两张同样的显卡), // 则应用 D3D vs Vendor 优选逻辑。 + if (key == "GPU.Temp") + { + int existingScore = SensorMatcher.ScoreGpuTempSensor(existing); + int newScore = SensorMatcher.ScoreGpuTempSensor(s); + if (newScore > existingScore) + { + newMap[key] = s; + } + continue; + } + bool existingIsD3D = existing.Name.Contains("D3D", StringComparison.OrdinalIgnoreCase); bool newIsD3D = s.Name.Contains("D3D", StringComparison.OrdinalIgnoreCase); @@ -277,9 +290,72 @@ void RegisterTo(IHardware hw) _lastMapBuild = DateTime.Now; // ★★★ [优化 3] 指纹记录已移除 ★★★ } + + WriteDiagnostics(computer, newMap, newGpu, newCpuCache); } // 复用 HardwareRules 的字符串匹配,避免重复造轮子 public static bool Has(string source, string sub) => HardwareRules.Has(source, sub); + + private static void WriteDiagnostics( + Computer computer, + Dictionary newMap, + IHardware? newGpu, + List newCpuCache) + { + if (!string.Equals(Environment.GetEnvironmentVariable("LITEMONITOR_DIAG"), "1", StringComparison.OrdinalIgnoreCase)) + return; + + try + { + var sb = new StringBuilder(8192); + sb.AppendLine("==== LiteMonitor Hardware Diagnostics ===="); + sb.AppendLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); + sb.AppendLine(); + sb.AppendLine("[Selected]"); + sb.AppendLine($"GPU={newGpu?.Name ?? ""}"); + sb.AppendLine($"CPUCoreCache={newCpuCache.Count}"); + sb.AppendLine(); + sb.AppendLine("[Mapped Keys]"); + + foreach (var kv in newMap.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase)) + { + string hwName = kv.Value.Hardware?.Name ?? ""; + string sensorName = kv.Value.Name ?? ""; + string sensorType = kv.Value.SensorType.ToString(); + string value = kv.Value.Value?.ToString("0.###") ?? "null"; + sb.AppendLine($"{kv.Key} => {sensorType} | {sensorName} [{hwName}] | {value}"); + } + + sb.AppendLine(); + sb.AppendLine("[Hardware Tree]"); + foreach (var hw in computer.Hardware) + { + DumpHardware(sb, hw, 0); + } + + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "hardware_diagnostic.log"), sb.ToString(), Encoding.UTF8); + } + catch + { + } + } + + private static void DumpHardware(StringBuilder sb, IHardware hw, int depth) + { + string indent = new string(' ', depth * 2); + sb.AppendLine($"{indent}- {hw.HardwareType}: {hw.Name}"); + + foreach (var s in hw.Sensors.OrderBy(x => x.SensorType).ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)) + { + string value = s.Value?.ToString("0.###") ?? "null"; + sb.AppendLine($"{indent} * {s.SensorType}: {s.Name} = {value}"); + } + + foreach (var sub in hw.SubHardware) + { + DumpHardware(sb, sub, depth + 1); + } + } } -} \ No newline at end of file +} diff --git a/src/System/HardwareServices/SensorMatcher.cs b/src/System/HardwareServices/SensorMatcher.cs index ca0c78c..1532ea5 100644 --- a/src/System/HardwareServices/SensorMatcher.cs +++ b/src/System/HardwareServices/SensorMatcher.cs @@ -12,6 +12,69 @@ public static class SensorMatcher { // 复用 HardwareRules 的字符串匹配,避免重复造轮子 private static bool Has(string source, string sub) => HardwareRules.Has(source, sub); + + // 统一 GPU 负载命名识别:覆盖不同厂商/驱动的常见命名,并排除干扰项 + private static bool IsGpuLoadName(string name) + { + bool include = + Has(name, "core") || + Has(name, "d3d 3d") || + Has(name, "3d") || + Has(name, "graphics") || + Has(name, "gpu") || + Has(name, "render") || + Has(name, "utilization") || + Has(name, "global") || + name.Equals("load", StringComparison.OrdinalIgnoreCase); + + if (!include) return false; + + bool exclude = + Has(name, "memory") || + Has(name, "video") || + Has(name, "encode") || + Has(name, "decode") || + Has(name, "copy") || + Has(name, "bus") || + Has(name, "pcie") || + Has(name, "media"); + + return !exclude; + } + + public static int ScoreGpuTempSensor(ISensor sensor) + { + if (sensor.SensorType != SensorType.Temperature) return int.MinValue; + + string name = sensor.Name ?? ""; + if (string.IsNullOrWhiteSpace(name)) return int.MinValue; + + bool exclude = + Has(name, "memory") || + Has(name, "vram") || + Has(name, "hbm") || + Has(name, "vrm") || + Has(name, "liquid") || + Has(name, "coolant"); + + if (exclude) return int.MinValue; + + int score = 0; + + if (Has(name, "core")) score += 120; + if (Has(name, "package")) score += 110; + if (Has(name, "gpu")) score += 100; + if (Has(name, "edge")) score += 95; + if (Has(name, "junction")) score += 90; + if (Has(name, "hot spot") || Has(name, "hotspot")) score += 85; + if (Has(name, "temperature")) score += 40; + if (Has(name, "temp")) score += 20; + if (Has(name, "soc")) score += 10; + if (name.Equals("temperature", StringComparison.OrdinalIgnoreCase)) score += 60; + if (name.Equals("gpu", StringComparison.OrdinalIgnoreCase)) score += 10; + + return score > 0 ? score : int.MinValue; + } /// /// 尝试匹配传感器名称到标准 Key @@ -71,8 +134,8 @@ public static class SensorMatcher // --- GPU --- if (type is HardwareType.GpuNvidia or HardwareType.GpuAmd or HardwareType.GpuIntel) { - if (s.SensorType == SensorType.Load && (Has(name, "core") || Has(name, "d3d 3d"))) return "GPU.Load"; - if (s.SensorType == SensorType.Temperature && (Has(name, "core") || Has(name, "hot spot") || Has(name, "soc") || Has(name, "vr"))) return "GPU.Temp"; + if (s.SensorType == SensorType.Load && IsGpuLoadName(name)) return "GPU.Load"; + if (ScoreGpuTempSensor(s) != int.MinValue) return "GPU.Temp"; // VRAM Logic (简化且准确) // 1. 根据硬件规则判断是否应该优先找共享内存 (核显) diff --git a/src/System/Program.cs b/src/System/Program.cs index 38eb757..0c60612 100644 --- a/src/System/Program.cs +++ b/src/System/Program.cs @@ -133,6 +133,8 @@ static void LogCrash(Exception? ex, string source) $"[Time]: {DateTime.Now}\n" + $"[Source]: {source}\n" + $"[Message]: {ex.Message}\n" + + $"[Exception]: {ex.GetType().FullName}\n" + + $"[Detail]: {ex}\n" + $"[Stack]:\n{ex.StackTrace}\n" + "==================================================\n\n"; @@ -148,4 +150,4 @@ static void LogCrash(Exception? ex, string source) } } } -} \ No newline at end of file +} diff --git a/src/UI/Helpers/MainFormBizHelper.cs b/src/UI/Helpers/MainFormBizHelper.cs index 78f9911..20bd738 100644 --- a/src/UI/Helpers/MainFormBizHelper.cs +++ b/src/UI/Helpers/MainFormBizHelper.cs @@ -159,7 +159,14 @@ public void RebuildMenus() { if (_form.ContextMenuStrip != null) { - _form.ContextMenuStrip.Dispose(); + try + { + _form.ContextMenuStrip.Dispose(); + } + catch (TypeLoadException) + { + // single-file + trimmed 下,WinForms ToolStrip 清理链可能抛 TypeLoadException,忽略以保证运行。 + } _form.ContextMenuStrip = null; } _form.ContextMenuStrip = MenuManager.Build((MainForm)_form, _cfg, _ui); diff --git a/src/UI/Helpers/TaskbarBizHelper.cs b/src/UI/Helpers/TaskbarBizHelper.cs index 6b446bf..aae1f9e 100644 --- a/src/UI/Helpers/TaskbarBizHelper.cs +++ b/src/UI/Helpers/TaskbarBizHelper.cs @@ -16,6 +16,7 @@ public class TaskbarBizHelper private readonly Form _form; private readonly Settings _cfg; private readonly TaskbarWinHelper _winHelper; + private readonly string _targetDevice; private Rectangle _taskbarRect = Rectangle.Empty; private int _taskbarHeight = 32; @@ -33,11 +34,12 @@ public class TaskbarBizHelper public Color TransparentKey => _transparentKey; public bool LastIsLightTheme => _lastIsLightTheme; - public TaskbarBizHelper(Form form, Settings cfg, TaskbarWinHelper winHelper) + public TaskbarBizHelper(Form form, Settings cfg, TaskbarWinHelper winHelper, string targetDevice) { _form = form; _cfg = cfg; _winHelper = winHelper; + _targetDevice = targetDevice; _isWin11 = Environment.OSVersion.Version >= new Version(10, 0, 22000); } @@ -84,7 +86,7 @@ public void CheckTheme(bool force = false) // ================================================================= public void FindHandles() { - var handles = _winHelper.FindHandles(_cfg.TaskbarMonitorDevice); + var handles = _winHelper.FindHandles(_targetDevice); _hTaskbar = handles.hTaskbar; _hTray = handles.hTray; } @@ -104,7 +106,7 @@ public void AttachToTaskbar() public void UpdateTaskbarRect() { - _taskbarRect = _winHelper.GetTaskbarRect(_hTaskbar, _cfg.TaskbarMonitorDevice); + _taskbarRect = _winHelper.GetTaskbarRect(_hTaskbar, _targetDevice); _taskbarHeight = Math.Max(24, _taskbarRect.Height); } diff --git a/src/UI/Helpers/TaskbarWinHelper.cs b/src/UI/Helpers/TaskbarWinHelper.cs index 371fcf5..584c9a7 100644 --- a/src/UI/Helpers/TaskbarWinHelper.cs +++ b/src/UI/Helpers/TaskbarWinHelper.cs @@ -215,7 +215,7 @@ private IntPtr FindSecondaryTaskbar(Screen screen) if (screen.Bounds.Contains(r.Location) || screen.Bounds.IntersectsWith(r)) return hWnd; } - return FindWindow("Shell_TrayWnd", null); + return IntPtr.Zero; } public Rectangle GetTaskbarRect(IntPtr hTaskbar, string targetDevice) diff --git a/src/UI/MainForm_Transparent.cs b/src/UI/MainForm_Transparent.cs index 1aa385e..567cfea 100644 --- a/src/UI/MainForm_Transparent.cs +++ b/src/UI/MainForm_Transparent.cs @@ -3,6 +3,7 @@ using LiteMonitor.src.UI; using LiteMonitor.src.UI.Helpers; using System; +using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; @@ -21,6 +22,7 @@ public class MainForm : Form private readonly MainFormBizHelper _bizHelper; private readonly int _wmTaskbarCreated; private const int WM_DISPLAYCHANGE = 0x007E; + private const int WM_GETOBJECT = 0x003D; private CancellationTokenSource _displayChangeCts; private Point _dragOffset; @@ -67,48 +69,90 @@ protected override CreateParams CreateParams public void CleanMemory() => _bizHelper.CleanMemory(); // ==== 任务栏显示 ==== - private TaskbarForm? _taskbar; + private readonly Dictionary _taskbars = new(); public void ToggleTaskbar(bool show) { if (show) { - if (_taskbar != null && !_taskbar.IsDisposed) + if (_ui == null) return; + + var desiredDevices = GetDesiredTaskbarDevices(); + var staleKeys = _taskbars.Keys.Where(x => !desiredDevices.Contains(x, StringComparer.OrdinalIgnoreCase)).ToList(); + + foreach (var key in staleKeys) { - if (_taskbar.TargetDevice != _cfg.TaskbarMonitorDevice) + if (_taskbars.TryGetValue(key, out var stale)) { - _taskbar.Close(); - _taskbar.Dispose(); - _taskbar = null; + if (!stale.IsDisposed) stale.Close(); + _taskbars.Remove(key); } } - if (_taskbar == null || _taskbar.IsDisposed) + foreach (var device in desiredDevices) { - if (_ui != null) + if (!_taskbars.TryGetValue(device, out var taskbar) || taskbar.IsDisposed) { - _taskbar = new TaskbarForm(_cfg, _ui, this); - _taskbar.Show(); + taskbar = new TaskbarForm(_cfg, _ui, this, device); + _taskbars[device] = taskbar; + taskbar.Show(); + continue; } - } - else - { - if (!_taskbar.Visible) + + if (!taskbar.Visible) { - _taskbar.Show(); - _taskbar.ReloadLayout(); + taskbar.Show(); } + + taskbar.ReloadLayout(); } } else { - if (_taskbar != null) + foreach (var taskbar in _taskbars.Values.ToList()) { - _taskbar.Close(); - _taskbar.Dispose(); - _taskbar = null; + if (!taskbar.IsDisposed) taskbar.Close(); } + _taskbars.Clear(); + } + } + + private List GetDesiredTaskbarDevices() + { + var configuredDevices = (_cfg.TaskbarMonitorDevices ?? new List()) + .Select(NormalizeTaskbarDevice) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (configuredDevices.Count > 0) + { + return configuredDevices; + } + + if (_cfg.TaskbarShowOnAllScreens) + { + return Screen.AllScreens + .Select(screen => NormalizeTaskbarDevice(screen.DeviceName)) + .Where(name => !string.IsNullOrEmpty(name)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); } + + return new List { NormalizeTaskbarDevice(_cfg.TaskbarMonitorDevice) }; + } + + private static string NormalizeTaskbarDevice(string? deviceName) + { + if (string.IsNullOrWhiteSpace(deviceName)) + { + return Screen.PrimaryScreen?.DeviceName ?? ""; + } + + var matched = Screen.AllScreens.FirstOrDefault(s => + string.Equals(s.DeviceName, deviceName, StringComparison.OrdinalIgnoreCase)); + + return matched?.DeviceName ?? deviceName; } // ========== 构造函数 ========== @@ -229,6 +273,13 @@ public void HideMainWindow() protected override void WndProc(ref Message m) { + if (m.Msg == WM_GETOBJECT) + { + // 裁剪发布在部分环境下会触发 WinForms 可访问性类型加载异常,直接忽略此消息避免异常刷屏。 + m.Result = IntPtr.Zero; + return; + } + if (m.Msg == _wmTaskbarCreated && _wmTaskbarCreated != 0) { // [Fix] Explorer 重启后,子窗口 TaskbarForm 会被销毁或失效。 @@ -344,6 +395,7 @@ protected override void OnFormClosed(FormClosedEventArgs e) _cfg.Save(); TrafficLogger.Save(); src.WebServer.LiteWebServer.Instance?.Stop(); + ToggleTaskbar(false); base.OnFormClosed(e); diff --git a/src/UI/MenuManager.cs b/src/UI/MenuManager.cs index 68175d8..23dd9a6 100644 --- a/src/UI/MenuManager.cs +++ b/src/UI/MenuManager.cs @@ -16,6 +16,19 @@ namespace LiteMonitor { public static class MenuManager { + private static Image? TryLoadMenuImage(Func resourceGetter) + { + try + { + return resourceGetter(); + } + catch + { + // 单文件+裁剪发布下,WinForms 资源反序列化可能失败。此处降级为空图标,避免启动崩溃。 + return null; + } + } + /// /// 构建 LiteMonitor 主菜单(右键菜单 + 托盘菜单) /// @@ -31,7 +44,7 @@ public static ContextMenuStrip Build(MainForm form, Settings cfg, UIController? // === 清理内存 === var cleanMem = new ToolStripMenuItem(LanguageManager.T("Menu.CleanMemory")); - cleanMem.Image = Properties.Resources.CleanMem; + cleanMem.Image = TryLoadMenuImage(() => Properties.Resources.CleanMem); cleanMem.Click += (_, __) => form.CleanMemory(); menu.Items.Add(cleanMem); menu.Items.Add(new ToolStripSeparator()); @@ -354,7 +367,7 @@ void SetMode(bool isHorizontal) var themeRoot = new ToolStripMenuItem(LanguageManager.T("Menu.Theme")); // 主题编辑器 (独立窗口,保持原样) var themeEditor = new ToolStripMenuItem(LanguageManager.T("Menu.ThemeEditor")); - themeEditor.Image = Properties.Resources.ThemeIcon; + themeEditor.Image = TryLoadMenuImage(() => Properties.Resources.ThemeIcon); themeEditor.Click += (_, __) => new ThemeEditor.ThemeEditorForm().Show(); themeRoot.DropDownItems.Add(themeEditor); themeRoot.DropDownItems.Add(new ToolStripSeparator()); @@ -381,7 +394,7 @@ void SetMode(bool isHorizontal) // --- [系统硬件详情] --- var btnHardware = new ToolStripMenuItem(LanguageManager.T("Menu.HardwareInfo")); - btnHardware.Image = Properties.Resources.HardwareInfo; // 或者找个图标 + btnHardware.Image = TryLoadMenuImage(() => Properties.Resources.HardwareInfo); // 或者找个图标 btnHardware.Click += (s, e) => { // 这里的模式是:每次点击都 new 一个新的,关闭即销毁。 @@ -397,7 +410,7 @@ void SetMode(bool isHorizontal) // 网络测速 (独立窗口,保持原样) var speedWindow = new ToolStripMenuItem(LanguageManager.T("Menu.Speedtest")); - speedWindow.Image = Properties.Resources.NetworkIcon; + speedWindow.Image = TryLoadMenuImage(() => Properties.Resources.NetworkIcon); speedWindow.Click += (_, __) => { var f = new SpeedTestForm(); @@ -408,7 +421,7 @@ void SetMode(bool isHorizontal) // 历史流量统计 (独立窗口,保持原样) var trafficItem = new ToolStripMenuItem(LanguageManager.T("Menu.Traffic")); - trafficItem.Image = Properties.Resources.TrafficIcon; + trafficItem.Image = TryLoadMenuImage(() => Properties.Resources.TrafficIcon); trafficItem.Click += (_, __) => { var formHistory = new TrafficHistoryForm(cfg); @@ -416,11 +429,11 @@ void SetMode(bool isHorizontal) }; menu.Items.Add(trafficItem); menu.Items.Add(new ToolStripSeparator()); - // ================================================================= + // ================================================================= // [新增] 设置中心入口 // ================================================================= var itemSettings = new ToolStripMenuItem(LanguageManager.T("Menu.SettingsPanel")); - itemSettings.Image = Properties.Resources.Settings; + itemSettings.Image = TryLoadMenuImage(() => Properties.Resources.Settings); // 临时写死中文,等面板做完善了再换成 LanguageManager.T("Menu.Settings") diff --git a/src/UI/Settings/TaskbarPage.cs b/src/UI/Settings/TaskbarPage.cs index 60010df..35b4033 100644 --- a/src/UI/Settings/TaskbarPage.cs +++ b/src/UI/Settings/TaskbarPage.cs @@ -14,6 +14,7 @@ public class TaskbarPage : SettingsPageBase private Panel _container; private List _customColorInputs = new List(); private List _customLayoutInputs = new List(); + private List _monitorChecks = new List(); private Control _styleCombo; private CheckBox _chkCustomLayout; @@ -45,6 +46,7 @@ public TaskbarPage() private void InitializeUI() { CreateGeneralGroup(); + CreateMonitorGroup(); CreateLayoutGroup(); CreateColorGroup(); } @@ -117,24 +119,6 @@ private void CreateGeneralGroup() group.AddToggle(this, "Menu.TaskbarSingleLine", () => Config?.TaskbarSingleLine ?? false, v => { if(Config!=null) Config.TaskbarSingleLine = v; }); group.AddToggle(this, "Menu.TaskbarHoverShowAll", () => Config?.TaskbarHoverShowAll ?? false, v => { if (Config != null) Config.TaskbarHoverShowAll = v; }); group.AddToggle(this, "Menu.ClickThrough", () => Config?.TaskbarClickThrough ?? false, v => { if(Config!=null) Config.TaskbarClickThrough = v; }); - - // Monitor Selection - var screens = Screen.AllScreens; - var screenNames = screens.Select((s, i) => $"{i + 1}: {s.DeviceName.Replace(@"\\.\DISPLAY", "Display ")}{(s.Primary ? " [Main]" : "")}").ToList(); - screenNames.Insert(0, LanguageManager.T("Menu.Auto")); - - group.AddComboIndex(this, "Menu.TaskbarMonitor", screenNames.ToArray(), - () => { - if (string.IsNullOrEmpty(Config?.TaskbarMonitorDevice)) return 0; - var idx = Array.FindIndex(screens, s => s.DeviceName == Config.TaskbarMonitorDevice); - return idx >= 0 ? idx + 1 : 0; - }, - idx => { - if (Config == null) return; - if (idx == 0) Config.TaskbarMonitorDevice = ""; - else Config.TaskbarMonitorDevice = screens[idx - 1].DeviceName; - } - ); // Double Click Action string[] actions = { @@ -164,6 +148,111 @@ private void CreateGeneralGroup() AddGroupToPage(group); } + private void CreateMonitorGroup() + { + var group = new LiteSettingsGroup(LanguageManager.T("Menu.TaskbarMonitorSettings")); + AddMonitorSelectionItems(group); + group.AddHint(LanguageManager.T("Menu.TaskbarMonitorTip")); + AddGroupToPage(group); + } + + private void AddMonitorSelectionItems(LiteSettingsGroup group) + { + _monitorChecks.Clear(); + var chkAuto = new LiteCheck(false, LanguageManager.T("Menu.Enable")); + chkAuto.CheckedChanged += (s, e) => + { + if (Config == null || !chkAuto.Checked) return; + + Config.TaskbarMonitorDevices.Clear(); + Config.TaskbarMonitorDevice = ""; + Config.TaskbarShowOnAllScreens = false; + + foreach (var check in _monitorChecks.Where(x => !ReferenceEquals(x, chkAuto))) + { + if (check.Checked) check.Checked = false; + } + }; + RegisterRefresh(() => chkAuto.Checked = GetSelectedDevices().Count == 0); + _monitorChecks.Add(chkAuto); + group.AddItem(new LiteSettingsItem(LanguageManager.T("Menu.TaskbarMonitor"), chkAuto)); + + var screens = Screen.AllScreens; + for (int i = 0; i < screens.Length; i++) + { + var screen = screens[i]; + string deviceName = screen.DeviceName; + string label = $"{i + 1}: {deviceName.Replace(@"\\.\DISPLAY", "Display ")}"; + var chk = new LiteCheck(false, LanguageManager.T("Menu.Enable")); + + chk.CheckedChanged += (s, e) => + { + if (Config == null) return; + + var selected = GetSelectedDevices(); + if (chk.Checked) + { + if (!selected.Contains(deviceName, StringComparer.OrdinalIgnoreCase)) + { + selected.Add(deviceName); + } + + Config.TaskbarMonitorDevices = selected + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + Config.TaskbarMonitorDevice = Config.TaskbarMonitorDevices.FirstOrDefault() ?? ""; + Config.TaskbarShowOnAllScreens = false; + + if (chkAuto.Checked) chkAuto.Checked = false; + } + else + { + selected.RemoveAll(x => string.Equals(x, deviceName, StringComparison.OrdinalIgnoreCase)); + Config.TaskbarMonitorDevices = selected; + Config.TaskbarMonitorDevice = Config.TaskbarMonitorDevices.FirstOrDefault() ?? ""; + } + }; + + RegisterRefresh(() => + { + var selected = GetSelectedDevices(); + chk.Checked = selected.Contains(deviceName, StringComparer.OrdinalIgnoreCase); + }); + + _monitorChecks.Add(chk); + group.AddItem(new LiteSettingsItem(label, chk)); + } + } + + private List GetSelectedDevices() + { + if (Config == null) return new List(); + + if (Config.TaskbarShowOnAllScreens) + { + return Screen.AllScreens + .Select(screen => screen.DeviceName) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + if (Config.TaskbarMonitorDevices != null && Config.TaskbarMonitorDevices.Count > 0) + { + return Config.TaskbarMonitorDevices + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + if (!string.IsNullOrWhiteSpace(Config.TaskbarMonitorDevice)) + { + return new List { Config.TaskbarMonitorDevice }; + } + + return new List(); + } + private void CreateLayoutGroup() { var group = new LiteSettingsGroup(LanguageManager.T("Menu.TaskbarCustomLayout")); @@ -290,4 +379,4 @@ private void AddGroupToPage(LiteSettingsGroup group) _container.Controls.SetChildIndex(wrapper, 0); } } -} \ No newline at end of file +} diff --git a/src/UI/TaskbarForm.cs b/src/UI/TaskbarForm.cs index fa3972e..6f31408 100644 --- a/src/UI/TaskbarForm.cs +++ b/src/UI/TaskbarForm.cs @@ -35,20 +35,21 @@ public class TaskbarForm : Form private const int WM_RBUTTONDOWN = 0x0204; private const int WM_RBUTTONUP = 0x0205; private const int WM_LBUTTONDBLCLK = 0x0203; + private const int WM_GETOBJECT = 0x003D; private bool _isWin11; - public TaskbarForm(Settings cfg, UIController ui, MainForm mainForm) + public TaskbarForm(Settings cfg, UIController ui, MainForm mainForm, string targetDevice) { _cfg = cfg; _ui = ui; _mainForm = mainForm; - TargetDevice = _cfg.TaskbarMonitorDevice; + TargetDevice = targetDevice ?? ""; _isWin11 = Environment.OSVersion.Version >= new Version(10, 0, 22000); // 初始化组件 _winHelper = new TaskbarWinHelper(this); - _bizHelper = new TaskbarBizHelper(this, _cfg, _winHelper); + _bizHelper = new TaskbarBizHelper(this, _cfg, _winHelper, TargetDevice); // 窗体属性 FormBorderStyle = FormBorderStyle.None; @@ -111,6 +112,13 @@ protected override void Dispose(bool disposing) protected override void WndProc(ref Message m) { + if (m.Msg == WM_GETOBJECT) + { + // 裁剪发布在部分环境下会触发 WinForms 可访问性类型加载异常,直接忽略此消息避免异常刷屏。 + m.Result = IntPtr.Zero; + return; + } + // [Fix] 兼容性修复:在 Win11 25H2 + StartAllBack 环境下, // 右键事件会穿透到原生任务栏。 // 因此不再区分系统版本,统一拦截右键按下和抬起消息。 @@ -141,7 +149,14 @@ private void ShowContextMenu() { if (_currentMenu != null) { - _currentMenu.Dispose(); + try + { + _currentMenu.Dispose(); + } + catch (TypeLoadException) + { + // single-file + trimmed 下,WinForms ToolStrip 清理链可能抛 TypeLoadException,忽略以保证运行。 + } _currentMenu = null; } diff --git a/src/UI/UIController.cs b/src/UI/UIController.cs index edc3258..a181fc1 100644 --- a/src/UI/UIController.cs +++ b/src/UI/UIController.cs @@ -198,7 +198,7 @@ private async void Tick() } else { - it.Value = _mon.Get(it.Key); + SetMetricValue(it, _mon.Get(it.Key)); it.TickSmooth(_cfg.AnimationSpeed); } } @@ -219,7 +219,7 @@ void UpdateItem(MetricItem it) } else { - it.Value = _mon.Get(it.Key); + SetMetricValue(it, _mon.Get(it.Key)); it.TickSmooth(_cfg.AnimationSpeed); } } @@ -319,9 +319,7 @@ private void BuildMetrics() } else { - float? val = _mon.Get(item.Key); - item.Value = val; - if (val.HasValue) item.DisplayValue = val.Value; + SetMetricValue(item, _mon.Get(item.Key)); } currentGroupList.Add(item); @@ -444,9 +442,14 @@ private MetricItem CreateMetric(MonitorItemConfig cfg) private void InitMetricValue(MetricItem? item) { if (item == null) return; - float? val = _mon.Get(item.Key); - item.Value = val; - if (val.HasValue) item.DisplayValue = val.Value; + SetMetricValue(item, _mon.Get(item.Key)); + } + + private static void SetMetricValue(MetricItem item, float? value) + { + item.Value = value; + item.TextValue = value.HasValue ? null : "--"; + if (value.HasValue) item.DisplayValue = value.Value; } private void CheckTemperatureAlert() @@ -509,4 +512,4 @@ public void Dispose() _mon.Dispose(); } } -} \ No newline at end of file +} diff --git "a/\344\275\277\347\224\250\350\257\264\346\230\216(\345\277\205\350\257\273).txt" "b/\344\275\277\347\224\250\350\257\264\346\230\216(\345\277\205\350\257\273).txt" new file mode 100644 index 0000000..4368e9f --- /dev/null +++ "b/\344\275\277\347\224\250\350\257\264\346\230\216(\345\277\205\350\257\273).txt" @@ -0,0 +1,5 @@ +LiteMonitor 使用说明 + +1. 首次启动后,如果 CPU 温度、频率或功耗无法读取,请允许程序安装所需驱动组件。 +2. 主题、语言和插件资源会随主程序一起放在 resources 目录下,请不要单独删除。 +3. 更完整的功能说明、编译方式和更新说明请查看仓库根目录下的 README.md。