Repository navigation
Expand file tree
/
Copy pathPowerModeEventService.cs
More file actions
182 lines (160 loc) · 6.97 KB
/
Copy pathPowerModeEventService.cs
File metadata and controls
182 lines (160 loc) · 6.97 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
using System.Management;
namespace HonorPCHelper;
internal sealed class PowerModeEventService : IDisposable
{
// Клавиша микрофона; код совпадает с KEY_MICMUTE из huawei-wmi.
private const uint MicMuteKeyCode = 0x287;
// Клавиша камеры. Код снят с MagicBook Pro 14: huawei-wmi отдаёт его
// как KEY_WLAN, но на этой клавиатуре F8 - именно камера.
private const uint CameraKeyCode = 0x288;
// Клавиша скриншота на F12. Код снят с MagicBook Pro 14. Вместе с событием
// прошивка шлёт скан-код E0_78 без виртуальной клавиши, поэтому ни Windows,
// ни PC Manager назначить на неё действие не могут. Скан-код у моделей
// разный (встречался и E0_77), а событие идёт по общей схеме с F7 и F8.
private const uint ScreenshotKeyCode = 0x28E;
// Клавиша переключает состояние, поэтому лишнее событие обошлось бы дорого.
// На проверенной машине одно нажатие даёт ровно одно событие, но у другой
// прошивки это не гарантировано. Порог подавляющий - отсчёт продлевается
// и погашенным событием, - поэтому пачка любой длины даёт ровно одно
// переключение, по первому событию и без задержки.
private const int HotkeyDebounceMilliseconds = 400;
private readonly Action<bool> _onModeChanged;
private readonly Action<KeyboardBacklightLevel> _onBacklightChanged;
private readonly Action _onMicMuteKey;
private readonly Action _onCameraKey;
private readonly Action _onScreenshotKey;
private readonly Func<bool>? _shouldIgnoreBacklightEvent;
private ManagementEventWatcher? _watcher;
private long _lastEventTime;
// Отрицательные значения, чтобы вскоре после загрузки системы, когда
// TickCount64 ещё мал, первое нажатие не попадало под собственный порог.
private long _lastMicMuteKeyTime = -HotkeyDebounceMilliseconds;
private long _lastCameraKeyTime = -HotkeyDebounceMilliseconds;
private long _lastScreenshotKeyTime = -HotkeyDebounceMilliseconds;
private volatile bool _currentState = HardwareSettings.PerformanceModeActive;
internal PowerModeEventService(
Action<bool> onModeChanged,
Action<KeyboardBacklightLevel> onBacklightChanged,
Action onMicMuteKey,
Action onCameraKey,
Action onScreenshotKey,
Func<bool>? shouldIgnoreBacklightEvent = null)
{
_onModeChanged = onModeChanged;
_onBacklightChanged = onBacklightChanged;
_onMicMuteKey = onMicMuteKey;
_onCameraKey = onCameraKey;
_onScreenshotKey = onScreenshotKey;
_shouldIgnoreBacklightEvent = shouldIgnoreBacklightEvent;
}
internal void Start()
{
var scope = new ManagementScope(@"\\.\root\wmi");
scope.Connect();
_watcher = new ManagementEventWatcher(scope, new WqlEventQuery("SELECT * FROM OemWMIEvent"));
_watcher.EventArrived += OnEventArrived;
_watcher.Start();
}
public void Dispose()
{
if (_watcher is null)
return;
_watcher.EventArrived -= OnEventArrived;
try
{
_watcher.Stop();
}
catch (ManagementException)
{
}
_watcher.Dispose();
_watcher = null;
}
private void OnEventArrived(object sender, EventArrivedEventArgs eventArgs)
{
try
{
ProcessEvent(eventArgs);
}
catch (Exception exception)
{
AppLog.Error("HONOR WMI event processing failed", exception);
}
}
private void ProcessEvent(EventArrivedEventArgs eventArgs)
{
var value = eventArgs.NewEvent.Properties["Force"]?.Value;
if (value is null)
return;
var code = Convert.ToUInt32(value) & 0xFFFF;
if (code == MicMuteKeyCode)
{
if (Accept(ref _lastMicMuteKeyTime))
_onMicMuteKey();
return;
}
if (code == CameraKeyCode)
{
if (Accept(ref _lastCameraKeyTime))
_onCameraKey();
return;
}
if (code == ScreenshotKeyCode)
{
if (Accept(ref _lastScreenshotKeyTime))
_onScreenshotKey();
return;
}
if (code is 0x2B1 or 0x2B2 or 0x2B3)
{
// Right after wake the firmware re-initializes the backlight and emits
// a spurious level event; ignoring it prevents a false manual override.
if (_shouldIgnoreBacklightEvent?.Invoke() == true)
return;
var level = code switch
{
0x2B1 => KeyboardBacklightLevel.Off,
0x2B2 => KeyboardBacklightLevel.Low,
_ => KeyboardBacklightLevel.High
};
HardwareSettings.KeyboardBacklight = level;
_onBacklightChanged(level);
return;
}
if (code == 0x2A8)
{
// HUNTER на игровых моделях (issue #7). Событие сообщает итоговый
// режим, а не нажатие, поэтому повтор безвреден и порог не нужен.
_currentState = false;
HardwareSettings.HunterModeActive = true;
HardwareSettings.PerformanceModeActive = false;
_onModeChanged(false);
return;
}
if (code is not (0x2A0 or 0x2A1 or 0x2A6))
return;
var now = Environment.TickCount64;
if (now - Interlocked.Read(ref _lastEventTime) < 500)
return;
Interlocked.Exchange(ref _lastEventTime, now);
_currentState = code switch
{
0x2A0 => false,
0x2A1 => true,
_ => !_currentState
};
HardwareSettings.HunterModeActive = false;
HardwareSettings.PerformanceModeActive = _currentState;
_onModeChanged(_currentState);
}
/// <summary>
/// Пропускает первое событие пачки и гасит остальные. Отсчёт сдвигается
/// и погашенным событием: пока прошивка повторяет нажатие, порог не истекает.
/// </summary>
private static bool Accept(ref long lastTime)
{
var now = Environment.TickCount64;
var previous = Interlocked.Exchange(ref lastTime, now);
return now - previous >= HotkeyDebounceMilliseconds;
}
}