LenovoLegionToolkit
295 строк · 9.3 Кб
1using System;2using System.Collections.Generic;3using System.Diagnostics;4using System.Linq;5using System.Threading;6using System.Threading.Tasks;7using LenovoLegionToolkit.Lib.Extensions;8using LenovoLegionToolkit.Lib.Resources;9using LenovoLegionToolkit.Lib.System;10using LenovoLegionToolkit.Lib.System.Management;11using LenovoLegionToolkit.Lib.Utils;12using NeoSmart.AsyncLock;13
14namespace LenovoLegionToolkit.Lib.Controllers;15
16public class GPUController17{
18private readonly AsyncLock _lock = new();19
20private Task? _refreshTask;21private CancellationTokenSource? _refreshCancellationTokenSource;22
23private GPUState _state = GPUState.Unknown;24private List<Process> _processes = new();25private string? _gpuInstanceId;26private string? _performanceState;27
28public GPUState LastKnownState => _state;29public event EventHandler<GPUStatus>? Refreshed;30
31public bool IsSupported()32{33try34{35NVAPI.Initialize();36return NVAPI.GetGPU() is not null;37}38catch39{40return false;41}42finally43{44try45{46NVAPI.Unload();47}48catch { /* Ignored. */ }49}50}51
52public async Task<GPUStatus> RefreshNowAsync()53{54using (await _lock.LockAsync().ConfigureAwait(false))55{56await RefreshLoopAsync(0, 0, CancellationToken.None).ConfigureAwait(false);57return new GPUStatus(_state, _performanceState, _processes);58}59}60
61public async Task StartAsync(int delay = 1_000, int interval = 5_000)62{63await StopAsync(true).ConfigureAwait(false);64
65if (Log.Instance.IsTraceEnabled)66Log.Instance.Trace($"Starting... [delay={delay}, interval={interval}]");67
68_refreshCancellationTokenSource = new CancellationTokenSource();69var token = _refreshCancellationTokenSource.Token;70_refreshTask = Task.Run(() => RefreshLoopAsync(delay, interval, token), token);71}72
73public async Task StopAsync(bool waitForFinish = false)74{75if (Log.Instance.IsTraceEnabled)76Log.Instance.Trace($"Stopping... [refreshTask.isNull={_refreshTask is null}, _refreshCancellationTokenSource.IsCancellationRequested={_refreshCancellationTokenSource?.IsCancellationRequested}]");77
78_refreshCancellationTokenSource?.Cancel();79
80if (waitForFinish)81{82if (Log.Instance.IsTraceEnabled)83Log.Instance.Trace($"Waiting to finish...");84
85if (_refreshTask is not null)86{87try88{89await _refreshTask.ConfigureAwait(false);90}91catch (OperationCanceledException) { }92}93
94if (Log.Instance.IsTraceEnabled)95Log.Instance.Trace($"Finished");96}97
98_refreshCancellationTokenSource = null;99_refreshTask = null;100
101if (Log.Instance.IsTraceEnabled)102Log.Instance.Trace($"Stopped");103}104
105public async Task RestartGPUAsync()106{107using (await _lock.LockAsync().ConfigureAwait(false))108{109if (Log.Instance.IsTraceEnabled)110Log.Instance.Trace($"Deactivating... [state={_state}, gpuInstanceId={_gpuInstanceId}]");111
112if (_state is not GPUState.Active and not GPUState.Inactive)113return;114
115if (string.IsNullOrEmpty(_gpuInstanceId))116return;117
118await CMD.RunAsync("pnputil", $"/restart-device \"{_gpuInstanceId}\"").ConfigureAwait(false);119
120if (Log.Instance.IsTraceEnabled)121Log.Instance.Trace($"Deactivating... [state= {_state}, gpuInstanceId={_gpuInstanceId}]");122}123}124
125public async Task KillGPUProcessesAsync()126{127using (await _lock.LockAsync().ConfigureAwait(false))128{129if (Log.Instance.IsTraceEnabled)130Log.Instance.Trace($"Deactivating... [state= {_state}, gpuInstanceId={_gpuInstanceId}]");131
132if (_state is not GPUState.Active)133return;134
135if (string.IsNullOrEmpty(_gpuInstanceId))136return;137
138foreach (var process in _processes)139{140try141{142process.Kill(true);143await process.WaitForExitAsync().ConfigureAwait(false);144}145catch (Exception ex)146{147if (Log.Instance.IsTraceEnabled)148Log.Instance.Trace($"Couldn't kill process. [pid={process.Id}, name={process.ProcessName}]", ex);149}150}151
152if (Log.Instance.IsTraceEnabled)153Log.Instance.Trace($"Deactivating... [state= {_state}, gpuInstanceId={_gpuInstanceId}]");154}155}156
157private async Task RefreshLoopAsync(int delay, int interval, CancellationToken token)158{159try160{161if (Log.Instance.IsTraceEnabled)162Log.Instance.Trace($"Initializing NVAPI...");163
164NVAPI.Initialize();165
166if (Log.Instance.IsTraceEnabled)167Log.Instance.Trace($"Initialized NVAPI");168
169await Task.Delay(delay, token).ConfigureAwait(false);170
171while (true)172{173token.ThrowIfCancellationRequested();174
175using (await _lock.LockAsync(token).ConfigureAwait(false))176{177
178if (Log.Instance.IsTraceEnabled)179Log.Instance.Trace($"Will refresh...");180
181await RefreshStateAsync().ConfigureAwait(false);182
183if (Log.Instance.IsTraceEnabled)184Log.Instance.Trace($"Refreshed");185
186Refreshed?.Invoke(this, new GPUStatus(_state, _performanceState, _processes));187}188
189if (interval > 0)190await Task.Delay(interval, token).ConfigureAwait(false);191else192break;193}194}195catch (Exception ex) when (ex is not OperationCanceledException)196{197if (Log.Instance.IsTraceEnabled)198Log.Instance.Trace($"Exception occurred", ex);199
200throw;201}202finally203{204if (Log.Instance.IsTraceEnabled)205Log.Instance.Trace($"Unloading NVAPI...");206
207NVAPI.Unload();208
209if (Log.Instance.IsTraceEnabled)210Log.Instance.Trace($"Unloaded NVAPI");211}212}213
214private async Task RefreshStateAsync()215{216if (Log.Instance.IsTraceEnabled)217Log.Instance.Trace($"Refresh in progress...");218
219_state = GPUState.Unknown;220_processes = new();221_gpuInstanceId = null;222_performanceState = null;223
224var gpu = NVAPI.GetGPU();225if (gpu is null)226{227_state = GPUState.NvidiaGpuNotFound;228
229if (Log.Instance.IsTraceEnabled)230Log.Instance.Trace($"GPU present [state={_state}, processes.Count={_processes.Count}, gpuInstanceId={_gpuInstanceId}]");231
232return;233}234
235try236{237var stateId = gpu.PerformanceStatesInfo.CurrentPerformanceState.StateId.ToString().GetUntilOrEmpty("_");238_performanceState = Resource.GPUController_PoweredOn;239if (!string.IsNullOrWhiteSpace(stateId))240_performanceState += $", {stateId}";241}242catch (Exception ex) when (ex.Message == "NVAPI_GPU_NOT_POWERED")243{244_state = GPUState.PoweredOff;245_performanceState = Resource.GPUController_PoweredOff;246
247if (Log.Instance.IsTraceEnabled)248Log.Instance.Trace($"Powered off [state={_state}, processes.Count={_processes.Count}, gpuInstanceId={_gpuInstanceId}]");249
250return;251}252catch (Exception ex)253{254if (Log.Instance.IsTraceEnabled)255Log.Instance.Trace($"GPU status exception.", ex);256
257_performanceState = "Unknown";258}259
260var pnpDeviceIdPart = NVAPI.GetGPUId(gpu);261
262if (string.IsNullOrEmpty(pnpDeviceIdPart))263throw new InvalidOperationException("pnpDeviceIdPart is null or empty");264
265var gpuInstanceId = await WMI.Win32.PnpEntity.GetDeviceIDAsync(pnpDeviceIdPart).ConfigureAwait(false);266var processNames = NVAPIExtensions.GetActiveProcesses(gpu);267
268if (NVAPI.IsDisplayConnected(gpu))269{270_processes = processNames;271_state = GPUState.MonitorConnected;272
273if (Log.Instance.IsTraceEnabled)274Log.Instance.Trace(275$"Monitor connected [state={_state}, processes.Count={_processes.Count}, gpuInstanceId={_gpuInstanceId}]");276}277else if (processNames.Any())278{279_processes = processNames;280_state = GPUState.Active;281_gpuInstanceId = gpuInstanceId;282
283if (Log.Instance.IsTraceEnabled)284Log.Instance.Trace($"Active [state={_state}, processes.Count={_processes.Count}, gpuInstanceId={_gpuInstanceId}, pnpDeviceIdPart={pnpDeviceIdPart}]");285}286else287{288_state = GPUState.Inactive;289_gpuInstanceId = gpuInstanceId;290
291if (Log.Instance.IsTraceEnabled)292Log.Instance.Trace($"Inactive [state={_state}, processes.Count={_processes.Count}, gpuInstanceId={_gpuInstanceId}]");293}294}295}
296