Repository navigation
Expand file tree
/
Copy pathDiagnosticsService.cs
More file actions
197 lines (178 loc) · 8.93 KB
/
Copy pathDiagnosticsService.cs
File metadata and controls
197 lines (178 loc) · 8.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
using System.Management;
namespace HonorPCHelper;
internal static class DiagnosticsService
{
// NotifyIcon.Text не может быть длиннее 127 символов.
private const int MaxTooltipLength = 127;
private const int PowerCacheMilliseconds = 2000;
/// <summary>Снимок мощности вместе со временем измерения: читается и пишется целиком.</summary>
private sealed record PowerSample(double? Watts, long Ticks);
private static PowerSample? _power;
// Подсказка пересобирается при каждом наведении на иконку, поэтому строки
// складываются в буфер, а локализуются только литералы: интерполяция
// обоих языковых вариантов сразу удваивала бы аллокации.
[ThreadStatic] private static System.Text.StringBuilder? _buffer;
internal static string BuildCompactToolTip()
{
var state = HardwareSettings.ReadTooltipState();
var hasHardwareState = HardwareSensorSnapshot.TryParse(
state.SensorSnapshot, out var hardwareState) && hardwareState.IsFresh;
var mode = state.HunterModeActive
? "HUNTER"
: state.PerformanceModeActive
? L.T("производительный", "performance", "高性能")
: L.T("умный", "smart", "智能");
var backlightLevel = hasHardwareState
? hardwareState.KeyboardBacklightMode switch
{
0x02 => KeyboardBacklightLevel.Off,
0x03 => KeyboardBacklightLevel.Low,
0x04 => KeyboardBacklightLevel.High,
_ => state.KeyboardBacklight
}
: state.KeyboardBacklight;
var backlight = backlightLevel switch
{
KeyboardBacklightLevel.Off => L.T("выкл.", "off", "关"),
KeyboardBacklightLevel.Low => L.T("слабая", "weak", "弱"),
KeyboardBacklightLevel.High => L.T("сильная", "strong", "强"),
_ => "?"
};
var protection = hasHardwareState && hardwareState.ChargeStart.HasValue && hardwareState.ChargeEnd.HasValue
? $"{hardwareState.ChargeStart}–{hardwareState.ChargeEnd}%"
: state.BatteryProtection switch
{
BatteryProtectionMode.Home => "40–70%",
BatteryProtectionMode.Office => "70–90%",
BatteryProtectionMode.Travel => "95–100%",
BatteryProtectionMode.Disabled => L.T("выкл.", "off", "关"),
_ => "?"
};
var text = _buffer ??= new System.Text.StringBuilder(MaxTooltipLength + 32);
text.Clear();
// Строки разделяются одним \n: подсказка ограничена 127 символами,
// и пары \r\n отняли бы у неё ещё пять.
text.Append(L.T("Режим: ", "Mode: ", "模式:")).Append(mode);
text.Append('\n').Append(L.T("Подсветка: ", "Backlight: ", "背光:")).Append(backlight);
text.Append('\n').Append(L.T("Заряд: ", "Charge limit: ", "充电限制:")).Append(protection);
var power = ReadBatteryPowerWatts();
if (power.HasValue)
{
// Это скорость заряда/разряда батареи, а не потребление ноутбука.
text.Append('\n').Append(L.T("Батарея: ", "Battery: ", "电池功率:"));
AppendPower(text, power.Value);
}
if (hasHardwareState)
{
if (hardwareState.CpuTemperature.HasValue || hardwareState.BatteryTemperature.HasValue)
{
text.Append('\n').Append("CPU: ");
AppendTemperature(text, hardwareState.CpuTemperature);
text.Append(L.T("; батарея: ", "; battery: ", ";电池:"));
AppendTemperature(text, hardwareState.BatteryTemperature);
}
if (hardwareState.Fan1Rpm.HasValue || hardwareState.Fan2Rpm.HasValue)
AppendFans(text, hardwareState);
}
if (text.Length > MaxTooltipLength)
text.Length = MaxTooltipLength;
return text.ToString();
}
private static void AppendTemperature(System.Text.StringBuilder text, int? value)
{
if (value.HasValue)
text.Append(value.Value).Append("°C");
else
text.Append('?');
}
/// <summary>
/// Обороты вентиляторов вместе с целевыми, к которым их ведёт прошивка.
/// Если строка с целью не влезает в подсказку, остаются одни фактические:
/// обрезка посреди числа показала бы неверные обороты.
/// </summary>
private static void AppendFans(System.Text.StringBuilder text, HardwareSensorSnapshot state)
{
var start = text.Length;
text.Append('\n').Append(L.T("Вент.: ", "Fans: ", "风扇:"));
var valuesStart = text.Length;
// MagicBook Pro 14 цель не сообщает: байты под неё нулевые и при вращении.
var target1 = state.Fan1TargetRpm is > 0 ? state.Fan1TargetRpm : null;
var target2 = state.Fan2TargetRpm is > 0 ? state.Fan2TargetRpm : null;
if (target1.HasValue || target2.HasValue)
{
AppendFan(text, state.Fan1Rpm, target1);
text.Append(" / ");
AppendFan(text, state.Fan2Rpm, target2);
if (text.Length <= MaxTooltipLength)
return;
text.Length = valuesStart;
}
AppendFan(text, state.Fan1Rpm, null);
text.Append('/');
AppendFan(text, state.Fan2Rpm, null);
text.Append(L.T(" об/мин", " RPM", " 转/分"));
if (text.Length > MaxTooltipLength)
text.Length = start;
}
private static void AppendFan(System.Text.StringBuilder text, int? value, int? target)
{
if (value.HasValue)
text.Append(value.Value);
else
text.Append('?');
if (target.HasValue)
text.Append('→').Append(target.Value);
}
private static void AppendPower(System.Text.StringBuilder text, double watts)
{
if (Math.Abs(watts) < 0.05)
{
// Ограничитель держит батарею в диапазоне, и ток через неё не идёт.
// Голые "0 Вт" читались как неработающий датчик (issue #14). Русский
// вариант без "0 Вт": иначе в производительном режиме строка
// вентиляторов перестаёт влезать в 127 символов.
text.Append(L.T("без тока", "0 W, idle", "0 瓦(未充放电)"));
return;
}
if (watts > 0)
text.Append('+');
text.Append(watts.ToString("0.0")).Append(L.T(" Вт", " W", " 瓦"));
}
// Charge/discharge power in watts: positive while charging, negative while
// discharging. Read from the standard root\wmi BatteryStatus class, which is
// available without administrator rights, and cached briefly so repeated
// tooltip refreshes during a hover don't re-query WMI each time.
private static double? ReadBatteryPowerWatts()
{
// Кэшируется и отсутствие значения: иначе на машине без батареи WMI
// опрашивался бы при каждой перерисовке подсказки.
var cached = Volatile.Read(ref _power);
if (cached is not null && Environment.TickCount64 - cached.Ticks < PowerCacheMilliseconds)
return cached.Watts;
double? result = null;
try
{
var scope = new ManagementScope(@"\\.\root\wmi");
scope.Connect();
using var searcher = new ManagementObjectSearcher(
scope, new ObjectQuery("SELECT ChargeRate, DischargeRate FROM BatteryStatus"));
using var items = searcher.Get();
foreach (ManagementBaseObject item in items)
{
using (item)
{
var charge = Convert.ToInt64(item["ChargeRate"] ?? 0L);
var discharge = Convert.ToInt64(item["DischargeRate"] ?? 0L);
result = (charge - discharge) / 1000.0;
}
break;
}
}
catch (Exception exception)
{
AppLog.Error("Could not read battery power", exception);
}
Volatile.Write(ref _power, new PowerSample(result, Environment.TickCount64));
return result;
}
}