diff --git a/ColorControl.UI/Blazor.cs b/ColorControl.UI/Blazor.cs index 537736c..db7b437 100644 --- a/ColorControl.UI/Blazor.cs +++ b/ColorControl.UI/Blazor.cs @@ -5,39 +5,39 @@ namespace ColorControl.UI; public static class Blazor { - public static void Start(Config? config = null) - { - var builder = WebApplication.CreateBuilder(); + public static void Start(Config? config = null) + { + var builder = WebApplication.CreateBuilder(); - // Add services to the container. - builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); + // Add services to the container. + builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); - builder.Services.AddSingleton(new AppState { SelectedTheme = (config?.UseDarkMode ?? true) ? "dark" : "light" }); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddHttpContextAccessor(); - builder.Services.AddSingleton(); - builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(config?.UiPort > 0 ? config.UiPort : 5000)); + builder.Services.AddSingleton(new AppState { SelectedTheme = (config?.UseDarkMode ?? true) ? "dark" : "light" }); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddHttpContextAccessor(); + builder.Services.AddSingleton(); + builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(config?.UiPort > 0 ? config.UiPort : 5000)); - var app = builder.Build(); + var app = builder.Build(); - // Configure the HTTP request pipeline. - if (!app.Environment.IsDevelopment()) - { - app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } + // Configure the HTTP request pipeline. + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseStaticFiles(); - app.UseAntiforgery(); + app.UseStaticFiles(); + app.UseAntiforgery(); - app.MapRazorComponents() - .AddInteractiveServerRenderMode(); + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); - app.Run(); - } + app.Run(); + } } diff --git a/ColorControl.UI/ColorControl.UI.csproj b/ColorControl.UI/ColorControl.UI.csproj index 33cdc83..a06ae38 100644 --- a/ColorControl.UI/ColorControl.UI.csproj +++ b/ColorControl.UI/ColorControl.UI.csproj @@ -41,6 +41,9 @@ true + + true + true diff --git a/ColorControl.UI/Components/Layout/NavMenu.razor b/ColorControl.UI/Components/Layout/NavMenu.razor index dbcb094..8b5df25 100644 --- a/ColorControl.UI/Components/Layout/NavMenu.razor +++ b/ColorControl.UI/Components/Layout/NavMenu.razor @@ -1,8 +1,7 @@ @using ColorControl.Shared.Contracts @using ColorControl.UI.Services; -@using ColorControl.UI.Services -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState diff --git a/ColorControl.UI/Components/Layout/TopMenu.razor b/ColorControl.UI/Components/Layout/TopMenu.razor index 193e9af..2e0b4af 100644 --- a/ColorControl.UI/Components/Layout/TopMenu.razor +++ b/ColorControl.UI/Components/Layout/TopMenu.razor @@ -4,7 +4,7 @@ @inject AppState AppState @inject NavigationManager _navigationManager; -@rendermode InteractiveServer +@rendermode Constants.RenderMode
@* *@ @@ -15,7 +15,7 @@ private string ThemeTransitionName = "Dark"; - protected override async Task OnInitializedAsync() + protected override void OnInitialized() { ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme"; } diff --git a/ColorControl.UI/Components/Pages/AMD/AmdPresetPage.razor b/ColorControl.UI/Components/Pages/AMD/AmdPresetPage.razor index f4b1e14..a716efc 100644 --- a/ColorControl.UI/Components/Pages/AMD/AmdPresetPage.razor +++ b/ColorControl.UI/Components/Pages/AMD/AmdPresetPage.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Services -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService diff --git a/ColorControl.UI/Components/Pages/AMD/AmdSettings.razor b/ColorControl.UI/Components/Pages/AMD/AmdSettings.razor index 7321eff..c05fcad 100644 --- a/ColorControl.UI/Components/Pages/AMD/AmdSettings.razor +++ b/ColorControl.UI/Components/Pages/AMD/AmdSettings.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService - - -@*
-
-
- - @display.DisplayName @@ @display.AdapterName - -
-
- @display.DevicePath - EDID? @display.DisplayColors.Any(dc => dc.DisplayPrimariesSource == MHC2Gen.DisplayPrimariesSource.EDID) -
-
-
- *@ } + }
} @@ -70,7 +55,7 @@ else else { } @@ -149,7 +146,7 @@ else - + @@ -159,7 +156,7 @@ else - + } @@ -120,18 +120,18 @@ else await _rpcClientService.CallAsync("GameService", "ApplyPresetWithId", preset.id); } - private async Task PresetOnEditClick(GamePreset preset) + private void PresetOnEditClick(GamePreset preset) { CurrentPreset = preset; } - private async Task PresetOnCloneClick(GamePreset preset) + private void PresetOnCloneClick(GamePreset preset) { CurrentPreset = new GamePreset(preset); CurrentPreset.SetCloneValues(); } - private async Task PresetOnDeleteClick(GamePreset preset) + private void PresetOnDeleteClick(GamePreset preset) { CurrentPreset = preset; } @@ -148,7 +148,7 @@ else StateHasChanged(); } - private async Task AddPresetOnClick() + private void AddPresetOnClick() { CurrentPreset = new GamePreset(); } diff --git a/ColorControl.UI/Components/Pages/Generic/ConfirmModal.razor b/ColorControl.UI/Components/Pages/Generic/ConfirmModal.razor index 61496e1..220387f 100644 --- a/ColorControl.UI/Components/Pages/Generic/ConfirmModal.razor +++ b/ColorControl.UI/Components/Pages/Generic/ConfirmModal.razor @@ -2,7 +2,7 @@ @using ColorControl.UI.Services; @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @@ -19,13 +19,21 @@ [Parameter] public string ModalId { get; set; } = "confirmModal"; + [Parameter] + public List? ExtraFields { get; set; } + public List? Fields { get; set; } - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (Message != null) { Fields = [new FieldDefinition { FieldType = FieldType.Label, Label = Message }]; + + if (ExtraFields != null) + { + Fields.AddRange(ExtraFields); + } } } diff --git a/ColorControl.UI/Components/Pages/Generic/FieldDefs.razor b/ColorControl.UI/Components/Pages/Generic/FieldDefs.razor index 7330bd8..48a85b5 100644 --- a/ColorControl.UI/Components/Pages/Generic/FieldDefs.razor +++ b/ColorControl.UI/Components/Pages/Generic/FieldDefs.razor @@ -2,7 +2,7 @@ @using ColorControl.UI.Services; @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject NavigationManager _navigationManager; diff --git a/ColorControl.UI/Components/Pages/Generic/FieldsModal.razor b/ColorControl.UI/Components/Pages/Generic/FieldsModal.razor index 432b90d..c788ed3 100644 --- a/ColorControl.UI/Components/Pages/Generic/FieldsModal.razor +++ b/ColorControl.UI/Components/Pages/Generic/FieldsModal.razor @@ -2,7 +2,7 @@ @using ColorControl.UI.Services; @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject NavigationManager _navigationManager; @@ -120,7 +120,7 @@ } } - private async Task OnClose(MouseEventArgs e) + private void OnClose(MouseEventArgs e) { IsVisible = false; } diff --git a/ColorControl.UI/Components/Pages/Generic/NotificationsComponent.razor b/ColorControl.UI/Components/Pages/Generic/NotificationsComponent.razor index 67f2995..5bc2e08 100644 --- a/ColorControl.UI/Components/Pages/Generic/NotificationsComponent.razor +++ b/ColorControl.UI/Components/Pages/Generic/NotificationsComponent.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Services -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject JSHelper jsHelper; @inject NotificationService notificationService; @@ -13,7 +13,7 @@ @if (Notifications?.Any() == true) {
-
+
@foreach (var notification in Notifications) { @@ -48,7 +48,7 @@ @code { private List Notifications { get; } = []; - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnAfterRender(bool firstRender) { if (firstRender) { @@ -77,7 +77,7 @@ notification.ShownAt = DateTime.UtcNow; } - private async Task NotificationOnClick(NotificationDto notification) + private void NotificationOnClick(NotificationDto notification) { if (notification.InternalUrl != null) { diff --git a/ColorControl.UI/Components/Pages/Generic/ShortcutInput.razor b/ColorControl.UI/Components/Pages/Generic/ShortcutInput.razor index 7ae275d..22b9757 100644 --- a/ColorControl.UI/Components/Pages/Generic/ShortcutInput.razor +++ b/ColorControl.UI/Components/Pages/Generic/ShortcutInput.razor @@ -3,7 +3,7 @@ @using ColorControl.UI.Services; @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @if (Label != null) { diff --git a/ColorControl.UI/Components/Pages/Generic/StepsInput.razor b/ColorControl.UI/Components/Pages/Generic/StepsInput.razor index 01c2439..3cee702 100644 --- a/ColorControl.UI/Components/Pages/Generic/StepsInput.razor +++ b/ColorControl.UI/Components/Pages/Generic/StepsInput.razor @@ -222,7 +222,7 @@ private List? ActionFields; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (Steps != null) { @@ -369,7 +369,7 @@ await EmitSteps(); } - private async Task RawTextOnChange(ChangeEventArgs e) + private void RawTextOnChange(ChangeEventArgs e) { RawText = e.Value?.ToString(); } diff --git a/ColorControl.UI/Components/Pages/Generic/TriggerInput.razor b/ColorControl.UI/Components/Pages/Generic/TriggerInput.razor index 1f27951..98b5604 100644 --- a/ColorControl.UI/Components/Pages/Generic/TriggerInput.razor +++ b/ColorControl.UI/Components/Pages/Generic/TriggerInput.razor @@ -3,7 +3,7 @@ @using ColorControl.UI.Services; @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @if (Triggers != null) { diff --git a/ColorControl.UI/Components/Pages/Home.razor b/ColorControl.UI/Components/Pages/Home.razor index d42bdb3..c62a137 100644 --- a/ColorControl.UI/Components/Pages/Home.razor +++ b/ColorControl.UI/Components/Pages/Home.razor @@ -6,7 +6,7 @@ @using ColorControl.UI.Generics @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState diff --git a/ColorControl.UI/Components/Pages/Info.razor b/ColorControl.UI/Components/Pages/Info.razor index 8d84c4e..4b86513 100644 --- a/ColorControl.UI/Components/Pages/Info.razor +++ b/ColorControl.UI/Components/Pages/Info.razor @@ -3,7 +3,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Services; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState diff --git a/ColorControl.UI/Components/Pages/LG/LgDevicePage.razor b/ColorControl.UI/Components/Pages/LG/LgDevicePage.razor index 2fdb1bd..b22a67a 100644 --- a/ColorControl.UI/Components/Pages/LG/LgDevicePage.razor +++ b/ColorControl.UI/Components/Pages/LG/LgDevicePage.razor @@ -4,7 +4,7 @@ @using NvAPIWrapper.Native.Display @using ColorControl.Shared.Common -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -143,6 +143,14 @@
+
+ + +
+
+ + +
@@ -177,7 +185,7 @@ private bool? IsTestConnectionSuccessful; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (_device != Device) { diff --git a/ColorControl.UI/Components/Pages/LG/LgExecuteActionModal.razor b/ColorControl.UI/Components/Pages/LG/LgExecuteActionModal.razor index 052dcc3..223e0ea 100644 --- a/ColorControl.UI/Components/Pages/LG/LgExecuteActionModal.razor +++ b/ColorControl.UI/Components/Pages/LG/LgExecuteActionModal.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @@ -28,7 +28,7 @@ private FieldDefinition? Field; private FieldDefinition? DeviceField; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (Action == null) { diff --git a/ColorControl.UI/Components/Pages/LG/LgPresetPage.razor b/ColorControl.UI/Components/Pages/LG/LgPresetPage.razor index 0de10a7..919b3dd 100644 --- a/ColorControl.UI/Components/Pages/LG/LgPresetPage.razor +++ b/ColorControl.UI/Components/Pages/LG/LgPresetPage.razor @@ -6,7 +6,7 @@ @using ColorControl.Shared.Common @using static ColorControl.UI.Components.Pages.Generic.StepsInput -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -114,7 +114,7 @@ private string WasValidated = ""; private bool IsVisible; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (PresetParam == null) { diff --git a/ColorControl.UI/Components/Pages/LG/LgSettings.razor b/ColorControl.UI/Components/Pages/LG/LgSettings.razor index 1e10871..3b7afdd 100644 --- a/ColorControl.UI/Components/Pages/LG/LgSettings.razor +++ b/ColorControl.UI/Components/Pages/LG/LgSettings.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService diff --git a/ColorControl.UI/Components/Pages/LG/LgSummary.razor b/ColorControl.UI/Components/Pages/LG/LgSummary.razor index 026d5eb..584b076 100644 --- a/ColorControl.UI/Components/Pages/LG/LgSummary.razor +++ b/ColorControl.UI/Components/Pages/LG/LgSummary.razor @@ -9,7 +9,7 @@ @using ColorControl.UI.Generics @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject ProtectedLocalStorage _localStorage @@ -162,7 +162,7 @@ else
@@ -248,18 +248,18 @@ else await _rpcClientService.CallAsync("LgService", "ApplyPresetWithId", preset.id); } - private async Task PresetOnEditClick(LgPreset preset) + private void PresetOnEditClick(LgPreset preset) { CurrentPreset = preset; } - private async Task PresetOnCloneClick(LgPreset preset) + private void PresetOnCloneClick(LgPreset preset) { CurrentPreset = new LgPreset(preset); CurrentPreset.SetCloneValues(); } - private async Task PresetOnDeleteClick(LgPreset preset) + private void PresetOnDeleteClick(LgPreset preset) { CurrentPreset = preset; } @@ -276,17 +276,17 @@ else StateHasChanged(); } - private async Task DeviceOnClick(LgDeviceDto device) + private void DeviceOnClick(LgDeviceDto device) { CurrentDevice = device; } - private async Task AddDeviceOnClick() + private void AddDeviceOnClick() { CurrentDevice = new LgDeviceDto { IsCustom = true }; } - private async Task AddPresetOnClick() + private void AddPresetOnClick() { CurrentPreset = new LgPreset(); } @@ -320,7 +320,7 @@ else StateHasChanged(); } - private async Task ActionOnclick(InvokableActionDto action) + private void ActionOnclick(InvokableActionDto action) { CurrentInvokableAction = action; } diff --git a/ColorControl.UI/Components/Pages/Logging.razor b/ColorControl.UI/Components/Pages/Logging.razor index d618dc6..819891f 100644 --- a/ColorControl.UI/Components/Pages/Logging.razor +++ b/ColorControl.UI/Components/Pages/Logging.razor @@ -3,7 +3,7 @@ @using ColorControl.Shared.Contracts @using ColorControl.UI.Services; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -147,6 +147,7 @@ else "WARN" => "warning", "ERROR" => "danger", "FATAL" => "secondary", + _ => "info" }; public bool Show(NLog.LogLevel level) => LogLevel >= level; @@ -200,7 +201,7 @@ else await _rpcClientService.CallAsync("LoggingService", "SetLogLevel", value); } - private async Task LogLevelFilterInput(ChangeEventArgs e) + private void LogLevelFilterInput(ChangeEventArgs e) { var value = e.Value as string ?? NLog.LogLevel.Debug.Name; logLevelFilter = NLog.LogLevel.FromString(value); diff --git a/ColorControl.UI/Components/Pages/Nvidia/NvidiaDithering.razor b/ColorControl.UI/Components/Pages/Nvidia/NvidiaDithering.razor index 9e8313a..79bf337 100644 --- a/ColorControl.UI/Components/Pages/Nvidia/NvidiaDithering.razor +++ b/ColorControl.UI/Components/Pages/Nvidia/NvidiaDithering.razor @@ -4,7 +4,7 @@ @using NvAPIWrapper.Native.Display @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState diff --git a/ColorControl.UI/Components/Pages/Nvidia/NvidiaDriverSettings.razor b/ColorControl.UI/Components/Pages/Nvidia/NvidiaDriverSettings.razor index a2b3496..8b54f41 100644 --- a/ColorControl.UI/Components/Pages/Nvidia/NvidiaDriverSettings.razor +++ b/ColorControl.UI/Components/Pages/Nvidia/NvidiaDriverSettings.razor @@ -4,11 +4,10 @@ @using NvAPIWrapper.Native.Display @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService -@inject AppState AppState -@inject NavigationManager _navigationManager; +@inject JSHelper jsHelper; @@ -215,6 +214,9 @@ { Preset.UpdateDriverSettings(changedSettings); + await jsHelper.CloseModal("driverSettingsModal"); + await jsHelper.OpenModal("presetModal"); + return; } diff --git a/ColorControl.UI/Components/Pages/Nvidia/NvidiaGpuSettings.razor b/ColorControl.UI/Components/Pages/Nvidia/NvidiaGpuSettings.razor new file mode 100644 index 0000000..c6f8bcc --- /dev/null +++ b/ColorControl.UI/Components/Pages/Nvidia/NvidiaGpuSettings.razor @@ -0,0 +1,195 @@ +@using ColorControl.Shared.Contracts +@using ColorControl.Shared.Contracts.NVIDIA +@using ColorControl.UI.Services; +@using NvAPIWrapper.Native.Display +@using ColorControl.Shared.Common; + +@rendermode Constants.RenderMode + +@inject RpcUiClientService _rpcClientService +@inject JSHelper jsHelper; + + + +@code { + + [Parameter] + public NvGpuOcSettings? OverclockSettings { get; set; } + + [Parameter] + public NvGpuInfoDto? GpuInfo { get; set; } + + private NvPreset? _preset; + + [Parameter] + public NvPreset? Preset { get; set; } + + [Parameter] + public Func? AfterApply { get; set; } + + private List? GpuInfos; + + private bool IsVisible { get; set; } + + protected override async Task OnParametersSetAsync() + { + if (_preset != Preset) + { + _preset = Preset; + } + + if (GpuInfo == null) + { + GpuInfos = await _rpcClientService.CallAsync>("NvService", "GetGpuInfos"); + GpuInfo = GpuInfos?.FirstOrDefault(); + } + + if (Preset != null) + { + OverclockSettings = Preset.ocSettings.FirstOrDefault(); + } + + if (GpuInfo != null && OverclockSettings == null) + { + OverclockSettings = GpuInfo.OverclockSettings; + } + } + + private async Task ApplyClick(MouseEventArgs e) + { + if (OverclockSettings == null) + { + return; + } + + if (Preset != null) + { + Preset.UpdateOverclockingSetting(OverclockSettings); + + await jsHelper.CloseModal("gpuSettingsModal"); + await jsHelper.OpenModal("presetModal"); + + return; + } + + var result = await _rpcClientService.CallAsync("NvService", "ApplyOverclocking", new List { OverclockSettings }); + + if (AfterApply != null && GpuInfo != null) + { + await AfterApply.Invoke(GpuInfo); + } + } + + private void OnFocus(FocusEventArgs e) + { + if (!IsVisible) + { + IsVisible = true; + } + } +} \ No newline at end of file diff --git a/ColorControl.UI/Components/Pages/Nvidia/NvidiaPreset.razor b/ColorControl.UI/Components/Pages/Nvidia/NvidiaPreset.razor index 4613886..b0beff5 100644 --- a/ColorControl.UI/Components/Pages/Nvidia/NvidiaPreset.razor +++ b/ColorControl.UI/Components/Pages/Nvidia/NvidiaPreset.razor @@ -1,11 +1,12 @@ @using ColorControl.Shared.Contracts +@using ColorControl.Shared.Contracts.DisplayInfo @using ColorControl.Shared.Contracts.NVIDIA @using ColorControl.UI.Services; @using ColorControl.UI.Components.Pages.Generic @using NvAPIWrapper.Native.Display @using ColorControl.Shared.Common; -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -281,7 +282,7 @@
- + @Preset.driverSettings.Count settings configured
@@ -460,7 +461,7 @@
} - @if (ColorProfileNames?.Any() == true) + @if (ColorProfiles?.Any() == true) {

@@ -480,9 +481,9 @@

@@ -501,6 +502,42 @@
} + @if (ShowOverclocking) + { +
+

+ + @if (!Preset.IsDisplayPreset) + { + + } + + +

+
+
+
+ + @if (Preset.ocSettings.Any()) + { + Overclocking settings configured: + @foreach (var ocSetting in Preset.ocSettings) + { +
@ocSetting.ToString()
+ } + } + else + { + No overclocking settings configured + } +
+
+
+
+ } +
} @@ -525,6 +562,9 @@ [Parameter] public Func? SetDriverSettingsPreset { get; set; } + [Parameter] + public Func? SetGpuSettingsPreset { get; set; } + private string? selectedActiveResolution; private string? selectedVirtualResolution; private string? selectedRefreshRate; @@ -532,15 +572,17 @@ private List? resolutions; private List? refreshRates; private List? displayPresets; - private List? ColorProfileNames; + private List? ColorProfiles; private string WasValidated = ""; private bool IsVisible; private bool IsDriverSettingsOpened; + private bool IsGpuSettingsOpened; + private bool ShowOverclocking; protected override async Task OnParametersSetAsync() { - if (IsDriverSettingsOpened && Preset != null) + if ((IsDriverSettingsOpened || IsGpuSettingsOpened) && Preset != null) { return; } @@ -564,8 +606,10 @@ if (Preset != null) { - ColorProfileNames = await _rpcClientService.CallAsync>("ColorProfileService", "GetDisplayColorProfiles", deviceName); + ColorProfiles = await _rpcClientService.CallAsync>("ColorProfileService", "GetAllDisplayColorProfiles", deviceName); } + + ShowOverclocking = true; } protected override void OnAfterRender(bool firstRender) @@ -641,10 +685,20 @@ Preset!.applyHdmiSettings = true; } - private async Task DriverSettingsOnClick(MouseEventArgs e) + private Task DriverSettingsOnClick(MouseEventArgs e) { IsDriverSettingsOpened = true; SetDriverSettingsPreset?.Invoke(Preset!); + + return Task.CompletedTask; + } + + private Task GpuSettingsOnClick(MouseEventArgs e) + { + IsGpuSettingsOpened = true; + SetGpuSettingsPreset?.Invoke(Preset!); + + return Task.CompletedTask; } private void ModalOnFocus(FocusEventArgs e) @@ -654,6 +708,11 @@ IsDriverSettingsOpened = false; StateHasChanged(); } + if (IsVisible && IsGpuSettingsOpened) + { + IsGpuSettingsOpened = false; + StateHasChanged(); + } if (!IsVisible) { diff --git a/ColorControl.UI/Components/Pages/Nvidia/NvidiaSettings.razor b/ColorControl.UI/Components/Pages/Nvidia/NvidiaSettings.razor index f39a2d9..f9a50ab 100644 --- a/ColorControl.UI/Components/Pages/Nvidia/NvidiaSettings.razor +++ b/ColorControl.UI/Components/Pages/Nvidia/NvidiaSettings.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @@ -241,15 +251,19 @@ else private List? Presets; private List? DisplayPresets; private List? GpuInfos; + private NvServiceConfig? NvConfig; private NvPreset? CurrentPreset; private NvPreset? CurrentDriverSettingsPreset; + private NvPreset? CurrentGpuSettingsPreset; + private NvGpuInfoDto? CurrentGpuInfo; private bool DitheringVisible; private PresetOrder PresetOrder; protected override async Task OnInitializedAsync() { + NvConfig = await _rpcClientService.CallAsync("NvService", "GetConfig"); await RefreshPresets(); DisplayPresets = await _rpcClientService.CallAsync>("NvService", "GetDisplayPresets"); GpuInfos = await _rpcClientService.CallAsync>("NvService", "GetGpuInfos"); @@ -271,19 +285,19 @@ else await _rpcClientService.CallAsync("NvService", "ApplyPresetWithId", preset.id); } - private async Task PresetOnEditClick(NvPreset preset) + private void PresetOnEditClick(NvPreset preset) { CurrentPreset = preset; } - private async Task PresetOnCloneClick(NvPreset preset) + private void PresetOnCloneClick(NvPreset preset) { CurrentPreset = new NvPreset(preset); CurrentPreset.name = preset.name; CurrentPreset.SetCloneValues(); } - private async Task PresetOnDeleteClick(NvPreset preset) + private void PresetOnDeleteClick(NvPreset preset) { CurrentPreset = preset; } @@ -300,12 +314,12 @@ else StateHasChanged(); } - private async Task DisplayPresetOnClick(NvPreset preset) + private void DisplayPresetOnClick(NvPreset preset) { CurrentPreset = preset; } - private async Task AddPresetOnClick() + private void AddPresetOnClick() { CurrentPreset = new NvPreset(); } @@ -323,10 +337,26 @@ else StateHasChanged(); } - private async Task SetDriverSettingsPreset(NvPreset preset) + private Task SetDriverSettingsPreset(NvPreset preset) { CurrentDriverSettingsPreset = preset; StateHasChanged(); + + return Task.CompletedTask; + } + + private Task SetGpuSettingsPreset(NvPreset preset) + { + CurrentGpuSettingsPreset = preset; + StateHasChanged(); + + return Task.CompletedTask; + } + + private void GpuSettingsOnClick(NvGpuInfoDto gpuInfo) + { + CurrentGpuInfo = gpuInfo; + CurrentGpuSettingsPreset = null; } private async Task PresetOrderChanged() diff --git a/ColorControl.UI/Components/Pages/Options.razor b/ColorControl.UI/Components/Pages/Options.razor index c502ea0..fbde3ad 100644 --- a/ColorControl.UI/Components/Pages/Options.razor +++ b/ColorControl.UI/Components/Pages/Options.razor @@ -5,7 +5,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState diff --git a/ColorControl.UI/Components/Pages/Samsung/SamsungDevicePage.razor b/ColorControl.UI/Components/Pages/Samsung/SamsungDevicePage.razor index 20eedc1..d26115f 100644 --- a/ColorControl.UI/Components/Pages/Samsung/SamsungDevicePage.razor +++ b/ColorControl.UI/Components/Pages/Samsung/SamsungDevicePage.razor @@ -4,7 +4,7 @@ @using NvAPIWrapper.Native.Display @using ColorControl.Shared.Common -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -143,6 +143,14 @@ +
+ + +
+
+ + +
@@ -179,7 +187,7 @@ private string WasValidated = ""; private bool IsVisible; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (_device != Device) { diff --git a/ColorControl.UI/Components/Pages/Samsung/SamsungPresetPage.razor b/ColorControl.UI/Components/Pages/Samsung/SamsungPresetPage.razor index bcca408..d28442c 100644 --- a/ColorControl.UI/Components/Pages/Samsung/SamsungPresetPage.razor +++ b/ColorControl.UI/Components/Pages/Samsung/SamsungPresetPage.razor @@ -6,7 +6,7 @@ @using ColorControl.Shared.Common @using static ColorControl.UI.Components.Pages.Generic.StepsInput -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject AppState AppState @@ -114,7 +114,7 @@ private string WasValidated = ""; private bool IsVisible; - protected override async Task OnParametersSetAsync() + protected override void OnParametersSet() { if (PresetParam == null) { diff --git a/ColorControl.UI/Components/Pages/Samsung/SamsungServiceMenu.razor b/ColorControl.UI/Components/Pages/Samsung/SamsungServiceMenu.razor index 97cb6ec..3ba85b4 100644 --- a/ColorControl.UI/Components/Pages/Samsung/SamsungServiceMenu.razor +++ b/ColorControl.UI/Components/Pages/Samsung/SamsungServiceMenu.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService ? NextAction; private Func? SecondaryAction; - protected override async Task OnInitializedAsync() + protected override void OnInitialized() { // NextAction = FirstStepExit; // Title = "Access Service Menu"; @@ -31,7 +31,7 @@ // Fields = [AddField("Service Menu Access Type", ServiceMenuType.FactoryMenu, subLabel: "Both types will not reset your settings. 'FactoryMenu' is recommended for 2024 series but will also work for older ones. Click 'Next >' to continue or 'X' to stop")]; } - protected async Task OnShow() + protected Task OnShow() { NextAction = FirstStepExit; Title = "Access Service Menu"; @@ -50,6 +50,8 @@ } StateHasChanged(); + + return Task.CompletedTask; } private async Task NextClick() @@ -98,12 +100,16 @@ ConfirmCaption = "Next >"; SecondaryCaption = "Skip"; SecondaryAction = FactoryMenuSecondStepEnterWithoutExecute; - Fields = [AddField("Service Menu - Advanced", null, FieldType.Label, "If the service menu opened and you want to access the Advanced settings, click 'Next >' or click 'Skip' to skip")]; + Fields = + [ + AddField("Service Menu - Advanced", null, FieldType.Label, "If the service menu opened and you want to access the Advanced settings, click 'Next >' or click 'Skip' to skip"), + AddField("Advanced Unlock Type", AdvancedUnlockType.TV, subLabel: "Select the desired unlock type for the Advanced section. If the target is a TV choose TV, otherwise choose Monitor") + ]; StateHasChanged(); } - protected async Task FactoryMenuSecondStepEnterWithoutExecute() + protected Task FactoryMenuSecondStepEnterWithoutExecute() { NextAction = FactoryMenuSecondStepExit; Title = "Exit Service Menu"; @@ -113,11 +119,27 @@ Fields = [AddField("Exit Service Menu Type", ExitServiceMenuType.Exit, subLabel: "Choose how to exit the service menu. You can either reboot the TV or just exit the service menu (if, for example, no changes have been made)")]; StateHasChanged(); + + return Task.CompletedTask; } protected async Task FactoryMenuSecondStepEnter() { - await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvanced); + if (Fields == null) + { + return; + } + + var unlockType = Fields.First(f => f.ValueType == typeof(AdvancedUnlockType)).ValueAsEnum(); + + if (unlockType == AdvancedUnlockType.TV) + { + await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvanced); + } + else + { + await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvancedMonitor); + } await FactoryMenuSecondStepEnterWithoutExecute(); } diff --git a/ColorControl.UI/Components/Pages/Samsung/SamsungSettings.razor b/ColorControl.UI/Components/Pages/Samsung/SamsungSettings.razor index 0d805e4..c33fe2b 100644 --- a/ColorControl.UI/Components/Pages/Samsung/SamsungSettings.razor +++ b/ColorControl.UI/Components/Pages/Samsung/SamsungSettings.razor @@ -4,7 +4,7 @@ @using ColorControl.Shared.Common; @using ColorControl.UI.Components.Pages.Generic -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService diff --git a/ColorControl.UI/Components/Pages/Samsung/SamsungSummary.razor b/ColorControl.UI/Components/Pages/Samsung/SamsungSummary.razor index 5ee9ab7..b1b6b2e 100644 --- a/ColorControl.UI/Components/Pages/Samsung/SamsungSummary.razor +++ b/ColorControl.UI/Components/Pages/Samsung/SamsungSummary.razor @@ -9,7 +9,7 @@ @using ColorControl.UI.Generics @using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage -@rendermode InteractiveServer +@rendermode Constants.RenderMode @inject RpcUiClientService _rpcClientService @inject ProtectedLocalStorage _localStorage @@ -160,7 +160,7 @@ else
@@ -229,18 +229,18 @@ else await _rpcClientService.CallAsync("SamsungService", "ApplyPresetWithId", preset.id); } - private async Task PresetOnEditClick(SamsungPreset preset) + private void PresetOnEditClick(SamsungPreset preset) { CurrentPreset = preset; } - private async Task PresetOnCloneClick(SamsungPreset preset) + private void PresetOnCloneClick(SamsungPreset preset) { CurrentPreset = new SamsungPreset(preset); CurrentPreset.SetCloneValues(); } - private async Task PresetOnDeleteClick(SamsungPreset preset) + private void PresetOnDeleteClick(SamsungPreset preset) { CurrentPreset = preset; } @@ -257,17 +257,17 @@ else StateHasChanged(); } - private async Task DeviceOnClick(SamsungDeviceDto device) + private void DeviceOnClick(SamsungDeviceDto device) { CurrentDevice = device; } - private async Task AddDeviceOnClick() + private void AddDeviceOnClick() { CurrentDevice = new SamsungDeviceDto { IsCustom = true }; } - private async Task AddPresetOnClick() + private void AddPresetOnClick() { CurrentPreset = new SamsungPreset(); } diff --git a/ColorControl.UI/Constants.cs b/ColorControl.UI/Constants.cs index 2e503b4..0d3ab0c 100644 --- a/ColorControl.UI/Constants.cs +++ b/ColorControl.UI/Constants.cs @@ -1,11 +1,18 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + public static class Constants { - public const string Primary = "primary"; - public const string Secondary = "secondary"; - public const string Success = "success"; - public const string Danger = "danger"; - public const string Warning = "warning"; - public const string Info = "info"; - public const string Light = "light"; - public const string Dark = "dark"; + public const string Primary = "primary"; + public const string Secondary = "secondary"; + public const string Success = "success"; + public const string Danger = "danger"; + public const string Warning = "warning"; + public const string Info = "info"; + public const string Light = "light"; + public const string Dark = "dark"; + + + public static IComponentRenderMode RenderMode = new InteractiveServerRenderMode(prerender: false); + } \ No newline at end of file diff --git a/ColorControl.UI/Services/NotificationService.cs b/ColorControl.UI/Services/NotificationService.cs index 780e7c4..5a59213 100644 --- a/ColorControl.UI/Services/NotificationService.cs +++ b/ColorControl.UI/Services/NotificationService.cs @@ -2,25 +2,33 @@ public class NotificationService { - public event Func? OnNotification; + public event Func? OnNotification; - private List _scheduledNotifications = new(); + private List _scheduledNotifications = new(); - public void SendNotification(NotificationDto notification) - { - var now = DateTime.UtcNow; - if (notification.ScheduledAt == null || notification.ScheduledAt <= now) - { - OnNotification?.Invoke(notification); + public void SendNotification(NotificationDto notification) + { + var now = DateTime.UtcNow; + if (notification.ScheduledAt == null || notification.ScheduledAt <= now) + { + OnNotification?.Invoke(notification); - return; - } + return; + } - Task.Run(async () => - { - await Task.Delay(notification.ScheduledAt.Value - now); + Task.Run(async () => + { + await Task.Delay(notification.ScheduledAt.Value - now); - OnNotification?.Invoke(notification); - }); - } + OnNotification?.Invoke(notification); + }); + } + + public async Task SendNotificationDirect(NotificationDto notification) + { + if (OnNotification != null) + { + await OnNotification.Invoke(notification); + } + } } \ No newline at end of file diff --git a/ColorControl.UI/Services/RpcUiClientService.cs b/ColorControl.UI/Services/RpcUiClientService.cs index bdb8965..68c8484 100644 --- a/ColorControl.UI/Services/RpcUiClientService.cs +++ b/ColorControl.UI/Services/RpcUiClientService.cs @@ -14,7 +14,7 @@ public class RpcUiClientService(NotificationService notificationService) { public string? LastErrorMessage { get; private set; } - public async Task CallAsync(string serviceName, string method, params object[] arguments) + public async Task CallAsync(string serviceName, string method, params object?[] arguments) { var rpcMessage = new SvcRpcMessage { @@ -28,7 +28,7 @@ public class RpcUiClientService(NotificationService notificationService) return await SendRpcMessageAsync(rpcMessage, timeout, pipeName: PipeUtils.MainPipe); } - public async Task CallWithOptionsAsync(string serviceName, string method, RpcUiClientOptions options, params object[] arguments) + public async Task CallWithOptionsAsync(string serviceName, string method, RpcUiClientOptions options, params object?[] arguments) { var rpcMessage = new SvcRpcMessage { @@ -73,7 +73,7 @@ public class RpcUiClientService(NotificationService notificationService) { LastErrorMessage = resultMessage?.ErrorMessage ?? "Unknown communication error"; - notificationService.SendNotification(new NotificationDto(LastErrorMessage, Constants.Danger)); + await notificationService.SendNotificationDirect(new NotificationDto(LastErrorMessage, Constants.Danger)); return default; } diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj index 314104b..182843d 100644 --- a/ColorControl/ColorControl.csproj +++ b/ColorControl/ColorControl.csproj @@ -18,8 +18,8 @@ Maassoft Maassoft 0 - 10.0.2.0 - 10.0.2.0 + 10.1.0.0 + 10.1.0.0 false true false diff --git a/ColorControl/Forms/OptionsPanel.Designer.cs b/ColorControl/Forms/OptionsPanel.Designer.cs index 40f7559..58e7a3d 100644 --- a/ColorControl/Forms/OptionsPanel.Designer.cs +++ b/ColorControl/Forms/OptionsPanel.Designer.cs @@ -499,7 +499,6 @@ namespace ColorControl.Forms private System.Windows.Forms.GroupBox grpGeneralOptions; private System.Windows.Forms.CheckBox chkStartMinimized; private System.Windows.Forms.CheckBox chkStartAfterLogin; - private System.Windows.Forms.TabPage tabInfo; private System.Windows.Forms.GroupBox grpMiscellaneousOptions; private System.Windows.Forms.CheckBox chkFixChromeFonts; private System.Windows.Forms.Label lblFixChromeFontsDescription; @@ -518,7 +517,6 @@ namespace ColorControl.Forms private System.Windows.Forms.Button btnElevationInfo; private System.Windows.Forms.Button btnStartStopService; private System.Windows.Forms.CheckBox chkAutoInstallUpdates; - private System.Windows.Forms.Button btnUpdate; private System.Windows.Forms.GroupBox grpOptionsModules; private System.Windows.Forms.CheckedListBox chkModules; private System.Windows.Forms.Label label1; diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs index dc88426..a543788 100644 --- a/ColorControl/MainForm.cs +++ b/ColorControl/MainForm.cs @@ -319,7 +319,7 @@ namespace ColorControl ResumeLayout(true); } - private async void MainForm_Click(object sender, EventArgs e) + private void MainForm_Click(object sender, EventArgs e) { _serviceManager.NvService?.TestResolution(); } diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs index 0e45914..0582101 100644 --- a/ColorControl/Program.cs +++ b/ColorControl/Program.cs @@ -29,444 +29,446 @@ using System.Windows.Forms; namespace ColorControl { - static class Program - { - public const string TS_TASKNAME = "ColorControl"; - public static string DataDir { get; private set; } - public static string ConfigFilename { get; private set; } - public static string LogFilename { get; private set; } - public static Config Config { get; private set; } - public static GlobalContext AppContext { get; private set; } - - public static bool IsRestarting { get; private set; } - public static bool UserExit = false; - - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static Mutex _mutex; - public static MainForm _mainForm; - private static LoggingRule _loggingRule; - - /// - /// The main entry point for the application. - /// - [STAThread] // autogenerated - public static void Main(string[] args) - { - // STA - MainAsync(args).GetAwaiter().GetResult(); - } - - static async Task MainAsync(string[] args) - { - DataDir = Utils.GetDataPath(); - - var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true; - var runElevated = args.Contains("--elevated"); - - InitLogger(runAsService, runElevated); - - Logger.Debug($"Using data path: {DataDir}"); - //Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName); - - if (runAsService) - { - await RunService(args); - return; - } - - await RunApp(args); - } - - static async Task RunApp(string[] args) - { - var startTime = DateTime.Now; - var currentDomain = AppDomain.CurrentDomain; - // Handler for unhandled exceptions. - currentDomain.UnhandledException += GlobalUnhandledExceptionHandler; - // Handler for exceptions in threads behind forms. - Application.ThreadException += GlobalThreadExceptionHandler; - - LoadConfig(); - - if (Config.UseGdiScaling) - { - var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled); - - Logger.Debug($"Result of setting SetHighDpiMode: {result}"); - } - - var mutexId = $"Global\\{typeof(MainForm).GUID}"; - - var startUpParams = StartUpParams.Parse(args); - - AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, mutexId); - - var host = CreateHostBuilder().Build(); - ServiceProvider = host.Services; - AppContext.ServiceProvider = ServiceProvider; - - MessageForms.Title = AppContext.ApplicationTitle; - - var existingProcess = Utils.GetProcessByName("ColorControl"); - - try - { - if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess)) - { - return; - } - } - finally - { - Utils.CloseConsole(); - } - - _mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated); - try - { - if (!mutexCreated) - { - if (existingProcess != null && existingProcess.Threads.Count > 0) - { - var thread = existingProcess.Threads[0]; - NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero); - - return; - } - - MessageBox.Show("Only one instance of this program can be active.", "ColorControl"); - } - else - { - _mutex.WaitOne(); - try - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - - if (Debugger.IsAttached) - { - //Utils.StartService(); - var winApiService = ServiceProvider.GetRequiredService(); - - if (winApiService.IsAdministrator()) - { - var backgroundService = ServiceProvider.GetRequiredService(); - backgroundService.PipeName = PipeUtils.ElevatedPipe; - - Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None)); - } - } - - var worker = ServiceProvider.GetRequiredService(); - - await worker.DoWork(); - - if (Debugger.IsAttached && !IsRestarting) - { - var winApiAdminService = ServiceProvider.GetRequiredService(); - winApiAdminService.StopService(); - } - } - catch (Exception ex) - { - MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - finally - { - if (mutexCreated) - { - _mutex?.Close(); - } - } - } - - public static void OpenDefaultUi() - { - var _ = Config.UiType switch - { - UiType.WebDefaultBrowser => OpenNewUi(), - UiType.WebEmbedded => OpenNewUiEmbedded(), - _ => OpenMainForm() - }; - } - - public static async Task OpenMainForm() - { - _mainForm ??= ServiceProvider.GetRequiredService(); - _mainForm.OpenForm(); - } - - public static bool IsMainFormOpened() - { - return _mainForm != null; - } - - public static void Exit() - { - UserExit = true; - _mainForm?.Close(); - - if (AppContext.MainHandle != 0) - { - NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0); - } - } - - public static void Restart() - { - IsRestarting = true; - - _mainForm?.CloseForRestart(); - - _mutex?.Close(); - _mutex = null; - - Application.Restart(); - Environment.Exit(0); - } - - public static async Task OpenNewUi() - { - if (await StartUiServer()) - { - await Task.Delay(500); - } - - var winApi = AppContext.ServiceProvider.GetRequiredService(); - - winApi.StartProcess(GetNewUiUrl()); - } - - public static string GetNewUiUrl() - { - return $"http://localhost:{(Config.UiPort > 0 ? Config.UiPort : 5000)}"; - } - - public static async Task OpenNewUiEmbedded() - { - if (await StartUiServer()) - { - await Task.Delay(500); - } - - BrowserWindow.CreateAndShow(GetNewUiUrl()); - } - - private static bool UiServerStarted; - - public static async Task StartUiServer() - { - if (UiServerStarted) - { - return false; - } - - var backgroundService = ServiceProvider.GetRequiredService(); - - backgroundService.PipeName = PipeUtils.MainPipe; - - await backgroundService.StartAsync(CancellationToken.None); - - var _ = Task.Run(() => ColorControl.UI.Blazor.Start(Config)); - UiServerStarted = true; - - return true; - } - - private static void InitLogger(bool runAsService, bool runElevated) - { - var config = new LoggingConfiguration(); - - // Targets where to log to: File and Console - var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : ""; - - LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt"); - - var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename }; - - _loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile); - _loggingRule.RuleName = "ColorControl"; - - // Rules for mapping loggers to targets - config.AddRule(_loggingRule); - - // Apply config - LogManager.Configuration = config; - } - - private static void LoadConfig() - { - ConfigFilename = Path.Combine(DataDir, "Settings.json"); - - try - { - if (File.Exists(ConfigFilename)) - { - var data = File.ReadAllText(ConfigFilename); - Config = JsonConvert.DeserializeObject(data); - } - } - catch (Exception ex) - { - Logger.Error($"LoadConfig: {ex.Message}"); - } - Config ??= new Config(); - - var minLogLevel = LogLevel.FromString(Config.LogLevel); - - if (minLogLevel != LogLevel.Trace) - { - _loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal); - LogManager.ReconfigExistingLoggers(); - } - } - - public static IServiceProvider ServiceProvider { get; private set; } - private static IHostBuilder CreateHostBuilder() - { - return Host.CreateDefaultBuilder() - .ConfigureServices((context, services) => - { - //services.AddSingleton(); - services.RegisterSharedServices(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - }); - } - private static async Task RunService(string[] args) - { - var startTime = DateTime.Now; - - Logger.Debug("RUNNING SERVICE"); - - AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime); - - using IHost host = Host.CreateDefaultBuilder(args) - .UseWindowsService(options => - { - options.ServiceName = "ColorControl Service"; - }) - .ConfigureServices(services => - { - services.RegisterSharedServices(); - services.AddSingleton(); - services.AddHostedService(); - }) - .Build(); - - ServiceProvider = host.Services; - AppContext.ServiceProvider = ServiceProvider; - - await host.RunAsync(); - } - - private static void RegisterSharedServices(this IServiceCollection services) - { - services.AddSingleton(AppContext); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - RpcServerService.ServiceTypes.Add(typeof(NvService)); - RpcServerService.ServiceTypes.Add(typeof(GameService)); - RpcServerService.ServiceTypes.Add(typeof(OptionsService)); - RpcServerService.ServiceTypes.Add(typeof(LoggingService)); - RpcServerService.ServiceTypes.Add(typeof(SamsungService)); - RpcServerService.ServiceTypes.Add(typeof(LgService)); - RpcServerService.ServiceTypes.Add(typeof(AmdService)); - RpcServerService.ServiceTypes.Add(typeof(ColorProfileService)); - } - - public static int EnumThreadWindows(IntPtr handle, IntPtr param) - { - NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero); - - return 1; - } - - private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) - { - GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception"); - } - - private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e) - { - GlobalHandleException(e.Exception, "Exception in thread"); - } - - private static void GlobalHandleException(Exception exception, string type) - { - if (UserExit) - { - return; - } - - var trace = exception.ToLogString(Environment.StackTrace); - var message = $"{type}: {trace}"; - - Logger.Error(message); - - MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - internal static void SetTheme(bool useDarkMode) - { - DarkModeUtils.SetDarkMode(useDarkMode); - - WinApi.RefreshImmersiveColorPolicyState(); - - DarkModeUtils.InitWpfTheme(); - - var notifyIconManager = ServiceProvider.GetRequiredService(); - - DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor); - - _mainForm?.SetTheme(useDarkMode); - } - - internal static NotifyIcon GetNotifyIcon() - { - var notifyIconManager = ServiceProvider.GetRequiredService(); - - return notifyIconManager.NotifyIcon; - } - } + static class Program + { + public const string TS_TASKNAME = "ColorControl"; + public static string DataDir { get; private set; } + public static string ConfigFilename { get; private set; } + public static string LogFilename { get; private set; } + public static Config Config { get; private set; } + public static GlobalContext AppContext { get; private set; } + + public static bool IsRestarting { get; private set; } + public static bool UserExit = false; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static Mutex _mutex; + public static MainForm _mainForm; + private static LoggingRule _loggingRule; + + /// + /// The main entry point for the application. + /// + [STAThread] // autogenerated + public static void Main(string[] args) + { + // STA + MainAsync(args).GetAwaiter().GetResult(); + } + + static async Task MainAsync(string[] args) + { + DataDir = Utils.GetDataPath(); + + var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true; + var runElevated = args.Contains("--elevated"); + + InitLogger(runAsService, runElevated); + + Logger.Debug($"Using data path: {DataDir}"); + //Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName); + + if (runAsService) + { + await RunService(args); + return; + } + + await RunApp(args); + } + + static async Task RunApp(string[] args) + { + var startTime = DateTime.Now; + var currentDomain = AppDomain.CurrentDomain; + // Handler for unhandled exceptions. + currentDomain.UnhandledException += GlobalUnhandledExceptionHandler; + // Handler for exceptions in threads behind forms. + Application.ThreadException += GlobalThreadExceptionHandler; + + LoadConfig(); + + if (Config.UseGdiScaling) + { + var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled); + + Logger.Debug($"Result of setting SetHighDpiMode: {result}"); + } + + var mutexId = $"Global\\{typeof(MainForm).GUID}"; + + var startUpParams = StartUpParams.Parse(args); + + AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, mutexId); + + var host = CreateHostBuilder().Build(); + ServiceProvider = host.Services; + AppContext.ServiceProvider = ServiceProvider; + + MessageForms.Title = AppContext.ApplicationTitle; + + var existingProcess = Utils.GetProcessByName("ColorControl"); + + try + { + if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess)) + { + return; + } + } + finally + { + Utils.CloseConsole(); + } + + _mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated); + try + { + if (!mutexCreated) + { + if (existingProcess != null && existingProcess.Threads.Count > 0) + { + var thread = existingProcess.Threads[0]; + NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero); + + return; + } + + MessageBox.Show("Only one instance of this program can be active.", "ColorControl"); + } + else + { + _mutex.WaitOne(); + try + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + if (Debugger.IsAttached) + { + //Utils.StartService(); + var winApiService = ServiceProvider.GetRequiredService(); + + if (winApiService.IsAdministrator()) + { + var backgroundService = ServiceProvider.GetRequiredService(); + backgroundService.PipeName = PipeUtils.ElevatedPipe; + + _ = Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None)); + } + } + + var worker = ServiceProvider.GetRequiredService(); + + await worker.DoWork(); + + if (Debugger.IsAttached && !IsRestarting) + { + var winApiAdminService = ServiceProvider.GetRequiredService(); + winApiAdminService.StopService(); + } + } + catch (Exception ex) + { + MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + finally + { + if (mutexCreated) + { + _mutex?.Close(); + } + } + } + + public static void OpenDefaultUi() + { + var _ = Config.UiType switch + { + UiType.WebDefaultBrowser => OpenNewUi(), + UiType.WebEmbedded => OpenNewUiEmbedded(), + _ => OpenMainForm() + }; + } + + public static Task OpenMainForm() + { + _mainForm ??= ServiceProvider.GetRequiredService(); + _mainForm.OpenForm(); + + return Task.CompletedTask; + } + + public static bool IsMainFormOpened() + { + return _mainForm != null; + } + + public static void Exit() + { + UserExit = true; + _mainForm?.Close(); + + if (AppContext.MainHandle != 0) + { + NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0); + } + } + + public static void Restart() + { + IsRestarting = true; + + _mainForm?.CloseForRestart(); + + _mutex?.Close(); + _mutex = null; + + Application.Restart(); + Environment.Exit(0); + } + + public static async Task OpenNewUi() + { + if (await StartUiServer()) + { + await Task.Delay(500); + } + + var winApi = AppContext.ServiceProvider.GetRequiredService(); + + winApi.StartProcess(GetNewUiUrl()); + } + + public static string GetNewUiUrl() + { + return $"http://localhost:{(Config.UiPort > 0 ? Config.UiPort : 5000)}"; + } + + public static async Task OpenNewUiEmbedded() + { + if (await StartUiServer()) + { + await Task.Delay(500); + } + + BrowserWindow.CreateAndShow(GetNewUiUrl()); + } + + private static bool UiServerStarted; + + public static async Task StartUiServer() + { + if (UiServerStarted) + { + return false; + } + + var backgroundService = ServiceProvider.GetRequiredService(); + + backgroundService.PipeName = PipeUtils.MainPipe; + + await backgroundService.StartAsync(CancellationToken.None); + + var _ = Task.Run(() => ColorControl.UI.Blazor.Start(Config)); + UiServerStarted = true; + + return true; + } + + private static void InitLogger(bool runAsService, bool runElevated) + { + var config = new LoggingConfiguration(); + + // Targets where to log to: File and Console + var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : ""; + + LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt"); + + var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename }; + + _loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile); + _loggingRule.RuleName = "ColorControl"; + + // Rules for mapping loggers to targets + config.AddRule(_loggingRule); + + // Apply config + LogManager.Configuration = config; + } + + private static void LoadConfig() + { + ConfigFilename = Path.Combine(DataDir, "Settings.json"); + + try + { + if (File.Exists(ConfigFilename)) + { + var data = File.ReadAllText(ConfigFilename); + Config = JsonConvert.DeserializeObject(data); + } + } + catch (Exception ex) + { + Logger.Error($"LoadConfig: {ex.Message}"); + } + Config ??= new Config(); + + var minLogLevel = LogLevel.FromString(Config.LogLevel); + + if (minLogLevel != LogLevel.Trace) + { + _loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal); + LogManager.ReconfigExistingLoggers(); + } + } + + public static IServiceProvider ServiceProvider { get; private set; } + private static IHostBuilder CreateHostBuilder() + { + return Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => + { + //services.AddSingleton(); + services.RegisterSharedServices(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + }); + } + private static async Task RunService(string[] args) + { + var startTime = DateTime.Now; + + Logger.Debug("RUNNING SERVICE"); + + AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime); + + using IHost host = Host.CreateDefaultBuilder(args) + .UseWindowsService(options => + { + options.ServiceName = "ColorControl Service"; + }) + .ConfigureServices(services => + { + services.RegisterSharedServices(); + services.AddSingleton(); + services.AddHostedService(); + }) + .Build(); + + ServiceProvider = host.Services; + AppContext.ServiceProvider = ServiceProvider; + + await host.RunAsync(); + } + + private static void RegisterSharedServices(this IServiceCollection services) + { + services.AddSingleton(AppContext); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + RpcServerService.ServiceTypes.Add(typeof(NvService)); + RpcServerService.ServiceTypes.Add(typeof(GameService)); + RpcServerService.ServiceTypes.Add(typeof(OptionsService)); + RpcServerService.ServiceTypes.Add(typeof(LoggingService)); + RpcServerService.ServiceTypes.Add(typeof(SamsungService)); + RpcServerService.ServiceTypes.Add(typeof(LgService)); + RpcServerService.ServiceTypes.Add(typeof(AmdService)); + RpcServerService.ServiceTypes.Add(typeof(ColorProfileService)); + } + + public static int EnumThreadWindows(IntPtr handle, IntPtr param) + { + NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero); + + return 1; + } + + private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) + { + GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception"); + } + + private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e) + { + GlobalHandleException(e.Exception, "Exception in thread"); + } + + private static void GlobalHandleException(Exception exception, string type) + { + if (UserExit) + { + return; + } + + var trace = exception.ToLogString(Environment.StackTrace); + var message = $"{type}: {trace}"; + + Logger.Error(message); + + MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + internal static void SetTheme(bool useDarkMode) + { + DarkModeUtils.SetDarkMode(useDarkMode); + + WinApi.RefreshImmersiveColorPolicyState(); + + DarkModeUtils.InitWpfTheme(); + + var notifyIconManager = ServiceProvider.GetRequiredService(); + + DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor); + + _mainForm?.SetTheme(useDarkMode); + } + + internal static NotifyIcon GetNotifyIcon() + { + var notifyIconManager = ServiceProvider.GetRequiredService(); + + return notifyIconManager.NotifyIcon; + } + } } diff --git a/ColorControl/Services/AMD/AmdService.cs b/ColorControl/Services/AMD/AmdService.cs index de06d86..4880e10 100644 --- a/ColorControl/Services/AMD/AmdService.cs +++ b/ColorControl/Services/AMD/AmdService.cs @@ -16,547 +16,547 @@ using System.Windows.Forms; namespace ColorControl.Services.AMD { - class AmdService : GraphicsService - { - private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); - - public override string ServiceName => "AMD"; - - protected override string PresetsBaseFilename => "AmdPresets.json"; - - private ADLDisplayInfo _currentDisplay; - - public static readonly int SHORTCUTID_AMDQA = -201; - - private string _configFilename; - public AmdServiceConfig Config { get; private set; } - - public AmdService(GlobalContext globalContext) : base(globalContext) - { - LoadPresets(); - LoadConfig(); - } - - protected override void AfterPresetsLoaded() - { - // Force disable display preset on presets - _presets.ForEach(p => p.IsDisplayPreset = false); - } - - public static async Task ExecutePresetAsync(string idOrName) - { - try - { - var globalContext = Program.ServiceProvider.GetRequiredService(); - - var service = new AmdService(globalContext); - - var result = await service.ApplyPreset(idOrName); - - if (!result) - { - Console.WriteLine("Preset not found or error while executing."); - } - - return result; - } - catch (Exception ex) - { - Console.WriteLine("Error executing preset: " + ex.ToLogString()); - return false; - } - } - - public override void InstallEventHandlers() - { - base.InstallEventHandlers(); - - SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut); - } - - private void LoadConfig() - { - _configFilename = Path.Combine(_dataPath, "AmdConfig.json"); - try - { - if (File.Exists(_configFilename)) - { - Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); - } - } - catch (Exception ex) - { - Logger.Error($"LoadConfig: {ex.Message}"); - } - Config ??= new AmdServiceConfig(); - } - - protected override List GetDefaultPresets() - { - return AmdPreset.GetDefaultPresets(); - } - - public override List GetInfo() - { - return [$"{_presets.Count} presets"]; - } - - public void GlobalSave() - { - SavePresets(); - SaveConfig(); - } - - public override bool HasDisplaysAttached(bool reinitialize = false) - { - try - { - return GetDisplays().Any(); - } - catch (Exception) - { - return false; - } - } - - public void SetCurrentDisplay(AmdPreset preset) - { - var displays = GetDisplays(); - if (preset.primaryDisplay) - { - _currentDisplay = displays.FirstOrDefault(); - } - else - { - _currentDisplay = displays.FirstOrDefault(x => x.DisplayName.Equals(preset.displayName)); - } - } - - public ADLDisplayInfo GetCurrentDisplay() - { - if (string.IsNullOrEmpty(_currentDisplay.DisplayName)) - { - _currentDisplay = GetDisplays().FirstOrDefault(); - } - - return _currentDisplay; - } - - public override async Task ApplyPreset(AmdPreset preset) - { - SetCurrentDisplay(preset); - - if (string.IsNullOrEmpty(_currentDisplay.DisplayName)) - { - Logger.Error("Could not set current display"); - return false; - } - - var result = true; - - if (preset.applyHDR) - { - var hdrEnabled = IsHDREnabled(); - if (preset.toggleHDR) - { - result = ADLWrapper.SetDisplayHDRState(_currentDisplay, !hdrEnabled); - } - else - { - result = ADLWrapper.SetDisplayHDRState(_currentDisplay, preset.HDREnabled); - } - - //if (preset.toggleHDR || preset.HDREnabled != hdrEnabled) - //{ - // ToggleHDR(config.DisplaySettingsDelay); - //} - - if (preset.SDRBrightness.HasValue) - { - var deviceName = GetDisplayDeviceName(_currentDisplay); - - SetSDRBrightness(deviceName, preset.SDRBrightness.Value); - } - } - - if (preset.DisplayConfig.ApplyRefreshRate || preset.DisplayConfig.ApplyResolution) - { - if (!SetMode(preset.DisplayConfig, true)) - { - result = false; - } - } - - if (preset.applyColorData) - { - if (!ADLWrapper.SetDisplayColorDepth(_currentDisplay, preset.colorDepth)) - { - Logger.Error("Could not set color depth"); - result = false; - } - if (preset.pixelFormat != ADLPixelFormat.UNKNOWN && !ADLWrapper.SetDisplayPixelFormat(_currentDisplay, preset.pixelFormat)) - { - Logger.Error("Could not set pixel format"); - result = false; - } - } - - if (preset.applyDithering) - { - if (!SetDithering(preset.ditherState)) - { - result = false; - } - } - - _lastAppliedPreset = preset; - - PresetApplied(); - - return result; - } - - public bool SetDithering(ADLDitherState ditherState) - { - var display = GetCurrentDisplay(); - - return ADLWrapper.SetDisplayDitherState(display, ditherState); - } - - public ADLDitherState GetDithering(ADLDisplayInfo display = default(ADLDisplayInfo)) - { - if (string.IsNullOrEmpty(display.DisplayName)) - { - display = GetCurrentDisplay(); - } - - var ditherState = ADLDitherState.DRIVER_DEFAULT; - - if (ADLWrapper.GetDisplayDitherState(display, ref ditherState)) - { - return ditherState; - } - - return ditherState; - } - - public string GetDisplayDeviceName(ADLDisplayInfo display) - { - return Screen.PrimaryScreen.DeviceName; - } - - public bool SetMode(DisplayConfig displayConfig, bool updateRegistry = false) - { - var display = GetCurrentDisplay(); - - return SetMode(GetDisplayDeviceName(display), displayConfig, updateRegistry); - } - - public List GetAvailableRefreshRates(AmdPreset preset = null) - { - if (preset != null) - { - SetCurrentDisplay(preset); - } - - var horizontal = 3840; - var vertical = 2160; - var display = GetCurrentDisplay(); - - ADLWrapper.GetDisplayResolution(display, ref horizontal, ref vertical); - - return GetAvailableRefreshRatesV2(GetDisplayDeviceName(display), horizontal, vertical); - } - - public List GetAvailableResolutionsV2(AmdPreset preset = null) - { - if (preset != null) - { - SetCurrentDisplay(preset); - } - - var display = GetCurrentDisplay(); - var deviceName = GetDisplayDeviceName(display); - if (deviceName == null) - { - return []; - } - - return GetAvailableResolutionsInternalV2(deviceName); - } - - public bool SetMode(VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false, AmdPreset preset = null) - { - var display = preset == null ? GetCurrentDisplay() : GetPresetDisplay(preset); - if (display.Equals(default(ADLDisplayInfo))) - { - return false; - } - - return SetMode(GetDisplayDeviceName(display), resolution, refreshRate, updateRegistry); - } - - public bool SetMode(DisplayConfig displayConfig, bool updateRegistry = false, AmdPreset preset = null) - { - var display = preset == null ? GetCurrentDisplay() : GetPresetDisplay(preset); - if (display.DisplayID.DisplayPhysicalIndex == 0) - { - return false; - } - - return SetMode(GetDisplayDeviceName(display), displayConfig, updateRegistry); - } - - public ADLDisplayInfo GetPresetDisplay(AmdPreset preset) - { - return GetCurrentDisplay(); - } - - public List GetAvailableRefreshRatesV2(AmdPreset preset = null) - { - if (preset != null) - { - SetCurrentDisplay(preset); - } - - var display = _currentDisplay; - if (_currentDisplay.DisplayID.DisplayLogicalAdapterIndex == 0) - { - return []; - } - - var desktopRect = preset?.DisplayConfig.ApplyResolution == true && preset?.DisplayConfig.Resolution.ActiveWidth > 0 ? - new Rectangle(0, 0, (int)preset.DisplayConfig.Resolution.ActiveWidth, (int)preset.DisplayConfig.Resolution.ActiveHeight) : - GetDesktopRect(display); - - return GetAvailableRefreshRatesV2(Screen.PrimaryScreen.DeviceName, desktopRect.Width, desktopRect.Height); - } - - private Rectangle GetDesktopRect(ADLDisplayInfo display) - { - // TODO: improve this - var screen = Screen.AllScreens.First(); - - return screen.Bounds; - } - - public bool IsHDREnabled(ADLDisplayInfo display = default) - { - if (display.Equals(default(ADLDisplayInfo))) - { - display = GetCurrentDisplay(); - } - - var supported = false; - var enabled = false; - ADLWrapper.GetDisplayHDRState(display, ref supported, ref enabled); - - Logger.Debug($"IsHDREnabled: supported {supported}, enabled {enabled}"); - - return enabled; - } - - public List GetDisplays() - { - var displays = ADLWrapper.GetAllDisplays(); - - //if (!displays.Any()) - //{ - // displays.Add(new ADLDisplayInfo - // { - // DisplayName = "Dummy" - // }); - //} - - return displays; - } - - public string GetFullDisplayName(ADLDisplayInfo display) - { - var name = display.DisplayName; - - var screen = GetScreenForDisplay(display); - if (screen != null) - { - name = $"{screen.DeviceName} ({name})"; - } - - return name; - } - - public Screen GetScreenForDisplay(ADLDisplayInfo display) - { - var index = 0; - foreach (var screen in Screen.AllScreens) - { - var info = CCD.GetDisplayInfo(screen.DeviceName); - Logger.Debug($"SCREEN[{index}]: {screen.DeviceName}, {info?.FriendlyName}, {screen.Primary}"); - index++; - } - return Screen.AllScreens.FirstOrDefault(x => CCD.GetDisplayInfo(x.DeviceName)?.FriendlyName.Equals(display.DisplayName) ?? false); - } - - public List GetSimpleDisplayInfos() - { - List displays; - try - { - displays = GetDisplays(); - } - catch (Exception e) - { - Logger.Error("Error while getting displays: " + e.ToLogString()); - return null; - } - - return displays.Select(d => new AmdDisplayInfo(d, null, null)).ToList(); - } - - public List GetDisplayInfos() - { - var list = new List(); - - var displayInfos = GetSimpleDisplayInfos(); - - foreach (var displayInfo in displayInfos) - { - var preset = GetPresetForDisplay(displayInfo, displayInfos.Count()); - - displayInfo.Values = preset.GetDisplayValues(_globalContext.Config); - displayInfo.InfoLine = preset.InfoLine; - - list.Add(displayInfo); - } - - return list; - } - - protected override void Initialize() - { - if (!ADLWrapper.Initialize()) - { - throw new InvalidOperationException("Cannot load AMD API"); - } - } - - protected override void Uninitialize() - { - ADLWrapper.Uninitialze(); - } - - public List GetDisplayPresets() - { - try - { - var list = new List(); - - var displayInfos = GetSimpleDisplayInfos(); - - foreach (var displayInfo in displayInfos) - { - var preset = GetPresetForDisplay(displayInfo, displayInfos.Count()); - - displayInfo.Values = preset.GetDisplayValues(_globalContext.Config); - displayInfo.InfoLine = preset.InfoLine; - - list.Add(preset); - } - - return list; - } - catch (Exception e) - { - Logger.Error("Error while getting displays: " + e.ToLogString()); - return null; - } - } - - public AmdPreset GetPresetForDisplay(AmdDisplayInfo displayInfo, int displayCount = 1) - { - if (displayInfo == null) - { - return null; - } - - var display = displayInfo.Display; - - var colorDepth = ADLColorDepth.UNKNOWN; - ADLWrapper.GetDisplayColorDepth(display, ref colorDepth); - - var pixelFormat = ADLPixelFormat.UNKNOWN; - ADLWrapper.GetDisplayPixelFormat(display, ref pixelFormat); - - var ditherState = GetDithering(display); - - var screen = GetScreenForDisplay(display); - var displayConfig = CCD.GetDisplayConfig(screen?.DeviceName); - - var ccdDisplay = screen != null ? CCD.GetDisplayInfo(screen.DeviceName) : null; - - var hdrEnabled = IsHDREnabled(display); - - var preset = new AmdPreset - { - IsDisplayPreset = true, - displayName = display.DisplayName, - DisplayId = ccdDisplay?.DisplayId, - primaryDisplay = screen?.Primary == true, - colorDepth = colorDepth, - pixelFormat = pixelFormat, - ditherState = ditherState, - DisplayConfig = displayConfig, - SDRBrightness = hdrEnabled ? GetSDRBrightness(screen?.DeviceName) : null, - HDREnabled = hdrEnabled, - }; + class AmdService : GraphicsService + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public override string ServiceName => "AMD"; + + protected override string PresetsBaseFilename => "AmdPresets.json"; + + private ADLDisplayInfo _currentDisplay; + + public static readonly int SHORTCUTID_AMDQA = -201; + + private string _configFilename; + public AmdServiceConfig Config { get; private set; } + + public AmdService(GlobalContext globalContext) : base(globalContext) + { + LoadPresets(); + LoadConfig(); + } + + protected override void AfterPresetsLoaded() + { + // Force disable display preset on presets + _presets.ForEach(p => p.IsDisplayPreset = false); + } + + public static async Task ExecutePresetAsync(string idOrName) + { + try + { + var globalContext = Program.ServiceProvider.GetRequiredService(); + + var service = new AmdService(globalContext); + + var result = await service.ApplyPreset(idOrName); + + if (!result) + { + Console.WriteLine("Preset not found or error while executing."); + } + + return result; + } + catch (Exception ex) + { + Console.WriteLine("Error executing preset: " + ex.ToLogString()); + return false; + } + } + + public override void InstallEventHandlers() + { + base.InstallEventHandlers(); + + SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut); + } + + private void LoadConfig() + { + _configFilename = Path.Combine(_dataPath, "AmdConfig.json"); + try + { + if (File.Exists(_configFilename)) + { + Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); + } + } + catch (Exception ex) + { + Logger.Error($"LoadConfig: {ex.Message}"); + } + Config ??= new AmdServiceConfig(); + } + + protected override List GetDefaultPresets() + { + return AmdPreset.GetDefaultPresets(); + } + + public override List GetInfo() + { + return [$"{_presets.Count} presets"]; + } + + public void GlobalSave() + { + SavePresets(); + SaveConfig(); + } + + public override bool HasDisplaysAttached(bool reinitialize = false) + { + try + { + return GetDisplays().Any(); + } + catch (Exception) + { + return false; + } + } + + public void SetCurrentDisplay(AmdPreset preset) + { + var displays = GetDisplays(); + if (preset.primaryDisplay) + { + _currentDisplay = displays.FirstOrDefault(); + } + else + { + _currentDisplay = displays.FirstOrDefault(x => x.DisplayName.Equals(preset.displayName)); + } + } + + public ADLDisplayInfo GetCurrentDisplay() + { + if (string.IsNullOrEmpty(_currentDisplay.DisplayName)) + { + _currentDisplay = GetDisplays().FirstOrDefault(); + } + + return _currentDisplay; + } + + public override Task ApplyPreset(AmdPreset preset) + { + SetCurrentDisplay(preset); + + if (string.IsNullOrEmpty(_currentDisplay.DisplayName)) + { + Logger.Error("Could not set current display"); + return Task.FromResult(false); + } + + var result = true; + + if (preset.applyHDR) + { + var hdrEnabled = IsHDREnabled(); + if (preset.toggleHDR) + { + result = ADLWrapper.SetDisplayHDRState(_currentDisplay, !hdrEnabled); + } + else + { + result = ADLWrapper.SetDisplayHDRState(_currentDisplay, preset.HDREnabled); + } + + //if (preset.toggleHDR || preset.HDREnabled != hdrEnabled) + //{ + // ToggleHDR(config.DisplaySettingsDelay); + //} + + if (preset.SDRBrightness.HasValue) + { + var deviceName = GetDisplayDeviceName(_currentDisplay); + + SetSDRBrightness(deviceName, preset.SDRBrightness.Value); + } + } + + if (preset.DisplayConfig.ApplyRefreshRate || preset.DisplayConfig.ApplyResolution) + { + if (!SetMode(preset.DisplayConfig, true)) + { + result = false; + } + } + + if (preset.applyColorData) + { + if (!ADLWrapper.SetDisplayColorDepth(_currentDisplay, preset.colorDepth)) + { + Logger.Error("Could not set color depth"); + result = false; + } + if (preset.pixelFormat != ADLPixelFormat.UNKNOWN && !ADLWrapper.SetDisplayPixelFormat(_currentDisplay, preset.pixelFormat)) + { + Logger.Error("Could not set pixel format"); + result = false; + } + } + + if (preset.applyDithering) + { + if (!SetDithering(preset.ditherState)) + { + result = false; + } + } + + _lastAppliedPreset = preset; + + PresetApplied(); + + return Task.FromResult(result); + } + + public bool SetDithering(ADLDitherState ditherState) + { + var display = GetCurrentDisplay(); + + return ADLWrapper.SetDisplayDitherState(display, ditherState); + } + + public ADLDitherState GetDithering(ADLDisplayInfo display = default(ADLDisplayInfo)) + { + if (string.IsNullOrEmpty(display.DisplayName)) + { + display = GetCurrentDisplay(); + } + + var ditherState = ADLDitherState.DRIVER_DEFAULT; + + if (ADLWrapper.GetDisplayDitherState(display, ref ditherState)) + { + return ditherState; + } + + return ditherState; + } + + public string GetDisplayDeviceName(ADLDisplayInfo display) + { + return Screen.PrimaryScreen.DeviceName; + } + + public bool SetMode(DisplayConfig displayConfig, bool updateRegistry = false) + { + var display = GetCurrentDisplay(); + + return SetMode(GetDisplayDeviceName(display), displayConfig, updateRegistry); + } + + public List GetAvailableRefreshRates(AmdPreset preset = null) + { + if (preset != null) + { + SetCurrentDisplay(preset); + } + + var horizontal = 3840; + var vertical = 2160; + var display = GetCurrentDisplay(); + + ADLWrapper.GetDisplayResolution(display, ref horizontal, ref vertical); + + return GetAvailableRefreshRatesV2(GetDisplayDeviceName(display), horizontal, vertical); + } + + public List GetAvailableResolutionsV2(AmdPreset preset = null) + { + if (preset != null) + { + SetCurrentDisplay(preset); + } + + var display = GetCurrentDisplay(); + var deviceName = GetDisplayDeviceName(display); + if (deviceName == null) + { + return []; + } + + return GetAvailableResolutionsInternalV2(deviceName); + } + + public bool SetMode(VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false, AmdPreset preset = null) + { + var display = preset == null ? GetCurrentDisplay() : GetPresetDisplay(preset); + if (display.Equals(default(ADLDisplayInfo))) + { + return false; + } + + return SetMode(GetDisplayDeviceName(display), resolution, refreshRate, updateRegistry); + } + + public bool SetMode(DisplayConfig displayConfig, bool updateRegistry = false, AmdPreset preset = null) + { + var display = preset == null ? GetCurrentDisplay() : GetPresetDisplay(preset); + if (display.DisplayID.DisplayPhysicalIndex == 0) + { + return false; + } + + return SetMode(GetDisplayDeviceName(display), displayConfig, updateRegistry); + } + + public ADLDisplayInfo GetPresetDisplay(AmdPreset preset) + { + return GetCurrentDisplay(); + } + + public List GetAvailableRefreshRatesV2(AmdPreset preset = null) + { + if (preset != null) + { + SetCurrentDisplay(preset); + } + + var display = _currentDisplay; + if (_currentDisplay.DisplayID.DisplayLogicalAdapterIndex == 0) + { + return []; + } + + var desktopRect = preset?.DisplayConfig.ApplyResolution == true && preset?.DisplayConfig.Resolution.ActiveWidth > 0 ? + new Rectangle(0, 0, (int)preset.DisplayConfig.Resolution.ActiveWidth, (int)preset.DisplayConfig.Resolution.ActiveHeight) : + GetDesktopRect(display); + + return GetAvailableRefreshRatesV2(Screen.PrimaryScreen.DeviceName, desktopRect.Width, desktopRect.Height); + } + + private Rectangle GetDesktopRect(ADLDisplayInfo display) + { + // TODO: improve this + var screen = Screen.AllScreens.First(); + + return screen.Bounds; + } + + public bool IsHDREnabled(ADLDisplayInfo display = default) + { + if (display.Equals(default(ADLDisplayInfo))) + { + display = GetCurrentDisplay(); + } + + var supported = false; + var enabled = false; + ADLWrapper.GetDisplayHDRState(display, ref supported, ref enabled); + + Logger.Debug($"IsHDREnabled: supported {supported}, enabled {enabled}"); + + return enabled; + } + + public List GetDisplays() + { + var displays = ADLWrapper.GetAllDisplays(); + + //if (!displays.Any()) + //{ + // displays.Add(new ADLDisplayInfo + // { + // DisplayName = "Dummy" + // }); + //} + + return displays; + } + + public string GetFullDisplayName(ADLDisplayInfo display) + { + var name = display.DisplayName; + + var screen = GetScreenForDisplay(display); + if (screen != null) + { + name = $"{screen.DeviceName} ({name})"; + } + + return name; + } + + public Screen GetScreenForDisplay(ADLDisplayInfo display) + { + var index = 0; + foreach (var screen in Screen.AllScreens) + { + var info = CCD.GetDisplayInfo(screen.DeviceName); + Logger.Debug($"SCREEN[{index}]: {screen.DeviceName}, {info?.FriendlyName}, {screen.Primary}"); + index++; + } + return Screen.AllScreens.FirstOrDefault(x => CCD.GetDisplayInfo(x.DeviceName)?.FriendlyName.Equals(display.DisplayName) ?? false); + } + + public List GetSimpleDisplayInfos() + { + List displays; + try + { + displays = GetDisplays(); + } + catch (Exception e) + { + Logger.Error("Error while getting displays: " + e.ToLogString()); + return null; + } + + return displays.Select(d => new AmdDisplayInfo(d, null, null)).ToList(); + } + + public List GetDisplayInfos() + { + var list = new List(); + + var displayInfos = GetSimpleDisplayInfos(); + + foreach (var displayInfo in displayInfos) + { + var preset = GetPresetForDisplay(displayInfo, displayInfos.Count()); + + displayInfo.Values = preset.GetDisplayValues(_globalContext.Config); + displayInfo.InfoLine = preset.InfoLine; + + list.Add(displayInfo); + } + + return list; + } + + protected override void Initialize() + { + if (!ADLWrapper.Initialize()) + { + throw new InvalidOperationException("Cannot load AMD API"); + } + } + + protected override void Uninitialize() + { + ADLWrapper.Uninitialze(); + } + + public List GetDisplayPresets() + { + try + { + var list = new List(); + + var displayInfos = GetSimpleDisplayInfos(); + + foreach (var displayInfo in displayInfos) + { + var preset = GetPresetForDisplay(displayInfo, displayInfos.Count()); + + displayInfo.Values = preset.GetDisplayValues(_globalContext.Config); + displayInfo.InfoLine = preset.InfoLine; + + list.Add(preset); + } + + return list; + } + catch (Exception e) + { + Logger.Error("Error while getting displays: " + e.ToLogString()); + return null; + } + } + + public AmdPreset GetPresetForDisplay(AmdDisplayInfo displayInfo, int displayCount = 1) + { + if (displayInfo == null) + { + return null; + } + + var display = displayInfo.Display; + + var colorDepth = ADLColorDepth.UNKNOWN; + ADLWrapper.GetDisplayColorDepth(display, ref colorDepth); + + var pixelFormat = ADLPixelFormat.UNKNOWN; + ADLWrapper.GetDisplayPixelFormat(display, ref pixelFormat); + + var ditherState = GetDithering(display); + + var screen = GetScreenForDisplay(display); + var displayConfig = CCD.GetDisplayConfig(screen?.DeviceName); + + var ccdDisplay = screen != null ? CCD.GetDisplayInfo(screen.DeviceName) : null; + + var hdrEnabled = IsHDREnabled(display); + + var preset = new AmdPreset + { + IsDisplayPreset = true, + displayName = display.DisplayName, + DisplayId = ccdDisplay?.DisplayId, + primaryDisplay = screen?.Primary == true, + colorDepth = colorDepth, + pixelFormat = pixelFormat, + ditherState = ditherState, + DisplayConfig = displayConfig, + SDRBrightness = hdrEnabled ? GetSDRBrightness(screen?.DeviceName) : null, + HDREnabled = hdrEnabled, + }; - return preset; - } + return preset; + } - private void SaveConfig() - { - Utils.WriteObject(_configFilename, Config); - } + private void SaveConfig() + { + Utils.WriteObject(_configFilename, Config); + } - public AmdServiceConfig GetConfig() - { - return Config; - } + public AmdServiceConfig GetConfig() + { + return Config; + } - public bool UpdateConfig(AmdServiceConfig config) - { - Config.Update(config); + public bool UpdateConfig(AmdServiceConfig config) + { + Config.Update(config); - SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut); + SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut); - SaveConfig(); + SaveConfig(); - return true; - } + return true; + } - public bool UpdatePreset(AmdPreset specPreset) - { - var currentPreset = _presets.FirstOrDefault(p => p.id == specPreset.id); + public bool UpdatePreset(AmdPreset specPreset) + { + var currentPreset = _presets.FirstOrDefault(p => p.id == specPreset.id); - if (currentPreset != null) - { - currentPreset.name = specPreset.name; - currentPreset.shortcut = specPreset.shortcut; - currentPreset.Update(specPreset); + if (currentPreset != null) + { + currentPreset.name = specPreset.name; + currentPreset.shortcut = specPreset.shortcut; + currentPreset.Update(specPreset); - SavePresets(); + SavePresets(); - return true; - } + return true; + } - var newPreset = new AmdPreset(specPreset); - newPreset.name = specPreset.name; - newPreset.shortcut = specPreset.shortcut; + var newPreset = new AmdPreset(specPreset); + newPreset.name = specPreset.name; + newPreset.shortcut = specPreset.shortcut; - _presets.Add(newPreset); + _presets.Add(newPreset); - SavePresets(); + SavePresets(); - return true; - } - } + return true; + } + } } diff --git a/ColorControl/Services/Common/ColorProfileService.cs b/ColorControl/Services/Common/ColorProfileService.cs index aab6386..d0dc2c1 100644 --- a/ColorControl/Services/Common/ColorProfileService.cs +++ b/ColorControl/Services/Common/ColorProfileService.cs @@ -63,21 +63,51 @@ public class ColorProfileService return CCD.GetDisplayDefaultColorProfile(displayName, isHdrColorProfile); } - public List GetDisplayColorProfiles(string displayName) + public List GetDisplayColorProfiles(string displayName, bool isHdrColorProfile = false) { if (displayName != null && !displayName.StartsWith("\\\\")) { displayName = GetDisplayNameByDisplayId(displayName); } - return CCD.GetDisplayColorProfiles(displayName); + return CCD.GetDisplayColorProfiles(displayName, isHdrColorProfile); + } + + public List GetAllDisplayColorProfiles(string displayName) + { + if (displayName != null && !displayName.StartsWith("\\\\")) + { + displayName = GetDisplayNameByDisplayId(displayName); + } + + var display = GetDisplayByGdiName(displayName); + + if (display == null) + { + return []; + } + + var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(display.DisplayName); + + return GetAllDisplayColorProfiles(display, advancedColor.isSupported); + } + + public List GetAllDisplayColorProfiles(Display display, bool isHdrSupported = false) + { + var colorProfiles = GetDisplayColorProfiles(display.DisplayName, false).Select(p => new ColorProfileDto { Name = p, IsHDRProfile = false }).ToList(); + if (isHdrSupported) + { + colorProfiles = colorProfiles.Union(GetDisplayColorProfiles(display.DisplayName, true).Select(p => new ColorProfileDto { Name = p, IsHDRProfile = true })).ToList(); + } + + return colorProfiles; } public List GetAllColorProfileNames() { if (!_winApiService.IsAdministrator()) { - return _rpcClientService.Call>("GetAllColorProfileNames"); + return _rpcClientService.Call>(nameof(GetAllColorProfileNames)); } return CCD.GetAllColorProfileNames(); @@ -103,6 +133,11 @@ public class ColorProfileService return deviceName; } + public Display GetDisplayByGdiName(string displayName) + { + return Display.GetDisplays().FirstOrDefault(d => d.DisplayName == displayName); + } + public bool GetUsePerUserDisplayProfiles(string displayName) { return CCD.GetUsePerUserDisplayProfiles(displayName); @@ -118,9 +153,11 @@ public class ColorProfileService return CCD.UninstallColorProfile(name); } - public ColorProfileDto GetColorProfile(string name, bool isHdrProfile = true) + public ColorProfileDto GetColorProfile(string name, string displayName = null) { - var data = MHC2Wrapper.LoadProfile(name, isHdrProfile); + var isAssociatedAsHdr = CCD.IsColorProfileAssociatedAsHdr(displayName, name); + + var data = MHC2Wrapper.LoadProfile(name, isAssociatedAsHdr); return new ColorProfileDto { @@ -249,6 +286,8 @@ public class ColorProfileService }); } + var colorProfiles = GetAllDisplayColorProfiles(d, advancedColor.isSupported); + return new DisplayInfoDto { DisplayName = ccdDisplayInfo.ExtendedName, @@ -260,8 +299,9 @@ public class ColorProfileService IsAdvancedColorEnabled = advancedColor.isEnabled, IsAdvancedColorSupported = advancedColor.isSupported, DisplayColors = displayColors, - ColorProfiles = GetDisplayColorProfiles(d.DisplayName), - DefaultColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, advancedColor.isEnabled) + ColorProfiles = colorProfiles, + DefaultSdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, false), + DefaultHdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, true), }; }) .Where(d => !onlyHdrEnabled.HasValue || onlyHdrEnabled == d.IsHdrSupportedAndEnabled) diff --git a/ColorControl/Services/Common/NotifyIconManager.cs b/ColorControl/Services/Common/NotifyIconManager.cs index 737295d..31f7600 100644 --- a/ColorControl/Services/Common/NotifyIconManager.cs +++ b/ColorControl/Services/Common/NotifyIconManager.cs @@ -123,7 +123,7 @@ public class NotifyIconManager private void OpenWinFormsUi(object sender, EventArgs e) { - Program.OpenMainForm(); + _ = Program.OpenMainForm(); } private async void OpenNewUi(object sender, EventArgs e) @@ -151,7 +151,7 @@ public class NotifyIconManager UpdateTrayMenu(_nvTrayMenu, presets, TrayMenuItemNv_Click); } - _novideoTrayMenu.Visible = _serviceManager.NvService != null && MainWindow.IsInitialized(); + _novideoTrayMenu.Visible = _serviceManager.NvService != null && MainWindow.IsCreated(); if (_novideoTrayMenu.Visible) { MainWindow.UpdateContextMenu(_novideoTrayMenu); diff --git a/ColorControl/Services/Common/UpdateManager.cs b/ColorControl/Services/Common/UpdateManager.cs index 8bc876c..b328428 100644 --- a/ColorControl/Services/Common/UpdateManager.cs +++ b/ColorControl/Services/Common/UpdateManager.cs @@ -19,8 +19,6 @@ public class UpdateManager private readonly WinApiAdminService _winApiAdminService; private readonly Config _config; - private string _downloadUrl; - public UpdateManager(GlobalContext globalContext, ServiceManager serviceManager, NotifyIconManager notifyIconManager, WinApiService winApiService, WinApiAdminService winApiAdminService) { _globalContext = globalContext; @@ -216,10 +214,10 @@ public class UpdateManager return true; } - public async Task RestartAfterUpdate() + public Task RestartAfterUpdate() { Program.Restart(); - return true; + return Task.FromResult(true); } } diff --git a/ColorControl/Services/GameLauncher/GameService.cs b/ColorControl/Services/GameLauncher/GameService.cs index 66cf357..4079c5b 100644 --- a/ColorControl/Services/GameLauncher/GameService.cs +++ b/ColorControl/Services/GameLauncher/GameService.cs @@ -15,370 +15,370 @@ using System.Threading.Tasks; namespace ColorControl.Services.GameLauncher { - class GameService : ServiceBase - { - private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); - - public override string ServiceName => "Game"; - - protected override string PresetsBaseFilename => "GamePresets.json"; - - private ProcessEventDispatcher _processEventDispatcher; - private readonly WinApiAdminService _winApiAdminService; - private readonly WinApiService _winApiService; - private ServiceManager _serviceManager; - private int _lastStartedProcessId; - private System.DateTime _lastStartedPresetDateTime = System.DateTime.MinValue; - private string _lastStartedProcessPath; - private string _configFilename; - - public GameServiceConfig Config { get; private set; } - - public static readonly int SHORTCUTID_GAMEQA = -203; - - public GameService(GlobalContext globalContext, ServiceManager serviceManager, ProcessEventDispatcher processEventDispatcher, WinApiAdminService winApiAdminService, WinApiService winApiService) : base(globalContext) - { - LoadConfig(); - LoadPresets(); - _serviceManager = serviceManager; - _processEventDispatcher = processEventDispatcher; - _winApiAdminService = winApiAdminService; - _winApiService = winApiService; - _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); - } - - public override void InstallEventHandlers() - { - base.InstallEventHandlers(); - - SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut); - } - - public override List GetInfo() - { - return [$"{_presets.Count} presets"]; - } - - private async Task ProcessChanged(object sender, ProcessChangedEventArgs e, CancellationToken token) - { - if (e.StartedProcesses?.Any() != true || !Config.ApplyExternallyLaunched) - { - return; - } - - if (_lastStartedPresetDateTime > System.DateTime.Now.AddSeconds(-5)) - { - return; - } - _lastStartedProcessId = 0; - - var startedProcesses = e.StartedProcesses; - - foreach (var startedProcess in startedProcesses) - { - try - { - if (await HandleStartedProcess(startedProcess)) - { - return; - } - } - catch (System.Exception) { } - } - } - - private async Task HandleStartedProcess(Process process) - { - GamePreset preset; - - if (_winApiService.IsAdministrator()) - { - var mainModule = process.MainModule; - - if (mainModule == null) - { - return false; - } - - var fileName = mainModule.FileName; - - preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase)); - } - else - { - var name = process.ProcessName.Replace("-Win64-Shipping", ""); - preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase)); - } - - if (preset == null || !preset.AutoSettings.AllowAutoApply) - { - return false; - } - - if (_lastStartedProcessId != process.Id) - { - Logger.Debug($"Auto-starting preset {preset.name} for process {process.ProcessName}"); - - var mainThreadId = 0; - var suspended = false; - if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Suspend) - { - mainThreadId = _winApiAdminService.SuspendMainThread(process.Id); - suspended = true; - } - else if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Restart) - { - _winApiAdminService.StopProcess(process.Id); - process = null; - } - - var _ = ExecutePreset(preset, process, suspended ? mainThreadId : 0); - - return true; - } - - return false; - } - - protected override List GetDefaultPresets() - { - return new List(); - } - - public void GlobalSave() - { - SavePresets(); - SaveConfig(); - } - - public override async Task ApplyPreset(GamePreset preset) - { - var result = await ExecutePreset(preset); - - _lastAppliedPreset = preset; - - PresetApplied(); - - return result; - } - - private async Task ExecutePreset(GamePreset preset, Process process = null, int resumeThreadId = default) - { - var result = true; - - await ExecuteSteps(preset.PreLaunchSteps); - - if (process == null && !string.IsNullOrEmpty(preset.Path)) - { - process = _winApiAdminService.StartProcess(preset.Path, preset.Parameters, setWorkingDir: true, elevate: preset.RunAsAdministrator, affinityMask: preset.ProcessAffinityMask, priorityClass: preset.ProcessPriorityClass); - - _lastStartedPresetDateTime = System.DateTime.Now; - _lastStartedProcessId = process.Id; - _lastStartedProcessPath = Path.GetDirectoryName(preset.Path); - } - else if (resumeThreadId != default) - { - _winApiAdminService.ResumeThread((uint)resumeThreadId); - } - - await ExecuteSteps(preset.PostLaunchSteps); - - if (process != null && preset.FinalizeSteps?.Any() == true) - { - await ExecuteFinalizationStepsAsync(process, preset.FinalizeSteps); - } - - return result; - } - - private async Task ExecuteFinalizationStepsAsync(Process process, List finalizeSteps) - { - await Task.Delay(1000); - - try - { - if (process.HasExited) - { - return; - } - - await process.WaitForExitAsync(); - } - catch (Exception ex) - { - Logger.Error(ex); - return; - } - - await ExecuteSteps(finalizeSteps); - } - - private async Task ExecuteSteps(List steps) - { - if (steps?.Any() != true) - { - return; - } - - foreach (var step in steps) - { - var keySpec = step.Split(':'); - - var delay = 0; - var key = step; - if (keySpec.Length == 2) - { - delay = Utils.ParseInt(keySpec[1]); - if (delay > 0) - { - key = keySpec[0]; - } - } - - var index = key.IndexOf("("); - string[] parameters = null; - if (index > -1) - { - var keyValue = key.Split('('); - key = keyValue[0]; - parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Trim().Split(';'); - } - - var handled = false; - - if (parameters != null) - { - handled = await _serviceManager.HandleExternalServiceAsync(key, parameters); - } - - if (!handled) - { - } - - if (delay > 0) - { - await Task.Delay(delay); - } - - } - } - - private void LoadConfig() - { - _configFilename = Path.Combine(_dataPath, "GameConfig.json"); - try - { - if (File.Exists(_configFilename)) - { - Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); - } - } - catch (System.Exception ex) - { - Logger.Error($"LoadConfig: {ex.Message}"); - } - Config ??= new GameServiceConfig(); - } - - private void SaveConfig() - { - Utils.WriteObject(_configFilename, Config); - } - - public GameServiceConfig GetConfig() - { - return Config; - } - - public bool UpdateConfig(GameServiceConfig config) - { - Config.Update(config); - - SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut); - - SaveConfig(); - - return true; - } - - public bool UpdatePreset(GamePreset presetSpec) - { - var preset = _presets.FirstOrDefault(x => x.id == presetSpec.id); - - if (preset != null) - { - preset.Update(presetSpec); - } - else - { - preset = new GamePreset(presetSpec); - _presets.Add(preset); - } - - SavePresets(); - - return true; - } - - public string SelectPath() - { - var fileInfo = Utils.SelectFile(); - - if (fileInfo == null) - { - return null; - } - - return fileInfo.FullName; - } - - public List GetMruApps(bool skipExistingPresets = false) - { - var key = Registry.ClassesRoot.OpenSubKey(@"Local Settings\Software\Microsoft\Windows\Shell\MuiCache"); - - if (key == null) - { - return new List(); - } - - var names = key.GetValueNames(); - var apps = new List(); - var winFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows); - - foreach (var name in names) - { - var parts = name.Split('.'); - - if (parts.Last() != "FriendlyAppName") - { - continue; - } - - var path = name.Replace("." + parts.Last(), ""); - - if (skipExistingPresets && _presets.Any(p => p.Path.Equals(path, StringComparison.OrdinalIgnoreCase))) - { - continue; - } - - if (!path.StartsWith(winFolder) && Path.Exists(path)) - { - var friendlyName = key.GetValue(name)?.ToString(); - - var app = new GameApp - { - Path = path, - Filename = Path.GetFileName(path), - FriendlyName = friendlyName - }; - - apps.Add(app); - } - } - - apps = apps.DistinctBy(a => a.Path).OrderBy(a => a.FriendlyName).ToList(); - - return apps; - } - } + class GameService : ServiceBase + { + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + public override string ServiceName => "Game"; + + protected override string PresetsBaseFilename => "GamePresets.json"; + + private ProcessEventDispatcher _processEventDispatcher; + private readonly WinApiAdminService _winApiAdminService; + private readonly WinApiService _winApiService; + private ServiceManager _serviceManager; + private int _lastStartedProcessId; + private System.DateTime _lastStartedPresetDateTime = System.DateTime.MinValue; + private string _lastStartedProcessPath; + private string _configFilename; + + public GameServiceConfig Config { get; private set; } + + public static readonly int SHORTCUTID_GAMEQA = -203; + + public GameService(GlobalContext globalContext, ServiceManager serviceManager, ProcessEventDispatcher processEventDispatcher, WinApiAdminService winApiAdminService, WinApiService winApiService) : base(globalContext) + { + LoadConfig(); + LoadPresets(); + _serviceManager = serviceManager; + _processEventDispatcher = processEventDispatcher; + _winApiAdminService = winApiAdminService; + _winApiService = winApiService; + _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); + } + + public override void InstallEventHandlers() + { + base.InstallEventHandlers(); + + SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut); + } + + public override List GetInfo() + { + return [$"{_presets.Count} presets"]; + } + + private async Task ProcessChanged(object sender, ProcessChangedEventArgs e, CancellationToken token) + { + if (e.StartedProcesses?.Any() != true || !Config.ApplyExternallyLaunched) + { + return; + } + + if (_lastStartedPresetDateTime > System.DateTime.Now.AddSeconds(-5)) + { + return; + } + _lastStartedProcessId = 0; + + var startedProcesses = e.StartedProcesses; + + foreach (var startedProcess in startedProcesses) + { + try + { + if (await HandleStartedProcess(startedProcess)) + { + return; + } + } + catch (System.Exception) { } + } + } + + private Task HandleStartedProcess(Process process) + { + GamePreset preset; + + if (_winApiService.IsAdministrator()) + { + var mainModule = process.MainModule; + + if (mainModule == null) + { + return Task.FromResult(false); + } + + var fileName = mainModule.FileName; + + preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase)); + } + else + { + var name = process.ProcessName.Replace("-Win64-Shipping", ""); + preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase)); + } + + if (preset == null || !preset.AutoSettings.AllowAutoApply) + { + return Task.FromResult(false); + } + + if (_lastStartedProcessId != process.Id) + { + Logger.Debug($"Auto-starting preset {preset.name} for process {process.ProcessName}"); + + var mainThreadId = 0; + var suspended = false; + if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Suspend) + { + mainThreadId = _winApiAdminService.SuspendMainThread(process.Id); + suspended = true; + } + else if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Restart) + { + _winApiAdminService.StopProcess(process.Id); + process = null; + } + + var _ = ExecutePreset(preset, process, suspended ? mainThreadId : 0); + + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + protected override List GetDefaultPresets() + { + return new List(); + } + + public void GlobalSave() + { + SavePresets(); + SaveConfig(); + } + + public override async Task ApplyPreset(GamePreset preset) + { + var result = await ExecutePreset(preset); + + _lastAppliedPreset = preset; + + PresetApplied(); + + return result; + } + + private async Task ExecutePreset(GamePreset preset, Process process = null, int resumeThreadId = default) + { + var result = true; + + await ExecuteSteps(preset.PreLaunchSteps); + + if (process == null && !string.IsNullOrEmpty(preset.Path)) + { + process = _winApiAdminService.StartProcess(preset.Path, preset.Parameters, setWorkingDir: true, elevate: preset.RunAsAdministrator, affinityMask: preset.ProcessAffinityMask, priorityClass: preset.ProcessPriorityClass); + + _lastStartedPresetDateTime = System.DateTime.Now; + _lastStartedProcessId = process.Id; + _lastStartedProcessPath = Path.GetDirectoryName(preset.Path); + } + else if (resumeThreadId != default) + { + _winApiAdminService.ResumeThread((uint)resumeThreadId); + } + + await ExecuteSteps(preset.PostLaunchSteps); + + if (process != null && preset.FinalizeSteps?.Any() == true) + { + await ExecuteFinalizationStepsAsync(process, preset.FinalizeSteps); + } + + return result; + } + + private async Task ExecuteFinalizationStepsAsync(Process process, List finalizeSteps) + { + await Task.Delay(1000); + + try + { + if (process.HasExited) + { + return; + } + + await process.WaitForExitAsync(); + } + catch (Exception ex) + { + Logger.Error(ex); + return; + } + + await ExecuteSteps(finalizeSteps); + } + + private async Task ExecuteSteps(List steps) + { + if (steps?.Any() != true) + { + return; + } + + foreach (var step in steps) + { + var keySpec = step.Split(':'); + + var delay = 0; + var key = step; + if (keySpec.Length == 2) + { + delay = Utils.ParseInt(keySpec[1]); + if (delay > 0) + { + key = keySpec[0]; + } + } + + var index = key.IndexOf("("); + string[] parameters = null; + if (index > -1) + { + var keyValue = key.Split('('); + key = keyValue[0]; + parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Trim().Split(';'); + } + + var handled = false; + + if (parameters != null) + { + handled = await _serviceManager.HandleExternalServiceAsync(key, parameters); + } + + if (!handled) + { + } + + if (delay > 0) + { + await Task.Delay(delay); + } + + } + } + + private void LoadConfig() + { + _configFilename = Path.Combine(_dataPath, "GameConfig.json"); + try + { + if (File.Exists(_configFilename)) + { + Config = JsonConvert.DeserializeObject(File.ReadAllText(_configFilename)); + } + } + catch (System.Exception ex) + { + Logger.Error($"LoadConfig: {ex.Message}"); + } + Config ??= new GameServiceConfig(); + } + + private void SaveConfig() + { + Utils.WriteObject(_configFilename, Config); + } + + public GameServiceConfig GetConfig() + { + return Config; + } + + public bool UpdateConfig(GameServiceConfig config) + { + Config.Update(config); + + SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut); + + SaveConfig(); + + return true; + } + + public bool UpdatePreset(GamePreset presetSpec) + { + var preset = _presets.FirstOrDefault(x => x.id == presetSpec.id); + + if (preset != null) + { + preset.Update(presetSpec); + } + else + { + preset = new GamePreset(presetSpec); + _presets.Add(preset); + } + + SavePresets(); + + return true; + } + + public string SelectPath() + { + var fileInfo = Utils.SelectFile(); + + if (fileInfo == null) + { + return null; + } + + return fileInfo.FullName; + } + + public List GetMruApps(bool skipExistingPresets = false) + { + var key = Registry.ClassesRoot.OpenSubKey(@"Local Settings\Software\Microsoft\Windows\Shell\MuiCache"); + + if (key == null) + { + return new List(); + } + + var names = key.GetValueNames(); + var apps = new List(); + var winFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + + foreach (var name in names) + { + var parts = name.Split('.'); + + if (parts.Last() != "FriendlyAppName") + { + continue; + } + + var path = name.Replace("." + parts.Last(), ""); + + if (skipExistingPresets && _presets.Any(p => p.Path.Equals(path, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + if (!path.StartsWith(winFolder) && Path.Exists(path)) + { + var friendlyName = key.GetValue(name)?.ToString(); + + var app = new GameApp + { + Path = path, + Filename = Path.GetFileName(path), + FriendlyName = friendlyName + }; + + apps.Add(app); + } + } + + apps = apps.DistinctBy(a => a.Path).OrderBy(a => a.FriendlyName).ToList(); + + return apps; + } + } } diff --git a/ColorControl/Services/LG/LgDevice.cs b/ColorControl/Services/LG/LgDevice.cs index dbd06a8..7453fbd 100644 --- a/ColorControl/Services/LG/LgDevice.cs +++ b/ColorControl/Services/LG/LgDevice.cs @@ -14,1349 +14,1353 @@ using System.Threading.Tasks; namespace ColorControl.Services.LG { - class LgDevice - { - public class InvokableAction - { - public Func, Task> AsyncFunction { get; set; } - public string Name { get; set; } - public Type EnumType { get; set; } - public decimal MinValue { get; set; } - public decimal MaxValue { get; set; } - public string Category { get; set; } - public string Title { get; set; } - public int CurrentValue { get; set; } - public int NumberOfValues { get; set; } - public LgPreset Preset { get; set; } - public bool Advanced { get; set; } - public ModelYear FromModelYear { get; set; } = ModelYear.None; - public ModelYear ToModelYear { get; set; } = ModelYear.None; - public List ValueLabels { get; set; } - } - - public class LgDevicePictureSettings - { - public int Backlight { get; set; } - public int Contrast { get; set; } - public int Brightness { get; set; } - public int Color { get; set; } - public int Volume { get; set; } - } - - public enum PowerState - { - Unknown, - Active, - Power_Off, - Suspend, - Active_Standby, - Screen_Off - } - - public enum PowerOffSource - { - Unknown, - App, - Manually - } - - public enum ModelYear - { - None = 0, - SeriesPre2018, - Series2018, - Series2019, - Series2020, - Series2021, - Series2022, - Series2023 - } - - protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); - public static List DefaultActionsOnGameBar = new() { "backlight", "contrast", "brightness", "color" }; - - private static List IreValueLabels = ["IRE 2.5", "IRE 5", "IRE 7.5", "IRE 10", "IRE 15", "IRE 20", "IRE 25", "IRE 30", "IRE 35", "IRE 40", "IRE 45", "IRE 50", "IRE 55", "IRE 60", "IRE 65", "IRE 70", "IRE 75", "IRE 80", "IRE 85", "IRE 90", "IRE 95", "IRE 100"]; - private static List Ire10ptValueLabels = ["IRE 10", "IRE 20", "IRE 30", "IRE 40", "IRE 50", "IRE 60", "IRE 70", "IRE 80", "IRE 90", "IRE 100"]; - - public string Name { get; private set; } - public string IpAddress { get; private set; } - public string MacAddress { get; private set; } - public bool IsCustom { get; private set; } - [JsonIgnore] - public bool IsDummy { get; private set; } - - public bool PowerOnAfterStartup { get; set; } - public bool PowerOnAfterResume { get; set; } - public bool PowerOffOnShutdown { get; set; } - public bool PowerOffOnStandby { get; set; } - public bool PowerOffOnScreenSaver { get; set; } - public int ScreenSaverMinimalDuration { get; set; } - public bool TurnScreenOffOnScreenSaver { get; set; } - public bool HandleManualScreenSaver { get; set; } - public bool PowerOnAfterScreenSaver { get; set; } - public bool TurnScreenOnAfterScreenSaver { get; set; } - public bool PowerOnAfterManualPowerOff { get; set; } - public bool PowerOnByWindows { get; set; } - public bool PowerOffByWindows { get; set; } - public bool TriggersEnabled { get; set; } - public int HDMIPortNumber { get; set; } - - public bool UseSecureConnection { get; set; } = true; - - private List _actionsForGameBar; - - public List ActionsOnGameBar - { - get - { - return _actionsForGameBar; - } - set - { - if (value?.Any() == false) - { - _actionsForGameBar = DefaultActionsOnGameBar; - } - else - { - _actionsForGameBar = value; - } - } - } - - [JsonIgnore] - public PowerState CurrentState { get; set; } - - [JsonIgnore] - private LgTvApi _lgTvApi; - [JsonIgnore] - private bool _justWokeUp; - [JsonIgnore] - public PowerOffSource PoweredOffBy { get; private set; } - - [JsonIgnore] - public bool PoweredOffViaApp { get; private set; } - [JsonIgnore] - private DateTimeOffset _poweredOffViaAppDateTime { get; set; } - [JsonIgnore] - private List _invokableActions = new List(); - [JsonIgnore] - private SemaphoreSlim _connectSemaphore = new SemaphoreSlim(1, 1); - - [JsonIgnore] - public string ModelName { get; private set; } - [JsonIgnore] - public ModelYear Year { get; private set; } - - [JsonIgnore] - public LgDevicePictureSettings PictureSettings { get; private set; } - - [JsonIgnore] - public string CurrentAppId { get; private set; } - - public event EventHandler PictureSettingsChangedEvent; - public event EventHandler PowerStateChangedEvent; - - [JsonIgnore] - private Timer _powerOffTimer; - - [JsonIgnore] - private ServiceManager _serviceManager; - - [JsonConstructor] - public LgDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false) - { - PictureSettings = new LgDevicePictureSettings(); - ActionsOnGameBar = new List(DefaultActionsOnGameBar); - - Name = name; - IpAddress = ipAddress; - MacAddress = macAddress; - IsCustom = isCustom; - IsDummy = isDummy; - TriggersEnabled = true; - - AddInvokableAction("WOL", WakeAction); - AddInvokableAction("Reboot", RebootAction); - AddGenericPictureAction("backlight", minValue: 0, maxValue: 100); - AddGenericPictureAction("brightness", minValue: 0, maxValue: 100, title: "Brightness/Black Level"); - AddGenericPictureAction("contrast", minValue: 0, maxValue: 100); - AddGenericPictureAction("color", minValue: 0, maxValue: 100); - AddGenericPictureAction("pictureMode", typeof(PictureMode), title: "Picture Mode"); - AddGenericPictureAction("sharpness", minValue: 0, maxValue: 50); - //AddGenericPictureAction("dynamicRange", typeof(DynamicRange), category: "dimensionInfo", title: "Dynamic Range"); - AddGenericPictureAction("colorGamut", typeof(ColorGamut), title: "Color Gamut"); - AddGenericPictureAction("dynamicContrast", typeof(OffToHigh), title: "Dynamic Contrast"); - AddGenericPictureAction("superResolution", typeof(OffToHigh), title: "Super Resolution"); - AddGenericPictureAction("gamma", typeof(GammaExp)); - AddGenericPictureAction("colorTemperature", minValue: -50, maxValue: 50, title: "Color Temperature"); - AddGenericPictureAction("whiteBalanceColorTemperature", typeof(WhiteBalanceColorTemperature), title: "White Balance Color Temperature"); - AddGenericPictureAction("eyeComfortMode", typeof(OffToOn), title: "Eye Comfort Mode"); - //AddGenericPictureAction("dynamicColor", typeof(OffToAuto)); - //AddGenericPictureAction("superResolution", typeof(OffToAuto)); - AddGenericPictureAction("peakBrightness", typeof(OffToHigh), title: "Peak Brightness", fromModelYear: ModelYear.Series2019); - AddGenericPictureAction("smoothGradation", typeof(OffToAuto), title: "Smooth Gradation", fromModelYear: ModelYear.Series2019); - AddGenericPictureAction("energySaving", typeof(EnergySaving), title: "Energy Saving"); - AddGenericPictureAction("hdrDynamicToneMapping", typeof(DynamicTonemapping), title: "HDR Dynamic Tone Mapping"); - AddGenericPictureAction("blackLevel", typeof(BlackLevel), title: "HDMI Black Level"); - AddGenericPictureAction("dolbyPrecisionDetail", typeof(OffToOn), title: "Dolby Precision Detail", fromModelYear: ModelYear.Series2022); - - AddGenericPictureAction("ai_Brightness", typeof(OffToOn), title: "AI Brightness", fromModelYear: ModelYear.Series2019, category: "aiPicture"); - AddGenericPictureAction("ai_Genre", typeof(OffToOn), title: "AI Genre Selection", fromModelYear: ModelYear.Series2021, category: "aiPicture"); - AddGenericPictureAction("ai_Picture", typeof(OffToOn), title: "AI Picture Pro", fromModelYear: ModelYear.Series2021, category: "aiPicture"); - - AddGenericPictureAction("arcPerApp", typeof(AspectRatio), title: "Aspect Ratio", category: "aspectRatio"); - AddGenericPictureAction("justScan", typeof(OffToAuto2), title: "Just Scan", category: "aspectRatio"); - AddGenericPictureAction("allDirZoomHRatio", minValue: 0, maxValue: 10, title: "All-Direction Zoom Horizontal Ratio", category: "aspectRatio"); - AddGenericPictureAction("allDirZoomVRatio", minValue: 0, maxValue: 9, title: "All-Direction Zoom Vertical Ratio", category: "aspectRatio"); - AddGenericPictureAction("allDirZoomHPosition", minValue: -10, maxValue: 9, title: "All-Direction Zoom Horizontal Position", category: "aspectRatio"); - AddGenericPictureAction("allDirZoomVPosition", minValue: -9, maxValue: 9, title: "All-Direction Zoom Vertical Position", category: "aspectRatio"); - AddGenericPictureAction("vertZoomVPosition", minValue: -8, maxValue: 9, title: "Vertical Zoom Position", category: "aspectRatio"); - AddGenericPictureAction("vertZoomVRatio", minValue: 0, maxValue: 9, title: "Vertical Zoom Ratio", category: "aspectRatio"); - //AddGenericPictureAction("ambientLightCompensation", typeof(OffToAuto2)); - - AddGenericPictureAction("truMotionMode", typeof(TruMotionMode), title: "TruMotion"); - AddGenericPictureAction("truMotionJudder", minValue: 0, maxValue: 10, title: "TruMotion Judder"); - AddGenericPictureAction("truMotionBlur", minValue: 0, maxValue: 10, title: "TruMotion Blur"); - AddGenericPictureAction("motionProOLED", typeof(OffToHigh), title: "OLED Motion Pro", fromModelYear: ModelYear.Series2019); - AddGenericPictureAction("motionPro", typeof(OffToOn), title: "Motion Pro"); - AddGenericPictureAction("realCinema", typeof(OffToOn), title: "Real Cinema"); - - AddHdmiPictureAction("uhdDeepColorHDMI", typeof(OffToOn), category: "other"); - AddGenericPictureAction("lowLevelAdjustment", minValue: -30, maxValue: 30, category: "other", title: "Fine Tune Dark Areas", fromModelYear: ModelYear.Series2019); - AddGenericPictureAction("blackStabilizer", minValue: -30, maxValue: 30, category: "other", title: "Black Stabilizer", fromModelYear: ModelYear.Series2021); - AddGenericPictureAction("whiteStabilizer", minValue: -30, maxValue: 30, category: "other", title: "White Stabilizer", fromModelYear: ModelYear.Series2021); - AddGenericPictureAction("blueLight", typeof(BlueLight), category: "other", title: "Reduce Blue Light", fromModelYear: ModelYear.Series2021); - AddGenericPictureAction("noiseReduction", typeof(OffToHigh), title: "Noise Reduction"); - AddGenericPictureAction("mpegNoiseReduction", typeof(OffToHigh), title: "MPEG Noise Reduction"); - - AddHdmiPictureAction("gameMode_hdmi", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI1", fromModelYear: ModelYear.Series2021); - AddGenericPictureAction("gameOptimization", typeof(OffToOn), category: "other", title: "VRR & G-Sync", fromModelYear: ModelYear.Series2021); - AddGenericPictureAction("inputOptimization", typeof(InputOptimization), category: "other", title: "Prevent Input Delay(Input Lag)", fromModelYear: ModelYear.Series2021); - AddHdmiPictureAction("gameOptimizationHDMI", typeof(OffToOn), category: "other"); - AddGenericPictureAction("freesync", typeof(OffToOn), category: "other", title: "AMD FreeSync Premium", fromModelYear: ModelYear.Series2020); - AddHdmiPictureAction("freesyncOLEDHDMI", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020); - - AddHdmiPictureAction("444BypassHDMI", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMIX", fromModelYear: ModelYear.Series2023); - AddGenericPictureAction("444BypassHDMINone", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through Non-HDMI", fromModelYear: ModelYear.Series2023); - AddGenericPictureAction("qmsVrr", typeof(OffToOn), category: "other", title: "QMS-VRR", fromModelYear: ModelYear.Series2023); - - AddGenericPictureAction("masterLuminanceLevel", typeof(MasterLuminanceLevel), category: "other"); - AddHdmiPictureAction("colorimetryHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020); - AddHdmiPictureAction("masteringColorHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020); - AddHdmiPictureAction("masteringPeakHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); - AddHdmiPictureAction("maxCLLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); - AddHdmiPictureAction("maxFALLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); - - AddHdmiPictureAction("hdmiPcMode_hdmi", typeof(FalseToTrue), category: "other"); - AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); - AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); - AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); - AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); - AddGenericPictureAction("whiteBalanceBlue10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); - AddGenericPictureAction("whiteBalanceGreen10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); - AddGenericPictureAction("whiteBalanceRed10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); - AddGenericPictureAction("whiteBalanceBlueOffset", minValue: -50, maxValue: 50, title: "White Balance - Blue Offset"); - AddGenericPictureAction("whiteBalanceBlueGain", minValue: -50, maxValue: 50, title: "White Balance - Blue Gain"); - AddGenericPictureAction("whiteBalanceGreenOffset", minValue: -50, maxValue: 50, title: "White Balance - Green Offset"); - AddGenericPictureAction("whiteBalanceGreenGain", minValue: -50, maxValue: 50, title: "White Balance - Green Gain"); - AddGenericPictureAction("whiteBalanceRedOffset", minValue: -50, maxValue: 50, title: "White Balance - Red Offset"); - AddGenericPictureAction("whiteBalanceRedGain", minValue: -50, maxValue: 50, title: "White Balance - Red Gain"); - AddGenericPictureAction("whiteBalanceMethod", typeof(WhiteBalanceMethod), title: "White Balance - Method"); - AddInvokableAction("turnScreenOff", TurnScreenOffAction); - AddInvokableAction("turnScreenOn", TurnScreenOnAction); - - AddInternalPresetAction(new LgPreset("InStart", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "inStart" })); - AddInternalPresetAction(new LgPreset("EzAdjust", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "ezAdjust" })); - //AddInternalPresetAction(new LgPreset("PictureCheck", "com.webos.app.factorywin", null, new { id = "executeFactory", irKey = "pCheck" })); - AddInternalPresetAction(new LgPreset("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true })); - - AddSetDeviceConfigAction("HDMI_1_icon", typeof(HdmiIcon), "HDMI 1 icon"); - AddSetDeviceConfigAction("HDMI_2_icon", typeof(HdmiIcon), "HDMI 2 icon"); - AddSetDeviceConfigAction("HDMI_3_icon", typeof(HdmiIcon), "HDMI 3 icon"); - AddSetDeviceConfigAction("HDMI_4_icon", typeof(HdmiIcon), "HDMI 4 icon"); - - AddVolumeAction("audioVolume", "Volume"); - AddGenericPictureAction("soundMode", typeof(SoundMode), category: "sound", title: "Sound Mode"); - AddGenericPictureAction("smartSoundMode", typeof(OffToOn), category: "sound", title: "Adaptive Sound Control"); - AddGenericPictureAction("soundOutput", typeof(SoundOutput), category: "sound", title: "Sound Output"); - AddGenericPictureAction("autoVolume", typeof(OffToOn), category: "sound", title: "Auto Volume"); - AddGenericPictureAction("virtualSurround", typeof(OffToOn), category: "sound", title: "Virtual Surround"); - - AddGenericPictureAction("screenShift", typeof(OffToOn), title: "Screen Shift"); - AddGenericPictureAction("logoLuminanceAdjust", typeof(LogoLuminance), title: "Logo Luminance"); - - //AddGenericPictureAction("enableToastPopup", typeof(OffToOn), category: "option", title: "enableToastPopup"); - AddSetConfigAction("tv.conti.supportUsedTime", typeof(BoolFalseToTrue), title: "Total Power On Time"); - - AddGenericPictureAction("wolwowlOnOff", typeof(FalseToTrue), category: "network", title: "Wake-On-LAN"); - AddLunaAction("TPC", typeof(BoolFalseToTrue), title: "Temporal Peak Luminance Control (TPC)", ModelYear.Series2020); - AddLunaAction("GSR", typeof(BoolFalseToTrue), title: "Global Sticky/Stress Reduction (GSR)", ModelYear.Series2020); - } - - public LgDevice(LgDeviceDto device) : this(device.Name, device.IpAddress, device.MacAddress, device.IsCustom, device.IsDummy) - { - UpdateOptions(device.Options); - } - - private void UpdateOptions(LgDeviceOptions options) - { - UseSecureConnection = options.UseSecureConnection; - TurnScreenOffOnScreenSaver = options.TurnScreenOffOnScreenSaver; - TurnScreenOnAfterScreenSaver = options.TurnScreenOnAfterScreenSaver; - ScreenSaverMinimalDuration = options.ScreenSaverMinimalDuration; - HandleManualScreenSaver = options.HandleManualScreenSaver; - PowerOffOnScreenSaver = options.PowerOffOnScreenSaver; - PowerOnAfterScreenSaver = options.PowerOnAfterScreenSaver; - PowerOnAfterManualPowerOff = options.PowerOnAfterManualPowerOff; - PowerOnAfterResume = options.PowerOnAfterResume; - PowerOffOnStandby = options.PowerOffOnStandby; - PowerOffOnShutdown = options.PowerOffOnShutdown; - PowerOffByWindows = options.PowerOffByWindows; - PowerOnByWindows = options.PowerOnByWindows; - PowerOnAfterStartup = options.PowerOnAfterStartup; - TriggersEnabled = options.TriggersEnabled; - HDMIPortNumber = options.HDMIPortNumber; - } - - ~LgDevice() - { - ClearPowerOffTask(); - } - - public bool Update(LgDeviceDto device) - { - var reconnect = false; - - Name = device.Name; - if (IpAddress != device.IpAddress) - { - IpAddress = device.IpAddress; - reconnect = true; - } - MacAddress = device.MacAddress; - IsCustom = device.IsCustom; - IsDummy = device.IsDummy; - - UpdateOptions(device.Options); - - return reconnect; - } - - private void AddInvokableAction(string name, Func, Task> function) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = function, - Category = "misc", - Title = name - }; - - _invokableActions.Add(action); - } - - private void AddInternalPresetAction(LgPreset preset) - { - var action = new InvokableAction - { - Name = preset.name, - Preset = preset, - Advanced = true - }; - - _invokableActions.Add(action); - } - - private void AddHdmiPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None) - { - for (var i = 1; i <= 4; i++) - { - var replacedTitle = title?.Replace("HDMIX", $"HDMI{i}"); - - AddGenericPictureAction($"{name}{i}", type, minValue, maxValue, category, replacedTitle, numberOfValues, fromModelYear); - } - } - - private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None, List valueLabels = null) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = GenericPictureAction, - EnumType = type, - MinValue = minValue, - MaxValue = maxValue, - NumberOfValues = numberOfValues, - Category = category, - Title = title == null ? Utils.FirstCharUpperCase(name) : title, - FromModelYear = fromModelYear, - ValueLabels = valueLabels - }; - - _invokableActions.Add(action); - } - - private void AddSetDeviceConfigAction(string name, Type type, string title) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = GenericDeviceConfigAction, - EnumType = type, - Title = title == null ? Utils.FirstCharUpperCase(name) : title - }; - - _invokableActions.Add(action); - } - - private void AddSetConfigAction(string name, Type type, string title) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = GenericSetConfigAction, - EnumType = type, - Title = title == null ? Utils.FirstCharUpperCase(name) : title, - Category = "Config", - Advanced = true - }; - - _invokableActions.Add(action); - } - - private void AddLunaAction(string name, Type type, string title, ModelYear fromModelYear = ModelYear.None) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = GenericLunaAction, - EnumType = type, - Title = title == null ? Utils.FirstCharUpperCase(name) : title, - Category = "Config", - FromModelYear = fromModelYear, - Advanced = true - }; - - _invokableActions.Add(action); - } - - private void AddVolumeAction(string name, string title, ModelYear fromModelYear = ModelYear.None) - { - var action = new InvokableAction - { - Name = name, - AsyncFunction = GenericVolumeAction, - MinValue = 0, - MaxValue = 100, - Title = title == null ? Utils.FirstCharUpperCase(name) : title, - Category = "sound", - FromModelYear = fromModelYear - }; - - _invokableActions.Add(action); - } - - public void AddGameBarAction(string name) - { - if (!ActionsOnGameBar.Contains(name)) - { - ActionsOnGameBar.Add(name); - } - } - - public void RemoveGameBarAction(string name) - { - ActionsOnGameBar.Remove(name); - } - - public override string ToString() - { - //return (IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: ")) + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $" ({IpAddress})" : string.Empty); - return $"{(IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: "))}{Name}{(!string.IsNullOrEmpty(IpAddress) ? ", " + IpAddress : string.Empty)}"; - } - - public async Task Connect(int retries = 3) - { - var locked = _connectSemaphore.CurrentCount == 0; - await _connectSemaphore.WaitAsync(); - try - { - if (locked && _lgTvApi != null) - { - return true; - } - - try - { - DisposeConnection(); - _lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries, UseSecureConnection); - - //Test(); - //_lgTvApi.Test3(); - if (_lgTvApi != null && !Utils.ConsoleOpened) - { - var info = await _lgTvApi.GetSystemInfo("modelName"); - if (info != null) - { - ModelName = info.modelName; - SetModelYear(); - } - - Logger.Debug($"LG TV with ip-address {IpAddress} has model name {ModelName}"); - - await _lgTvApi.SubscribeVolume(VolumeChanged); - await _lgTvApi.SubscribePowerState(PowerStateChanged); - await _lgTvApi.SubscribePictureSettings(PictureSettingsChanged); - await _lgTvApi.SubscribeForegroundApp(ForegroundAppChanged); - - //await _lgTvApi.SetConfig("tv.conti.supportUsedTime", true); - - //await _lgTvApi.Reboot(); - - //var result = await GetPictureSettings(); - - //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, -5, -10, -15, -20, -25, -30, -35, -40, -45, -50, -50, -50, -40, -30, -20, -10, 10, 20, 30, 50 }); - //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); - - //await _lgTvApi.SetInput("HDMI_1"); - //await Task.Delay(2000); - //await _lgTvApi.SetConfig("com.palm.app.settings.enableHdmiPcLabel", true); - //await _lgTvApi.SetInput("HDMI_2"); - } - return _lgTvApi != null; - } - catch (Exception ex) - { - string logMessage = ex.ToLogString(Environment.StackTrace); - Logger.Error($"Error while connecting to {IpAddress}: {logMessage}"); - return false; - } - } - finally - { - _connectSemaphore.Release(); - } - } - - private void SetModelYear() - { - if (ModelName == null || ModelName.Contains("OLED") != true) - { - Year = ModelYear.None; - return; - } - - var normalized = ModelName.Replace("OLED", "").Replace(" ", ""); - - if (normalized.Length > 3) - { - normalized = normalized.Substring(3); - } - - var suffix = normalized.First(); - - Year = suffix switch - { - '6' => ModelYear.SeriesPre2018, - '7' => ModelYear.SeriesPre2018, - '8' => ModelYear.Series2018, - '9' => ModelYear.Series2019, - 'X' => ModelYear.Series2020, - '1' => ModelYear.Series2021, - '2' => ModelYear.Series2022, - '3' => ModelYear.Series2023, - _ => ModelYear.None - }; - - _invokableActions = _invokableActions.Where(a => Year == ModelYear.None || a.FromModelYear == ModelYear.None || Year >= a.FromModelYear).ToList(); - } - - public bool VolumeChanged(dynamic payload) - { - if (payload.volume != null) - { - PictureSettings.Volume = Utils.ParseDynamicAsInt(payload.volume, PictureSettings.Volume); - } - - var volumeAction = _invokableActions.FirstOrDefault(a => a.Name == "audioVolume"); - - if (volumeAction != null) - { - volumeAction.CurrentValue = PictureSettings.Volume; - } - - PictureSettingsChangedEvent?.Invoke(this, EventArgs.Empty); - - return true; - } - - public bool PictureSettingsChanged(dynamic payload) - { - if (payload.settings != null) - { - var settings = payload.settings; - - Logger.Debug($"[{Name}] PictureSettingsChanged: {JsonConvert.SerializeObject(settings)}"); - - if (settings.backlight != null) - { - PictureSettings.Backlight = Utils.ParseDynamicAsInt(settings.backlight, PictureSettings.Backlight); - } - if (settings.contrast != null) - { - PictureSettings.Contrast = Utils.ParseDynamicAsInt(settings.contrast, PictureSettings.Contrast); - } - if (settings.brightness != null) - { - PictureSettings.Brightness = Utils.ParseDynamicAsInt(settings.brightness, PictureSettings.Brightness); - } - if (settings.color != null) - { - PictureSettings.Color = Utils.ParseDynamicAsInt(settings.color, PictureSettings.Color); - } - } - - PictureSettingsChangedEvent?.Invoke(this, EventArgs.Empty); - - return true; - } - - public bool PowerStateChanged(dynamic payload) - { - Logger.Debug($"[{Name}] Power state change: {JsonConvert.SerializeObject(payload)}"); - - var state = payload.state != null ? ((string)payload.state).Replace(' ', '_') : PowerState.Unknown.ToString(); - - PowerState newState; - if (Enum.TryParse(state, out newState)) - { - CurrentState = newState; - - if (CurrentState == PowerState.Active) - { - if (payload.processing == null && (DateTimeOffset.Now - _poweredOffViaAppDateTime).TotalMilliseconds > 500) - { - PoweredOffViaApp = false; - PoweredOffBy = PowerOffSource.Unknown; - _poweredOffViaAppDateTime = DateTimeOffset.MinValue; - } - else if (payload.processing != null && ((string)payload.processing).Equals("Request Power Off", StringComparison.Ordinal)) - { - PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; - } - } - else if (CurrentState != PowerState.Screen_Off) - { - PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; - } - } - else - { - CurrentState = PowerState.Unknown; - Logger.Warn($"Unknown power state: {state}"); - } - - Logger.Debug($"PoweredOffBy: {PoweredOffBy}, PoweredOffViaApp: {PoweredOffViaApp}"); - - PowerStateChangedEvent?.Invoke(this, EventArgs.Empty); - - return true; - } - - public bool ForegroundAppChanged(dynamic payload) - { - Logger.Debug($"[{Name}] ForegroundAppChanged: {JsonConvert.SerializeObject(payload)}"); - - if (payload.appId != null) - { - CurrentAppId = payload.appId; - } - - return true; - } - - public bool IsConnected() - { - return !_lgTvApi?.ConnectionClosed ?? false; - } - - private void DisposeConnection() - { - if (_lgTvApi != null) - { - _lgTvApi.Dispose(); - _lgTvApi = null; - } - } - - public async Task ExecutePreset(LgPreset preset, bool reconnect, LgServiceConfig config) - { - var hasApp = !string.IsNullOrEmpty(preset.appId); - - var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase)); - - if (hasWOL) - { - var connected = await WakeAndConnect(0); - if (!connected) - { - return false; - } - } - - for (var tries = 0; tries <= 1; tries++) - { - if (!await Connected(reconnect || tries == 1)) - { - return false; - } - - if (hasApp) - { - try - { - var @params = preset.AppParams; - if (@params == null && config.ShowAdvancedActions) - { - if (preset.appId.Equals("com.webos.app.softwareupdate")) - { - @params = new { mode = "user", flagUpdate = true }; - } - else if (preset.appId.Equals("com.webos.app.factorywin")) - { - if (preset.name.Contains("ezadjust", StringComparison.OrdinalIgnoreCase)) - { - @params = new { id = "executeFactory", irKey = "ezAdjust" }; - } - else - { - @params = new { id = "executeFactory", irKey = "inStart" }; - } - } - } - - await _lgTvApi.LaunchApp(preset.appId, @params); - } - catch (Exception ex) - { - string logMessage = ex.ToLogString(Environment.StackTrace); - Logger.Error("Error while launching app: " + logMessage); - - if (tries == 0) - { - continue; - } - return false; - } - - if (_justWokeUp) - { - _justWokeUp = false; - await Task.Delay(1000); - } - } - - if (preset.steps.Any()) - { - if (hasApp) - { - await Task.Delay(1500); - } - try - { - await ExecuteSteps(_lgTvApi, preset, config); - } - catch (Exception ex) - { - string logMessage = ex.ToLogString(Environment.StackTrace); - Logger.Error("Error while executing steps: " + logMessage); - - if (tries == 0) - { - continue; - } - return false; - } - } - - return true; - } - - return true; - } - - private async Task ExecuteSteps(LgTvApi api, LgPreset preset, LgServiceConfig config) - { - LgWebOsMouseService mouse = null; - - foreach (var step in preset.steps) - { - var keySpec = step.Split(':'); - - var delay = 10; - var key = step; - if (keySpec.Length == 2) - { - delay = Utils.ParseInt(keySpec[1]); - if (delay > 0) - { - key = keySpec[0]; - } - } - - var index = key.IndexOf("("); - string[] parameters = null; - if (index > -1) - { - var keyValue = key.Split('('); - key = keyValue[0]; - parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';'); - } - - var executeKey = true; - var action = _invokableActions.FirstOrDefault(a => a.Name.Equals(key, StringComparison.OrdinalIgnoreCase)); - if (action != null) - { - await ExecuteActionAsync(action, parameters); - - executeKey = false; - } - if (parameters != null) - { - _serviceManager ??= Program.ServiceProvider.GetRequiredService(); - - if (await _serviceManager.HandleExternalServiceAsync(key, parameters)) - { - executeKey = false; - } - } - - if (executeKey) - { - mouse ??= await api.GetMouse(); - await SendKey(mouse, key); - delay = delay <= 10 ? config.DefaultButtonDelay : delay; - } - - if (delay > 0) - { - await Task.Delay(delay); - } - } - } - - public async Task ExecuteActionAsync(InvokableAction action, string[] parameters) - { - var function = action.AsyncFunction; - if (function == null) - { - return; - } - - if (parameters?.Length > 0) - { - var keyValues = new Dictionary { - { "name", action.Name }, - { "value", parameters }, - { "category", action.Category } - }; - - await function(keyValues); - return; - } - - await function(null); - } - - private async Task SendKey(LgWebOsMouseService mouse, string key) - { - key = key.ToUpper(); - if (key.Length >= 1 && int.TryParse(key[0].ToString(), out _)) - { - key = "_" + key; - } - var button = (ButtonType)Enum.Parse(typeof(ButtonType), key); - await mouse.SendButton(button); - } - - public async Task GetMouseAsync() - { - await CheckConnectionAsync(); - - return await _lgTvApi.GetMouse(); - } - - private async Task CheckConnectionAsync() - { - if (!await Connected()) - { - throw new InvalidOperationException("Not connected"); - } - } - - public async Task> GetApps(bool force = false) - { - if (!force) - { - await Task.Delay(5000); - } - - if (!await Connected(force)) - { - Logger.Debug("Cannot refresh apps: no connection could be made"); - return new List(); - } - - return await _lgTvApi.GetApps(force); - } - - public async Task PerformActionOnScreenSaver() - { - if (!IsPowerOffAllowed()) - { - return true; - } - - if (TurnScreenOffOnScreenSaver) - { - Logger.Debug("Turning screen off on screen saver"); - await _lgTvApi.TurnScreenOff(); - - return true; - } - - Logger.Debug($"Device {Name} is now powering off due to delayed screensaver task"); - - return await PowerOff(true, false); - } - - public async Task PerformActionAfterScreenSaver(int powerOnRetries) - { - if (TurnScreenOffOnScreenSaver && IsConnected()) - { - if (TurnScreenOnAfterScreenSaver) - { - try - { - Logger.Debug("Turning screen on after screen saver"); - await _lgTvApi.TurnScreenOn(); - } - catch (Exception) { } - } - - return true; - } - - return await WakeAndConnectWithRetries(powerOnRetries); - } - - internal async Task PowerOff(bool checkHdmi = false, bool reconnect = true) - { - if (!await Connected(reconnect) || CurrentState != PowerState.Active) - { - return false; - } - - if (checkHdmi && !IsPowerOffAllowed()) - { - return true; - } - - PoweredOffViaApp = true; - _poweredOffViaAppDateTime = DateTimeOffset.Now; - - try - { - await _lgTvApi.TurnOff().WaitAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(true); - } - catch (TimeoutException ex) - { - Logger.Debug($"Timeout when turning off tv: {ex.Message}"); - } - - return true; - } - - public bool IsPowerOffAllowed() - { - if (CurrentAppId != null && HDMIPortNumber != 0 && !CurrentAppId.EndsWith($".hdmi{HDMIPortNumber}", StringComparison.InvariantCulture)) - { - Logger.Debug($"[{Name}] PowerOff/ScreenOff is ignored because current app {CurrentAppId} does not match configured HDMI port {HDMIPortNumber}"); - return false; - } - - return true; - } - - internal async Task TestConnection(int retries = 1) - { - if (!await Connected(true, retries)) - { - return false; - } - - try - { - await _lgTvApi.IsMuted(); - return true; - } - catch (Exception ex) - { - Logger.Error("TestConnection: " + ex.ToLogString()); - return false; - } - } - - internal async Task WakeAndConnect(int wakeDelay = 5000, int connectDelay = 500) - { - try - { - await Task.Delay(wakeDelay); - var result = Wake(); - if (!result) - { - Logger.Debug("WOL failed"); - return false; - } - Logger.Debug("WOL succeeded"); - await Task.Delay(connectDelay); - result = await Connect(1); - Logger.Debug("Connect succeeded: " + result); - return result; - } - catch (Exception e) - { - Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString()); - return false; - } - } - - internal bool Wake() - { - var result = false; - - if (MacAddress != null) - { - var wolService = Program.ServiceProvider.GetRequiredService(); - - result = wolService.SendWol(MacAddress, IpAddress); - _justWokeUp = true; - } - else - { - Logger.Debug("Cannot wake device: the device has no MAC-address"); - } - - return result; - } - - internal async Task WakeAndConnectWithRetries(int retries = 5) - { - var wakeDelay = 0; - var maxRetries = retries <= 1 ? 5 : retries; - - var result = false; - for (var retry = 0; retry < maxRetries && !result; retry++) - { - Logger.Debug($"WakeAndConnectWithRetries: attempt {retry + 1} of {maxRetries}..."); - - var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - result = await WakeAndConnect(retry == 0 ? wakeDelay : 0); - ms = DateTimeOffset.Now.ToUnixTimeMilliseconds() - ms; - if (!result) - { - var delay = 2000 - ms; - if (delay > 0) - { - await Task.Delay((int)delay); - } - } - } - - return result; - } - - public List GetInvokableActions(bool includedAdvanced = false) - { - return _invokableActions.Where(a => includedAdvanced || !a.Advanced).ToList(); - } - - public List GetInvokableActionsForGameBar() - { - var actions = GetInvokableActions(); - return actions.Where(a => - a.Category == "picture" && (a.EnumType != null && a.EnumType != typeof(PictureMode) || a.MinValue >= 0 && a.MaxValue > 0) || - a.Name == "audioVolume" - ) - .ToList(); - } - - public List GetActionsForGameBar() - { - var actions = GetInvokableActions(); - return actions.Where(a => ActionsOnGameBar.Contains(a.Name)).ToList(); - } - - public void Test() - { - try - { - //_lgTvApi.GetServiceList(); - //_lgTvApi?.Test(); - } - catch (Exception ex) - { - Logger.Error("TEST: " + ex.ToLogString()); - } - } - - private async Task Connected(bool reconnect = false, int retries = 3) - { - if (reconnect || !IsConnected() || !string.Equals(_lgTvApi.GetIpAddress(), IpAddress)) - { - if (!await Connect(retries)) - { - Logger.Debug("Cannot apply LG-preset: no connection could be made"); - return false; - } - } - return true; - } - - internal void ConvertToCustom() - { - IsCustom = true; - } - - public bool IsUsingHDRPictureMode() - { - // Temporary workaround because I cannot read the picture mode at this time - return PictureSettings.Contrast == 100; - } - - internal async Task SetBacklight(int backlight) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetSystemSettings("backlight", backlight.ToString()); - } - - internal async Task SetContrast(int contrast) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetSystemSettings("contrast", contrast.ToString()); - } - - public async Task SetOLEDMotionPro(string mode) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetConfig("tv.model.motionProMode", mode); - } - - public async Task SetSvcMenuFlag(bool enabled) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetSystemSettings("svcMenuFlag", enabled, "other"); - } - - internal async Task SetConfig(string key, object value) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetConfig(key, value); - } - internal async Task SetSystemSettings(string name, string value) - { - await CheckConnectionAsync(); - - await _lgTvApi.SetSystemSettings(name, value); - - UpdateCurrentValueOfAction(name, value); - } - - internal async Task GetPictureSettings() - { - //var keys = new[] { "backlight", "brightness", "contrast", "color", "pictureMode", "colorGamut", "dynamicContrast", "peakBrightness", "smoothGradation", "energySaving", "motionProOLED" }; - //var keys = new[] { "backlight", "brightness", "contrast", "color" }; - - await CheckConnectionAsync(); - - return await _lgTvApi.GetSystemSettings2("picture"); - } + class LgDevice + { + public class InvokableAction + { + public Func, Task> AsyncFunction { get; set; } + public string Name { get; set; } + public Type EnumType { get; set; } + public decimal MinValue { get; set; } + public decimal MaxValue { get; set; } + public string Category { get; set; } + public string Title { get; set; } + public int CurrentValue { get; set; } + public int NumberOfValues { get; set; } + public LgPreset Preset { get; set; } + public bool Advanced { get; set; } + public ModelYear FromModelYear { get; set; } = ModelYear.None; + public ModelYear ToModelYear { get; set; } = ModelYear.None; + public List ValueLabels { get; set; } + } + + public class LgDevicePictureSettings + { + public int Backlight { get; set; } + public int Contrast { get; set; } + public int Brightness { get; set; } + public int Color { get; set; } + public int Volume { get; set; } + } + + public enum PowerState + { + Unknown, + Active, + Power_Off, + Suspend, + Active_Standby, + Screen_Off + } + + public enum PowerOffSource + { + Unknown, + App, + Manually + } + + public enum ModelYear + { + None = 0, + SeriesPre2018, + Series2018, + Series2019, + Series2020, + Series2021, + Series2022, + Series2023 + } + + protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + public static List DefaultActionsOnGameBar = new() { "backlight", "contrast", "brightness", "color" }; + + private static List IreValueLabels = ["IRE 2.5", "IRE 5", "IRE 7.5", "IRE 10", "IRE 15", "IRE 20", "IRE 25", "IRE 30", "IRE 35", "IRE 40", "IRE 45", "IRE 50", "IRE 55", "IRE 60", "IRE 65", "IRE 70", "IRE 75", "IRE 80", "IRE 85", "IRE 90", "IRE 95", "IRE 100"]; + private static List Ire10ptValueLabels = ["IRE 10", "IRE 20", "IRE 30", "IRE 40", "IRE 50", "IRE 60", "IRE 70", "IRE 80", "IRE 90", "IRE 100"]; + + public string Name { get; private set; } + public string IpAddress { get; private set; } + public string MacAddress { get; private set; } + public bool IsCustom { get; private set; } + [JsonIgnore] + public bool IsDummy { get; private set; } + + public bool PowerOnAfterStartup { get; set; } + public bool PowerOnAfterResume { get; set; } + public bool PowerOffOnShutdown { get; set; } + public bool PowerOffOnStandby { get; set; } + public bool PowerOffOnScreenSaver { get; set; } + public int ScreenSaverMinimalDuration { get; set; } + public bool TurnScreenOffOnScreenSaver { get; set; } + public bool HandleManualScreenSaver { get; set; } + public bool PowerOnAfterScreenSaver { get; set; } + public bool TurnScreenOnAfterScreenSaver { get; set; } + public bool PowerOnAfterManualPowerOff { get; set; } + public bool PowerOnByWindows { get; set; } + public bool PowerOffByWindows { get; set; } + public bool PowerOnAfterSessionUnlock { get; set; } + public bool PowerOffAfterSessionLock { get; set; } + public bool TriggersEnabled { get; set; } + public int HDMIPortNumber { get; set; } + + public bool UseSecureConnection { get; set; } = true; + + private List _actionsForGameBar; + + public List ActionsOnGameBar + { + get + { + return _actionsForGameBar; + } + set + { + if (value?.Any() == false) + { + _actionsForGameBar = DefaultActionsOnGameBar; + } + else + { + _actionsForGameBar = value; + } + } + } + + [JsonIgnore] + public PowerState CurrentState { get; set; } + + [JsonIgnore] + private LgTvApi _lgTvApi; + [JsonIgnore] + private bool _justWokeUp; + [JsonIgnore] + public PowerOffSource PoweredOffBy { get; private set; } + + [JsonIgnore] + public bool PoweredOffViaApp { get; private set; } + [JsonIgnore] + private DateTimeOffset _poweredOffViaAppDateTime { get; set; } + [JsonIgnore] + private List _invokableActions = new List(); + [JsonIgnore] + private SemaphoreSlim _connectSemaphore = new SemaphoreSlim(1, 1); + + [JsonIgnore] + public string ModelName { get; private set; } + [JsonIgnore] + public ModelYear Year { get; private set; } + + [JsonIgnore] + public LgDevicePictureSettings PictureSettings { get; private set; } + + [JsonIgnore] + public string CurrentAppId { get; private set; } + + public event EventHandler PictureSettingsChangedEvent; + public event EventHandler PowerStateChangedEvent; + + [JsonIgnore] + private Timer _powerOffTimer; + + [JsonIgnore] + private ServiceManager _serviceManager; + + [JsonConstructor] + public LgDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false) + { + PictureSettings = new LgDevicePictureSettings(); + ActionsOnGameBar = new List(DefaultActionsOnGameBar); + + Name = name; + IpAddress = ipAddress; + MacAddress = macAddress; + IsCustom = isCustom; + IsDummy = isDummy; + TriggersEnabled = true; + + AddInvokableAction("WOL", WakeAction); + AddInvokableAction("Reboot", RebootAction); + AddGenericPictureAction("backlight", minValue: 0, maxValue: 100); + AddGenericPictureAction("brightness", minValue: 0, maxValue: 100, title: "Brightness/Black Level"); + AddGenericPictureAction("contrast", minValue: 0, maxValue: 100); + AddGenericPictureAction("color", minValue: 0, maxValue: 100); + AddGenericPictureAction("pictureMode", typeof(PictureMode), title: "Picture Mode"); + AddGenericPictureAction("sharpness", minValue: 0, maxValue: 50); + //AddGenericPictureAction("dynamicRange", typeof(DynamicRange), category: "dimensionInfo", title: "Dynamic Range"); + AddGenericPictureAction("colorGamut", typeof(ColorGamut), title: "Color Gamut"); + AddGenericPictureAction("dynamicContrast", typeof(OffToHigh), title: "Dynamic Contrast"); + AddGenericPictureAction("superResolution", typeof(OffToHigh), title: "Super Resolution"); + AddGenericPictureAction("gamma", typeof(GammaExp)); + AddGenericPictureAction("colorTemperature", minValue: -50, maxValue: 50, title: "Color Temperature"); + AddGenericPictureAction("whiteBalanceColorTemperature", typeof(WhiteBalanceColorTemperature), title: "White Balance Color Temperature"); + AddGenericPictureAction("eyeComfortMode", typeof(OffToOn), title: "Eye Comfort Mode"); + //AddGenericPictureAction("dynamicColor", typeof(OffToAuto)); + //AddGenericPictureAction("superResolution", typeof(OffToAuto)); + AddGenericPictureAction("peakBrightness", typeof(OffToHigh), title: "Peak Brightness", fromModelYear: ModelYear.Series2019); + AddGenericPictureAction("smoothGradation", typeof(OffToAuto), title: "Smooth Gradation", fromModelYear: ModelYear.Series2019); + AddGenericPictureAction("energySaving", typeof(EnergySaving), title: "Energy Saving"); + AddGenericPictureAction("hdrDynamicToneMapping", typeof(DynamicTonemapping), title: "HDR Dynamic Tone Mapping"); + AddGenericPictureAction("blackLevel", typeof(BlackLevel), title: "HDMI Black Level"); + AddGenericPictureAction("dolbyPrecisionDetail", typeof(OffToOn), title: "Dolby Precision Detail", fromModelYear: ModelYear.Series2022); + + AddGenericPictureAction("ai_Brightness", typeof(OffToOn), title: "AI Brightness", fromModelYear: ModelYear.Series2019, category: "aiPicture"); + AddGenericPictureAction("ai_Genre", typeof(OffToOn), title: "AI Genre Selection", fromModelYear: ModelYear.Series2021, category: "aiPicture"); + AddGenericPictureAction("ai_Picture", typeof(OffToOn), title: "AI Picture Pro", fromModelYear: ModelYear.Series2021, category: "aiPicture"); + + AddGenericPictureAction("arcPerApp", typeof(AspectRatio), title: "Aspect Ratio", category: "aspectRatio"); + AddGenericPictureAction("justScan", typeof(OffToAuto2), title: "Just Scan", category: "aspectRatio"); + AddGenericPictureAction("allDirZoomHRatio", minValue: 0, maxValue: 10, title: "All-Direction Zoom Horizontal Ratio", category: "aspectRatio"); + AddGenericPictureAction("allDirZoomVRatio", minValue: 0, maxValue: 9, title: "All-Direction Zoom Vertical Ratio", category: "aspectRatio"); + AddGenericPictureAction("allDirZoomHPosition", minValue: -10, maxValue: 9, title: "All-Direction Zoom Horizontal Position", category: "aspectRatio"); + AddGenericPictureAction("allDirZoomVPosition", minValue: -9, maxValue: 9, title: "All-Direction Zoom Vertical Position", category: "aspectRatio"); + AddGenericPictureAction("vertZoomVPosition", minValue: -8, maxValue: 9, title: "Vertical Zoom Position", category: "aspectRatio"); + AddGenericPictureAction("vertZoomVRatio", minValue: 0, maxValue: 9, title: "Vertical Zoom Ratio", category: "aspectRatio"); + //AddGenericPictureAction("ambientLightCompensation", typeof(OffToAuto2)); + + AddGenericPictureAction("truMotionMode", typeof(TruMotionMode), title: "TruMotion"); + AddGenericPictureAction("truMotionJudder", minValue: 0, maxValue: 10, title: "TruMotion Judder"); + AddGenericPictureAction("truMotionBlur", minValue: 0, maxValue: 10, title: "TruMotion Blur"); + AddGenericPictureAction("motionProOLED", typeof(OffToHigh), title: "OLED Motion Pro", fromModelYear: ModelYear.Series2019); + AddGenericPictureAction("motionPro", typeof(OffToOn), title: "Motion Pro"); + AddGenericPictureAction("realCinema", typeof(OffToOn), title: "Real Cinema"); + + AddHdmiPictureAction("uhdDeepColorHDMI", typeof(OffToOn), category: "other"); + AddGenericPictureAction("lowLevelAdjustment", minValue: -30, maxValue: 30, category: "other", title: "Fine Tune Dark Areas", fromModelYear: ModelYear.Series2019); + AddGenericPictureAction("blackStabilizer", minValue: -30, maxValue: 30, category: "other", title: "Black Stabilizer", fromModelYear: ModelYear.Series2021); + AddGenericPictureAction("whiteStabilizer", minValue: -30, maxValue: 30, category: "other", title: "White Stabilizer", fromModelYear: ModelYear.Series2021); + AddGenericPictureAction("blueLight", typeof(BlueLight), category: "other", title: "Reduce Blue Light", fromModelYear: ModelYear.Series2021); + AddGenericPictureAction("noiseReduction", typeof(OffToHigh), title: "Noise Reduction"); + AddGenericPictureAction("mpegNoiseReduction", typeof(OffToHigh), title: "MPEG Noise Reduction"); + + AddHdmiPictureAction("gameMode_hdmi", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI1", fromModelYear: ModelYear.Series2021); + AddGenericPictureAction("gameOptimization", typeof(OffToOn), category: "other", title: "VRR & G-Sync", fromModelYear: ModelYear.Series2021); + AddGenericPictureAction("inputOptimization", typeof(InputOptimization), category: "other", title: "Prevent Input Delay(Input Lag)", fromModelYear: ModelYear.Series2021); + AddHdmiPictureAction("gameOptimizationHDMI", typeof(OffToOn), category: "other"); + AddGenericPictureAction("freesync", typeof(OffToOn), category: "other", title: "AMD FreeSync Premium", fromModelYear: ModelYear.Series2020); + AddHdmiPictureAction("freesyncOLEDHDMI", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020); + + AddHdmiPictureAction("444BypassHDMI", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMIX", fromModelYear: ModelYear.Series2023); + AddGenericPictureAction("444BypassHDMINone", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through Non-HDMI", fromModelYear: ModelYear.Series2023); + AddGenericPictureAction("qmsVrr", typeof(OffToOn), category: "other", title: "QMS-VRR", fromModelYear: ModelYear.Series2023); + + AddGenericPictureAction("masterLuminanceLevel", typeof(MasterLuminanceLevel), category: "other"); + AddHdmiPictureAction("colorimetryHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020); + AddHdmiPictureAction("masteringColorHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020); + AddHdmiPictureAction("masteringPeakHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); + AddHdmiPictureAction("maxCLLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); + AddHdmiPictureAction("maxFALLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020); + + AddHdmiPictureAction("hdmiPcMode_hdmi", typeof(FalseToTrue), category: "other"); + AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); + AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); + AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); + AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels); + AddGenericPictureAction("whiteBalanceBlue10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); + AddGenericPictureAction("whiteBalanceGreen10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); + AddGenericPictureAction("whiteBalanceRed10pt", minValue: -50, maxValue: 50, numberOfValues: 10, valueLabels: Ire10ptValueLabels); + AddGenericPictureAction("whiteBalanceBlueOffset", minValue: -50, maxValue: 50, title: "White Balance - Blue Offset"); + AddGenericPictureAction("whiteBalanceBlueGain", minValue: -50, maxValue: 50, title: "White Balance - Blue Gain"); + AddGenericPictureAction("whiteBalanceGreenOffset", minValue: -50, maxValue: 50, title: "White Balance - Green Offset"); + AddGenericPictureAction("whiteBalanceGreenGain", minValue: -50, maxValue: 50, title: "White Balance - Green Gain"); + AddGenericPictureAction("whiteBalanceRedOffset", minValue: -50, maxValue: 50, title: "White Balance - Red Offset"); + AddGenericPictureAction("whiteBalanceRedGain", minValue: -50, maxValue: 50, title: "White Balance - Red Gain"); + AddGenericPictureAction("whiteBalanceMethod", typeof(WhiteBalanceMethod), title: "White Balance - Method"); + AddInvokableAction("turnScreenOff", TurnScreenOffAction); + AddInvokableAction("turnScreenOn", TurnScreenOnAction); + + AddInternalPresetAction(new LgPreset("InStart", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "inStart" })); + AddInternalPresetAction(new LgPreset("EzAdjust", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "ezAdjust" })); + //AddInternalPresetAction(new LgPreset("PictureCheck", "com.webos.app.factorywin", null, new { id = "executeFactory", irKey = "pCheck" })); + AddInternalPresetAction(new LgPreset("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true })); + + AddSetDeviceConfigAction("HDMI_1_icon", typeof(HdmiIcon), "HDMI 1 icon"); + AddSetDeviceConfigAction("HDMI_2_icon", typeof(HdmiIcon), "HDMI 2 icon"); + AddSetDeviceConfigAction("HDMI_3_icon", typeof(HdmiIcon), "HDMI 3 icon"); + AddSetDeviceConfigAction("HDMI_4_icon", typeof(HdmiIcon), "HDMI 4 icon"); + + AddVolumeAction("audioVolume", "Volume"); + AddGenericPictureAction("soundMode", typeof(SoundMode), category: "sound", title: "Sound Mode"); + AddGenericPictureAction("smartSoundMode", typeof(OffToOn), category: "sound", title: "Adaptive Sound Control"); + AddGenericPictureAction("soundOutput", typeof(SoundOutput), category: "sound", title: "Sound Output"); + AddGenericPictureAction("autoVolume", typeof(OffToOn), category: "sound", title: "Auto Volume"); + AddGenericPictureAction("virtualSurround", typeof(OffToOn), category: "sound", title: "Virtual Surround"); + + AddGenericPictureAction("screenShift", typeof(OffToOn), title: "Screen Shift"); + AddGenericPictureAction("logoLuminanceAdjust", typeof(LogoLuminance), title: "Logo Luminance"); + + //AddGenericPictureAction("enableToastPopup", typeof(OffToOn), category: "option", title: "enableToastPopup"); + AddSetConfigAction("tv.conti.supportUsedTime", typeof(BoolFalseToTrue), title: "Total Power On Time"); + + AddGenericPictureAction("wolwowlOnOff", typeof(FalseToTrue), category: "network", title: "Wake-On-LAN"); + AddLunaAction("TPC", typeof(BoolFalseToTrue), title: "Temporal Peak Luminance Control (TPC)", ModelYear.Series2020); + AddLunaAction("GSR", typeof(BoolFalseToTrue), title: "Global Sticky/Stress Reduction (GSR)", ModelYear.Series2020); + } + + public LgDevice(LgDeviceDto device) : this(device.Name, device.IpAddress, device.MacAddress, device.IsCustom, device.IsDummy) + { + UpdateOptions(device.Options); + } + + private void UpdateOptions(LgDeviceOptions options) + { + UseSecureConnection = options.UseSecureConnection; + TurnScreenOffOnScreenSaver = options.TurnScreenOffOnScreenSaver; + TurnScreenOnAfterScreenSaver = options.TurnScreenOnAfterScreenSaver; + ScreenSaverMinimalDuration = options.ScreenSaverMinimalDuration; + HandleManualScreenSaver = options.HandleManualScreenSaver; + PowerOffOnScreenSaver = options.PowerOffOnScreenSaver; + PowerOnAfterScreenSaver = options.PowerOnAfterScreenSaver; + PowerOnAfterManualPowerOff = options.PowerOnAfterManualPowerOff; + PowerOnAfterResume = options.PowerOnAfterResume; + PowerOffOnStandby = options.PowerOffOnStandby; + PowerOffOnShutdown = options.PowerOffOnShutdown; + PowerOffByWindows = options.PowerOffByWindows; + PowerOnByWindows = options.PowerOnByWindows; + PowerOnAfterStartup = options.PowerOnAfterStartup; + PowerOnAfterSessionUnlock = options.PowerOnAfterSessionUnlock; + PowerOffAfterSessionLock = options.PowerOffAfterSessionLock; + TriggersEnabled = options.TriggersEnabled; + HDMIPortNumber = options.HDMIPortNumber; + } + + ~LgDevice() + { + ClearPowerOffTask(); + } + + public bool Update(LgDeviceDto device) + { + var reconnect = false; + + Name = device.Name; + if (IpAddress != device.IpAddress) + { + IpAddress = device.IpAddress; + reconnect = true; + } + MacAddress = device.MacAddress; + IsCustom = device.IsCustom; + IsDummy = device.IsDummy; + + UpdateOptions(device.Options); + + return reconnect; + } + + private void AddInvokableAction(string name, Func, Task> function) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = function, + Category = "misc", + Title = name + }; + + _invokableActions.Add(action); + } + + private void AddInternalPresetAction(LgPreset preset) + { + var action = new InvokableAction + { + Name = preset.name, + Preset = preset, + Advanced = true + }; + + _invokableActions.Add(action); + } + + private void AddHdmiPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None) + { + for (var i = 1; i <= 4; i++) + { + var replacedTitle = title?.Replace("HDMIX", $"HDMI{i}"); + + AddGenericPictureAction($"{name}{i}", type, minValue, maxValue, category, replacedTitle, numberOfValues, fromModelYear); + } + } + + private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None, List valueLabels = null) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericPictureAction, + EnumType = type, + MinValue = minValue, + MaxValue = maxValue, + NumberOfValues = numberOfValues, + Category = category, + Title = title == null ? Utils.FirstCharUpperCase(name) : title, + FromModelYear = fromModelYear, + ValueLabels = valueLabels + }; + + _invokableActions.Add(action); + } + + private void AddSetDeviceConfigAction(string name, Type type, string title) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericDeviceConfigAction, + EnumType = type, + Title = title == null ? Utils.FirstCharUpperCase(name) : title + }; + + _invokableActions.Add(action); + } + + private void AddSetConfigAction(string name, Type type, string title) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericSetConfigAction, + EnumType = type, + Title = title == null ? Utils.FirstCharUpperCase(name) : title, + Category = "Config", + Advanced = true + }; + + _invokableActions.Add(action); + } + + private void AddLunaAction(string name, Type type, string title, ModelYear fromModelYear = ModelYear.None) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericLunaAction, + EnumType = type, + Title = title == null ? Utils.FirstCharUpperCase(name) : title, + Category = "Config", + FromModelYear = fromModelYear, + Advanced = true + }; + + _invokableActions.Add(action); + } + + private void AddVolumeAction(string name, string title, ModelYear fromModelYear = ModelYear.None) + { + var action = new InvokableAction + { + Name = name, + AsyncFunction = GenericVolumeAction, + MinValue = 0, + MaxValue = 100, + Title = title == null ? Utils.FirstCharUpperCase(name) : title, + Category = "sound", + FromModelYear = fromModelYear + }; + + _invokableActions.Add(action); + } + + public void AddGameBarAction(string name) + { + if (!ActionsOnGameBar.Contains(name)) + { + ActionsOnGameBar.Add(name); + } + } + + public void RemoveGameBarAction(string name) + { + ActionsOnGameBar.Remove(name); + } + + public override string ToString() + { + //return (IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: ")) + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $" ({IpAddress})" : string.Empty); + return $"{(IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: "))}{Name}{(!string.IsNullOrEmpty(IpAddress) ? ", " + IpAddress : string.Empty)}"; + } + + public async Task Connect(int retries = 3) + { + var locked = _connectSemaphore.CurrentCount == 0; + await _connectSemaphore.WaitAsync(); + try + { + if (locked && _lgTvApi != null) + { + return true; + } + + try + { + DisposeConnection(); + _lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries, UseSecureConnection); + + //Test(); + //_lgTvApi.Test3(); + if (_lgTvApi != null && !Utils.ConsoleOpened) + { + var info = await _lgTvApi.GetSystemInfo("modelName"); + if (info != null) + { + ModelName = info.modelName; + SetModelYear(); + } + + Logger.Debug($"LG TV with ip-address {IpAddress} has model name {ModelName}"); + + await _lgTvApi.SubscribeVolume(VolumeChanged); + await _lgTvApi.SubscribePowerState(PowerStateChanged); + await _lgTvApi.SubscribePictureSettings(PictureSettingsChanged); + await _lgTvApi.SubscribeForegroundApp(ForegroundAppChanged); + + //await _lgTvApi.SetConfig("tv.conti.supportUsedTime", true); + + //await _lgTvApi.Reboot(); + + //var result = await GetPictureSettings(); + + //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, -5, -10, -15, -20, -25, -30, -35, -40, -45, -50, -50, -50, -40, -30, -20, -10, 10, 20, 30, 50 }); + //await _lgTvApi.SetSystemSettings("adjustingLuminance", new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }); + + //await _lgTvApi.SetInput("HDMI_1"); + //await Task.Delay(2000); + //await _lgTvApi.SetConfig("com.palm.app.settings.enableHdmiPcLabel", true); + //await _lgTvApi.SetInput("HDMI_2"); + } + return _lgTvApi != null; + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error($"Error while connecting to {IpAddress}: {logMessage}"); + return false; + } + } + finally + { + _connectSemaphore.Release(); + } + } + + private void SetModelYear() + { + if (ModelName == null || ModelName.Contains("OLED") != true) + { + Year = ModelYear.None; + return; + } + + var normalized = ModelName.Replace("OLED", "").Replace(" ", ""); + + if (normalized.Length > 3) + { + normalized = normalized.Substring(3); + } + + var suffix = normalized.First(); + + Year = suffix switch + { + '6' => ModelYear.SeriesPre2018, + '7' => ModelYear.SeriesPre2018, + '8' => ModelYear.Series2018, + '9' => ModelYear.Series2019, + 'X' => ModelYear.Series2020, + '1' => ModelYear.Series2021, + '2' => ModelYear.Series2022, + '3' => ModelYear.Series2023, + _ => ModelYear.None + }; + + _invokableActions = _invokableActions.Where(a => Year == ModelYear.None || a.FromModelYear == ModelYear.None || Year >= a.FromModelYear).ToList(); + } + + public bool VolumeChanged(dynamic payload) + { + if (payload.volume != null) + { + PictureSettings.Volume = Utils.ParseDynamicAsInt(payload.volume, PictureSettings.Volume); + } + + var volumeAction = _invokableActions.FirstOrDefault(a => a.Name == "audioVolume"); + + if (volumeAction != null) + { + volumeAction.CurrentValue = PictureSettings.Volume; + } + + PictureSettingsChangedEvent?.Invoke(this, EventArgs.Empty); + + return true; + } + + public bool PictureSettingsChanged(dynamic payload) + { + if (payload.settings != null) + { + var settings = payload.settings; + + Logger.Debug($"[{Name}] PictureSettingsChanged: {JsonConvert.SerializeObject(settings)}"); + + if (settings.backlight != null) + { + PictureSettings.Backlight = Utils.ParseDynamicAsInt(settings.backlight, PictureSettings.Backlight); + } + if (settings.contrast != null) + { + PictureSettings.Contrast = Utils.ParseDynamicAsInt(settings.contrast, PictureSettings.Contrast); + } + if (settings.brightness != null) + { + PictureSettings.Brightness = Utils.ParseDynamicAsInt(settings.brightness, PictureSettings.Brightness); + } + if (settings.color != null) + { + PictureSettings.Color = Utils.ParseDynamicAsInt(settings.color, PictureSettings.Color); + } + } + + PictureSettingsChangedEvent?.Invoke(this, EventArgs.Empty); + + return true; + } + + public bool PowerStateChanged(dynamic payload) + { + Logger.Debug($"[{Name}] Power state change: {JsonConvert.SerializeObject(payload)}"); + + var state = payload.state != null ? ((string)payload.state).Replace(' ', '_') : PowerState.Unknown.ToString(); + + PowerState newState; + if (Enum.TryParse(state, out newState)) + { + CurrentState = newState; + + if (CurrentState == PowerState.Active) + { + if (payload.processing == null && (DateTimeOffset.Now - _poweredOffViaAppDateTime).TotalMilliseconds > 500) + { + PoweredOffViaApp = false; + PoweredOffBy = PowerOffSource.Unknown; + _poweredOffViaAppDateTime = DateTimeOffset.MinValue; + } + else if (payload.processing != null && ((string)payload.processing).Equals("Request Power Off", StringComparison.Ordinal)) + { + PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; + } + } + else if (CurrentState != PowerState.Screen_Off) + { + PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually; + } + } + else + { + CurrentState = PowerState.Unknown; + Logger.Warn($"Unknown power state: {state}"); + } + + Logger.Debug($"PoweredOffBy: {PoweredOffBy}, PoweredOffViaApp: {PoweredOffViaApp}"); + + PowerStateChangedEvent?.Invoke(this, EventArgs.Empty); + + return true; + } + + public bool ForegroundAppChanged(dynamic payload) + { + Logger.Debug($"[{Name}] ForegroundAppChanged: {JsonConvert.SerializeObject(payload)}"); + + if (payload.appId != null) + { + CurrentAppId = payload.appId; + } + + return true; + } + + public bool IsConnected() + { + return !_lgTvApi?.ConnectionClosed ?? false; + } + + private void DisposeConnection() + { + if (_lgTvApi != null) + { + _lgTvApi.Dispose(); + _lgTvApi = null; + } + } + + public async Task ExecutePreset(LgPreset preset, bool reconnect, LgServiceConfig config) + { + var hasApp = !string.IsNullOrEmpty(preset.appId); + + var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase)); + + if (hasWOL) + { + var connected = await WakeAndConnect(0); + if (!connected) + { + return false; + } + } + + for (var tries = 0; tries <= 1; tries++) + { + if (!await Connected(reconnect || tries == 1)) + { + return false; + } + + if (hasApp) + { + try + { + var @params = preset.AppParams; + if (@params == null && config.ShowAdvancedActions) + { + if (preset.appId.Equals("com.webos.app.softwareupdate")) + { + @params = new { mode = "user", flagUpdate = true }; + } + else if (preset.appId.Equals("com.webos.app.factorywin")) + { + if (preset.name.Contains("ezadjust", StringComparison.OrdinalIgnoreCase)) + { + @params = new { id = "executeFactory", irKey = "ezAdjust" }; + } + else + { + @params = new { id = "executeFactory", irKey = "inStart" }; + } + } + } + + await _lgTvApi.LaunchApp(preset.appId, @params); + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error("Error while launching app: " + logMessage); + + if (tries == 0) + { + continue; + } + return false; + } + + if (_justWokeUp) + { + _justWokeUp = false; + await Task.Delay(1000); + } + } + + if (preset.steps.Any()) + { + if (hasApp) + { + await Task.Delay(1500); + } + try + { + await ExecuteSteps(_lgTvApi, preset, config); + } + catch (Exception ex) + { + string logMessage = ex.ToLogString(Environment.StackTrace); + Logger.Error("Error while executing steps: " + logMessage); + + if (tries == 0) + { + continue; + } + return false; + } + } + + return true; + } + + return true; + } + + private async Task ExecuteSteps(LgTvApi api, LgPreset preset, LgServiceConfig config) + { + LgWebOsMouseService mouse = null; + + foreach (var step in preset.steps) + { + var keySpec = step.Split(':'); + + var delay = 10; + var key = step; + if (keySpec.Length == 2) + { + delay = Utils.ParseInt(keySpec[1]); + if (delay > 0) + { + key = keySpec[0]; + } + } + + var index = key.IndexOf("("); + string[] parameters = null; + if (index > -1) + { + var keyValue = key.Split('('); + key = keyValue[0]; + parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';'); + } + + var executeKey = true; + var action = _invokableActions.FirstOrDefault(a => a.Name.Equals(key, StringComparison.OrdinalIgnoreCase)); + if (action != null) + { + await ExecuteActionAsync(action, parameters); + + executeKey = false; + } + if (parameters != null) + { + _serviceManager ??= Program.ServiceProvider.GetRequiredService(); + + if (await _serviceManager.HandleExternalServiceAsync(key, parameters)) + { + executeKey = false; + } + } + + if (executeKey) + { + mouse ??= await api.GetMouse(); + await SendKey(mouse, key); + delay = delay <= 10 ? config.DefaultButtonDelay : delay; + } + + if (delay > 0) + { + await Task.Delay(delay); + } + } + } + + public async Task ExecuteActionAsync(InvokableAction action, string[] parameters) + { + var function = action.AsyncFunction; + if (function == null) + { + return; + } + + if (parameters?.Length > 0) + { + var keyValues = new Dictionary { + { "name", action.Name }, + { "value", parameters }, + { "category", action.Category } + }; + + await function(keyValues); + return; + } + + await function(null); + } + + private async Task SendKey(LgWebOsMouseService mouse, string key) + { + key = key.ToUpper(); + if (key.Length >= 1 && int.TryParse(key[0].ToString(), out _)) + { + key = "_" + key; + } + var button = (ButtonType)Enum.Parse(typeof(ButtonType), key); + await mouse.SendButton(button); + } + + public async Task GetMouseAsync() + { + await CheckConnectionAsync(); + + return await _lgTvApi.GetMouse(); + } + + private async Task CheckConnectionAsync() + { + if (!await Connected()) + { + throw new InvalidOperationException("Not connected"); + } + } + + public async Task> GetApps(bool force = false) + { + if (!force) + { + await Task.Delay(5000); + } + + if (!await Connected(force)) + { + Logger.Debug("Cannot refresh apps: no connection could be made"); + return new List(); + } + + return await _lgTvApi.GetApps(force); + } + + public async Task PerformActionOnScreenSaver() + { + if (!IsPowerOffAllowed()) + { + return true; + } + + if (TurnScreenOffOnScreenSaver) + { + Logger.Debug("Turning screen off on screen saver"); + await _lgTvApi.TurnScreenOff(); + + return true; + } + + Logger.Debug($"Device {Name} is now powering off due to delayed screensaver task"); + + return await PowerOff(true, false); + } + + public async Task PerformActionAfterScreenSaver(int powerOnRetries) + { + if (TurnScreenOffOnScreenSaver && IsConnected()) + { + if (TurnScreenOnAfterScreenSaver) + { + try + { + Logger.Debug("Turning screen on after screen saver"); + await _lgTvApi.TurnScreenOn(); + } + catch (Exception) { } + } + + return true; + } + + return await WakeAndConnectWithRetries(powerOnRetries); + } + + internal async Task PowerOff(bool checkHdmi = false, bool reconnect = true) + { + if (!await Connected(reconnect) || CurrentState != PowerState.Active) + { + return false; + } + + if (checkHdmi && !IsPowerOffAllowed()) + { + return true; + } + + PoweredOffViaApp = true; + _poweredOffViaAppDateTime = DateTimeOffset.Now; + + try + { + await _lgTvApi.TurnOff().WaitAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(true); + } + catch (TimeoutException ex) + { + Logger.Debug($"Timeout when turning off tv: {ex.Message}"); + } + + return true; + } + + public bool IsPowerOffAllowed() + { + if (CurrentAppId != null && HDMIPortNumber != 0 && !CurrentAppId.EndsWith($".hdmi{HDMIPortNumber}", StringComparison.InvariantCulture)) + { + Logger.Debug($"[{Name}] PowerOff/ScreenOff is ignored because current app {CurrentAppId} does not match configured HDMI port {HDMIPortNumber}"); + return false; + } + + return true; + } + + internal async Task TestConnection(int retries = 1) + { + if (!await Connected(true, retries)) + { + return false; + } + + try + { + await _lgTvApi.IsMuted(); + return true; + } + catch (Exception ex) + { + Logger.Error("TestConnection: " + ex.ToLogString()); + return false; + } + } + + internal async Task WakeAndConnect(int wakeDelay = 5000, int connectDelay = 500) + { + try + { + await Task.Delay(wakeDelay); + var result = Wake(); + if (!result) + { + Logger.Debug("WOL failed"); + return false; + } + Logger.Debug("WOL succeeded"); + await Task.Delay(connectDelay); + result = await Connect(1); + Logger.Debug("Connect succeeded: " + result); + return result; + } + catch (Exception e) + { + Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString()); + return false; + } + } + + internal bool Wake() + { + var result = false; + + if (MacAddress != null) + { + var wolService = Program.ServiceProvider.GetRequiredService(); + + result = wolService.SendWol(MacAddress, IpAddress); + _justWokeUp = true; + } + else + { + Logger.Debug("Cannot wake device: the device has no MAC-address"); + } + + return result; + } + + internal async Task WakeAndConnectWithRetries(int retries = 5) + { + var wakeDelay = 0; + var maxRetries = retries <= 1 ? 5 : retries; + + var result = false; + for (var retry = 0; retry < maxRetries && !result; retry++) + { + Logger.Debug($"WakeAndConnectWithRetries: attempt {retry + 1} of {maxRetries}..."); + + var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + result = await WakeAndConnect(retry == 0 ? wakeDelay : 0); + ms = DateTimeOffset.Now.ToUnixTimeMilliseconds() - ms; + if (!result) + { + var delay = 2000 - ms; + if (delay > 0) + { + await Task.Delay((int)delay); + } + } + } + + return result; + } + + public List GetInvokableActions(bool includedAdvanced = false) + { + return _invokableActions.Where(a => includedAdvanced || !a.Advanced).ToList(); + } + + public List GetInvokableActionsForGameBar() + { + var actions = GetInvokableActions(); + return actions.Where(a => + a.Category == "picture" && (a.EnumType != null && a.EnumType != typeof(PictureMode) || a.MinValue >= 0 && a.MaxValue > 0) || + a.Name == "audioVolume" || (a.Category == "other" && (a.MinValue < a.MaxValue || a.EnumType != null)) + ) + .ToList(); + } + + public List GetActionsForGameBar() + { + var actions = GetInvokableActions(); + return actions.Where(a => ActionsOnGameBar.Contains(a.Name)).ToList(); + } + + public void Test() + { + try + { + //_lgTvApi.GetServiceList(); + //_lgTvApi?.Test(); + } + catch (Exception ex) + { + Logger.Error("TEST: " + ex.ToLogString()); + } + } + + private async Task Connected(bool reconnect = false, int retries = 3) + { + if (reconnect || !IsConnected() || !string.Equals(_lgTvApi.GetIpAddress(), IpAddress)) + { + if (!await Connect(retries)) + { + Logger.Debug("Cannot apply LG-preset: no connection could be made"); + return false; + } + } + return true; + } + + internal void ConvertToCustom() + { + IsCustom = true; + } + + public bool IsUsingHDRPictureMode() + { + // Temporary workaround because I cannot read the picture mode at this time + return PictureSettings.Contrast == 100; + } + + internal async Task SetBacklight(int backlight) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetSystemSettings("backlight", backlight.ToString()); + } + + internal async Task SetContrast(int contrast) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetSystemSettings("contrast", contrast.ToString()); + } + + public async Task SetOLEDMotionPro(string mode) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetConfig("tv.model.motionProMode", mode); + } + + public async Task SetSvcMenuFlag(bool enabled) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetSystemSettings("svcMenuFlag", enabled, "other"); + } + + internal async Task SetConfig(string key, object value) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetConfig(key, value); + } + internal async Task SetSystemSettings(string name, string value) + { + await CheckConnectionAsync(); + + await _lgTvApi.SetSystemSettings(name, value); + + UpdateCurrentValueOfAction(name, value); + } + + internal async Task GetPictureSettings() + { + //var keys = new[] { "backlight", "brightness", "contrast", "color", "pictureMode", "colorGamut", "dynamicContrast", "peakBrightness", "smoothGradation", "energySaving", "motionProOLED" }; + //var keys = new[] { "backlight", "brightness", "contrast", "color" }; + + await CheckConnectionAsync(); + + return await _lgTvApi.GetSystemSettings2("picture"); + } - private async Task WakeAction(Dictionary _) - { - return Wake(); - } + private Task WakeAction(Dictionary _) + { + return Task.FromResult(Wake()); + } - private async Task TurnScreenOffAction(Dictionary _) - { - await CheckConnectionAsync(); + private async Task TurnScreenOffAction(Dictionary _) + { + await CheckConnectionAsync(); - await _lgTvApi.TurnScreenOff(); + await _lgTvApi.TurnScreenOff(); - return true; - } + return true; + } - private async Task TurnScreenOnAction(Dictionary _) - { - await CheckConnectionAsync(); + private async Task TurnScreenOnAction(Dictionary _) + { + await CheckConnectionAsync(); - await _lgTvApi.TurnScreenOn(); + await _lgTvApi.TurnScreenOn(); - return true; - } + return true; + } - private async Task RebootAction(Dictionary _) - { - await CheckConnectionAsync(); + private async Task RebootAction(Dictionary _) + { + await CheckConnectionAsync(); - await _lgTvApi.Reboot(); + await _lgTvApi.Reboot(); - return true; - } + return true; + } - private async Task GenericPictureAction(Dictionary parameters) - { - await CheckConnectionAsync(); + private async Task GenericPictureAction(Dictionary parameters) + { + await CheckConnectionAsync(); - var settingName = parameters["name"].ToString(); - var stringValues = parameters["value"] as string[]; - var category = parameters["category"].ToString(); - object value = stringValues[0]; - if (stringValues.Length > 1) - { - value = stringValues.Select(s => int.Parse(s)).ToArray(); - } - await _lgTvApi.SetSystemSettings(settingName, value, category); + var settingName = parameters["name"].ToString(); + var stringValues = parameters["value"] as string[]; + var category = parameters["category"].ToString(); + object value = stringValues[0]; + if (stringValues.Length > 1) + { + value = stringValues.Select(s => int.Parse(s)).ToArray(); + } + await _lgTvApi.SetSystemSettings(settingName, value, category); - UpdateCurrentValueOfAction(settingName, value.ToString()); + UpdateCurrentValueOfAction(settingName, value.ToString()); - return true; - } + return true; + } - private async Task GenericDeviceConfigAction(Dictionary parameters) - { - await CheckConnectionAsync(); + private async Task GenericDeviceConfigAction(Dictionary parameters) + { + await CheckConnectionAsync(); - var id = parameters["name"].ToString().Replace("_icon", string.Empty); - var stringValues = parameters["value"] as string[]; - var value = stringValues[0]; + var id = parameters["name"].ToString().Replace("_icon", string.Empty); + var stringValues = parameters["value"] as string[]; + var value = stringValues[0]; - var description = Utils.GetDescriptionByEnumName(value); + var description = Utils.GetDescriptionByEnumName(value); - await _lgTvApi.SetDeviceConfig(id, value, description); + await _lgTvApi.SetDeviceConfig(id, value, description); - return true; - } + return true; + } - private async Task GenericSetConfigAction(Dictionary parameters) - { - await CheckConnectionAsync(); + private async Task GenericSetConfigAction(Dictionary parameters) + { + await CheckConnectionAsync(); - var key = parameters["name"].ToString(); - var values = parameters["value"] as object[]; - var value = values[0]; + var key = parameters["name"].ToString(); + var values = parameters["value"] as object[]; + var value = values[0]; - await _lgTvApi.SetConfig(key, value); + await _lgTvApi.SetConfig(key, value); - return true; - } + return true; + } - private async Task GenericLunaAction(Dictionary parameters) - { - await CheckConnectionAsync(); + private async Task GenericLunaAction(Dictionary parameters) + { + await CheckConnectionAsync(); - var key = parameters["name"].ToString(); - var values = parameters["value"] as object[]; - var value = values[0]; + var key = parameters["name"].ToString(); + var values = parameters["value"] as object[]; + var value = values[0]; - await _lgTvApi.SetTpcOrGsr(key, value?.ToString() == "bool_true"); + await _lgTvApi.SetTpcOrGsr(key, value?.ToString() == "bool_true"); - return true; - } + return true; + } - private async Task GenericVolumeAction(Dictionary parameters) - { - await CheckConnectionAsync(); + private async Task GenericVolumeAction(Dictionary parameters) + { + await CheckConnectionAsync(); - var values = parameters["value"] as object[]; - var value = values[0]; + var values = parameters["value"] as object[]; + var value = values[0]; - var volume = Utils.ParseInt(value.ToString(), 0); + var volume = Utils.ParseInt(value.ToString(), 0); - await _lgTvApi.SetVolume(volume); + await _lgTvApi.SetVolume(volume); - return true; - } + return true; + } - public Task SetVolume(int value) - { - return _lgTvApi.SetVolume(value); - } + public Task SetVolume(int value) + { + return _lgTvApi.SetVolume(value); + } - private void UpdateCurrentValueOfAction(string settingName, string value) - { - if (settingName == "backlight" || settingName == "contrast" || settingName == "brightness" || settingName == "color") - { - return; - } - var action = _invokableActions.FirstOrDefault(a => a.Name == settingName); - if (action != null) - { - if (action.EnumType != null) - { - try - { - var enumValue = Enum.Parse(action.EnumType, value); - var intEnum = (int)enumValue; - action.CurrentValue = intEnum; - } - catch (Exception) { } - } - else - { - action.CurrentValue = 0; - } - } - } - - internal void ClearPowerOffTask() - { - _powerOffTimer?.Dispose(); - } - - internal void PowerOffIn(int seconds) - { - ClearPowerOffTask(); + private void UpdateCurrentValueOfAction(string settingName, string value) + { + if (settingName == "backlight" || settingName == "contrast" || settingName == "brightness" || settingName == "color") + { + return; + } + var action = _invokableActions.FirstOrDefault(a => a.Name == settingName); + if (action != null) + { + if (action.EnumType != null) + { + try + { + var enumValue = Enum.Parse(action.EnumType, value); + var intEnum = (int)enumValue; + action.CurrentValue = intEnum; + } + catch (Exception) { } + } + else + { + action.CurrentValue = 0; + } + } + } + + internal void ClearPowerOffTask() + { + _powerOffTimer?.Dispose(); + } + + internal void PowerOffIn(int seconds) + { + ClearPowerOffTask(); - _powerOffTimer = new Timer(PowerOffByTimer); - _powerOffTimer.Change(TimeSpan.FromSeconds(seconds), TimeSpan.FromSeconds(seconds)); - } - - private void PowerOffByTimer(object _) - { - ClearPowerOffTask(); - - var x = PerformActionOnScreenSaver(); - } - - internal async Task ExecuteInvokableAction(InvokableActionDto invokableAction, List parameters) - { - var action = _invokableActions.FirstOrDefault(a => a.Name == invokableAction.Name); - - if (action == null) - { - return false; - } - - await ExecuteActionAsync(action, parameters?.ToArray() ?? []); - - return true; - } - } + _powerOffTimer = new Timer(PowerOffByTimer); + _powerOffTimer.Change(TimeSpan.FromSeconds(seconds), TimeSpan.FromSeconds(seconds)); + } + + private void PowerOffByTimer(object _) + { + ClearPowerOffTask(); + + var x = PerformActionOnScreenSaver(); + } + + internal async Task ExecuteInvokableAction(InvokableActionDto invokableAction, List parameters) + { + var action = _invokableActions.FirstOrDefault(a => a.Name == invokableAction.Name); + + if (action == null) + { + return false; + } + + await ExecuteActionAsync(action, parameters?.ToArray() ?? []); + + return true; + } + } } diff --git a/ColorControl/Services/LG/LgPanel.Designer.cs b/ColorControl/Services/LG/LgPanel.Designer.cs index 6e193be..faa6bc7 100644 --- a/ColorControl/Services/LG/LgPanel.Designer.cs +++ b/ColorControl/Services/LG/LgPanel.Designer.cs @@ -834,7 +834,7 @@ clbLgPower.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; clbLgPower.ColumnWidth = 240; clbLgPower.FormattingEnabled = true; - clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Use secure connection" }); + clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Power off when locking the pc", "Power on when unlocking the pc", "Use secure connection" }); clbLgPower.Location = new System.Drawing.Point(6, 70); clbLgPower.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); clbLgPower.MultiColumn = true; diff --git a/ColorControl/Services/LG/LgPanel.cs b/ColorControl/Services/LG/LgPanel.cs index 64cc640..b9adec7 100644 --- a/ColorControl/Services/LG/LgPanel.cs +++ b/ColorControl/Services/LG/LgPanel.cs @@ -19,1085 +19,1089 @@ using System.Windows.Forms; namespace ColorControl.Services.LG { - partial class LgPanel : UserControl, IModulePanel - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private Config _config; - private LgService _lgService; - private readonly ServiceManager _serviceManager; - private readonly GlobalContext _globalContext; - private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; - - private string _lgTabMessage; - private bool _disableEvents = false; - - public LgPanel(LgService lgService, ServiceManager serviceManager, GlobalContext globalContext, KeyboardShortcutDispatcher keyboardShortcutDispatcher) - { - _lgService = lgService; - _serviceManager = serviceManager; - _globalContext = globalContext; - _keyboardShortcutDispatcher = keyboardShortcutDispatcher; - _config = Shared.Common.GlobalContext.CurrentContext.Config; - - InitializeComponent(); - - FillLgPresets(); - - FormUtils.InitSortState(lvLgPresets, _config.LgPresetsSortState); - - //_lgService.AfterApplyPreset += LgServiceAfterApplyPreset; - _lgService.SelectedDeviceChangedEvent += _lgService_SelectedDeviceChangedEvent; - - edtLgDeviceFilter.Text = _lgService.Config.DeviceSearchKey; - - var values = Enum.GetValues(typeof(ButtonType)); - foreach (var button in values) - { - var text = button.ToString(); - if (text[0] == '_') - { - text = text.Substring(1); - } - - var item = mnuLgRcButtons.DropDownItems.AddCustom(text); - item.Click += miLgAddButton_Click; - } - } - - public void Init() - { - _ = Handle; - _ = AfterLgServiceRefreshDevices(); - } - - private void _lgService_SelectedDeviceChangedEvent(object sender, EventArgs e) - { - FormUtils.BeginInvokeCheck(this, () => SetLgDevicesSelectedIndex(sender)); - } - - private void SetLgDevicesSelectedIndex(object sender) - { - if (sender == null) - { - cbxLgDevices.SelectedIndex = -1; - return; - } - - cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(sender); - } - - private async Task AfterLgServiceRefreshDevices() - { - FillLgDevices(); - - var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; - - if (startUpParams.ExecuteLgPreset) - { - await _lgService.ApplyPreset(startUpParams.LgPresetName); - return; - } - } - - internal void Save() - { - FormUtils.SaveSortState(lvLgPresets.ListViewItemSorter, _config.LgPresetsSortState); - } - - private void edtShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } - - private void edtShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } - - private void FillLgPresets() - { - FormUtils.InitListView(lvLgPresets, LgPreset.GetColumnNames()); - - foreach (var preset in _lgService.GetPresets()) - { - AddOrUpdateItemLg(preset); - } - } - - private void AddOrUpdateItemLg(LgPreset preset = null, ListViewItem specItem = null) - { - ServiceFormUtils.AddOrUpdateListItem(lvLgPresets, _lgService.GetPresets(), _config, preset, specItem); - } - - private void btnCloneLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedLgPreset(); - - var newPreset = preset.Clone(); - AddOrUpdateItemLg(newPreset); - } - - private LgPreset GetSelectedLgPreset() - { - if (lvLgPresets.SelectedItems.Count > 0) - { - var item = lvLgPresets.SelectedItems[0]; - return (LgPreset)item.Tag; - } - else - { - return null; - } - } - - private void lvLgPresets_SelectedIndexChanged(object sender, EventArgs e) - { - var enabled = lvLgPresets.SelectedItems.Count > 0; - btnApplyLg.Enabled = enabled; - btnCloneLg.Enabled = enabled; - edtNameLg.Enabled = enabled; - chkLgQuickAccess.Enabled = enabled; - edtLgPresetDescription.Enabled = enabled; - cbxLgPresetDevice.Enabled = enabled; - edtShortcutLg.Enabled = enabled; - btnSetShortcutLg.Enabled = enabled; - edtStepsLg.Enabled = enabled; - btnLgAddButton.Enabled = enabled; - cbxLgApps.Enabled = enabled; - btnLgRefreshApps.Enabled = enabled; - btnDeleteLg.Enabled = enabled; - cbxLgPresetTrigger.Enabled = enabled; - edtLgPresetTriggerConditions.Enabled = enabled; - btnLgPresetEditTriggerConditions.Enabled = enabled; - edtLgPresetIncludedProcesses.Enabled = enabled; - edtLgPresetExcludedProcesses.Enabled = enabled; - edtLgPresetConnectedDisplaysRegex.Enabled = enabled; - - var preset = GetSelectedLgPreset(); - - if (preset != null) - { - edtNameLg.Text = preset.name; - chkLgQuickAccess.Checked = preset.ShowInQuickAccess; - edtLgPresetDescription.Text = preset.Description; - var lgApps = _lgService?.GetApps(); - cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId)); - edtShortcutLg.Text = preset.shortcut; - edtStepsLg.Text = preset.steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); - - var index = -1; - - for (var i = 0; i < cbxLgPresetDevice.Items.Count; i++) - { - var pnpDev = (LgDevice)cbxLgPresetDevice.Items[i]; - if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase)) - { - index = i; - break; - } - } - - cbxLgPresetDevice.SelectedIndex = index; - - var trigger = preset.Triggers.FirstOrDefault(); - - FormUtils.SetComboBoxEnumIndex(cbxLgPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); - - edtLgPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); - edtLgPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; - - edtLgPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; - edtLgPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; - edtLgPresetConnectedDisplaysRegex.Text = trigger?.ConnectedDisplaysRegex ?? string.Empty; - } - else - { - edtNameLg.Text = string.Empty; - chkLgQuickAccess.Checked = false; - edtLgPresetDescription.Text = string.Empty; - cbxLgApps.SelectedIndex = -1; - edtShortcutLg.Text = string.Empty; - edtStepsLg.Text = string.Empty; - cbxLgPresetDevice.SelectedIndex = -1; - cbxLgPresetTrigger.SelectedIndex = -1; - edtLgPresetTriggerConditions.Text = string.Empty; - edtLgPresetTriggerConditions.Tag = 0; - edtLgPresetIncludedProcesses.Text = string.Empty; - edtLgPresetExcludedProcesses.Text = string.Empty; - edtLgPresetConnectedDisplaysRegex.Text = string.Empty; - } - } - - private async void btnApplyLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedLgPreset(); - - await _lgService.ApplyPresetUi(preset); - } - - private void btnSetShortcutLg_Click(object sender, EventArgs e) - { - var shortcut = edtShortcutLg.Text.Trim(); - if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) - { - return; - } - - var preset = GetSelectedLgPreset(); - - var name = edtNameLg.Text.Trim(); - - if (name.Length == 0 || _lgService.GetPresets().Any(x => x.id != preset.id && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - MessageForms.WarningOk("The name can not be empty and must be unique."); - return; - } - - var clear = !string.IsNullOrEmpty(preset.shortcut); - - preset.name = name; - preset.ShowInQuickAccess = chkLgQuickAccess.Checked; - preset.Description = edtLgPresetDescription.Text.Trim(); - - if (cbxLgPresetDevice.SelectedIndex == -1) - { - preset.DeviceMacAddress = string.Empty; - } - else - { - var device = (LgDevice)cbxLgPresetDevice.SelectedItem; - preset.DeviceMacAddress = device.MacAddress; - } - - if (cbxLgApps.SelectedIndex == -1) - { - preset.appId = string.Empty; - } - else - { - var lgApp = (LgApp)cbxLgApps.SelectedItem; - preset.appId = lgApp.appId; - } - - var triggerType = FormUtils.GetComboBoxEnumItem(cbxLgPresetTrigger); - preset.UpdateTrigger(triggerType, - (PresetConditionType)edtLgPresetTriggerConditions.Tag, - edtLgPresetIncludedProcesses.Text, - edtLgPresetExcludedProcesses.Text, - edtLgPresetConnectedDisplaysRegex.Text); - - preset.shortcut = shortcut; - - var text = edtStepsLg.Text; - - Utils.ParseWords(preset.steps, text); - - AddOrUpdateItemLg(); - - _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); - } - - public void UpdateInfo() - { - if (_lgService == null) - { - return; - } - - if (scLgController.Panel2.Controls.Count == 0) - { - var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons()); - rcPanel.Parent = scLgController.Panel2; - rcPanel.Dock = DockStyle.Fill; - - if (DarkModeUtils.UseDarkMode) - { - DarkModeUtils.SetControlTheme(rcPanel); - } - } - chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl; - scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl; - - FormUtils.BuildComboBox(cbxLgPresetTrigger, PresetTriggerType.Reserved5); - - if (!string.IsNullOrEmpty(_lgTabMessage)) - { - MessageForms.WarningOk(_lgTabMessage); - _lgTabMessage = null; - } - } - - private void FillLgDevices() - { - var devices = _lgService.Devices; - - cbxLgDevices.Items.Clear(); - cbxLgDevices.Items.AddRange(devices.ToArray()); - - cbxLgPresetDevice.Items.Clear(); - cbxLgPresetDevice.Items.AddRange(devices.ToArray()); - var globalDevice = new LgDevice("Globally selected device", string.Empty, string.Empty, true, true); - cbxLgPresetDevice.Items.Insert(0, globalDevice); - - var device = _lgService.SelectedDevice; - - if (device != null) - { - cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(device); - } - - if (!devices.Any()) - { - var message = "It seems there's no LG TV available! Please make sure it's connected to the same network as this PC."; - - if (Visible /*tcMain.SelectedTab == tabLG*/) - { - MessageForms.WarningOk(message); - } - else - { - _lgTabMessage = message; - } - } - - if (cbxLgApps.Items.Count == 0 && device != null) - { - _lgService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false))); - } - - btnLgDeviceConvertToCustom.Enabled = devices.Any(); - btnLgExpert.Enabled = devices.Any(); - btnLgGameBar.Enabled = devices.Any(); - - cbxLgDevices_SelectedIndexChanged(cbxLgDevices, null); - - SetLgDevicePowerOptions(); - } - - private void SetLgDevicePowerOptions() - { - var device = _lgService.SelectedDevice; - clbLgPower.Enabled = device != null; - - _disableEvents = true; - try - { - clbLgPower.SetItemChecked(0, device?.PowerOnAfterStartup ?? false); - clbLgPower.SetItemChecked(1, device?.PowerOnAfterResume ?? false); - clbLgPower.SetItemChecked(2, device?.PowerOffOnShutdown ?? false); - clbLgPower.SetItemChecked(3, device?.PowerOffOnStandby ?? false); - clbLgPower.SetItemChecked(4, device?.PowerOffOnScreenSaver ?? false); - clbLgPower.SetItemChecked(5, device?.PowerOnAfterScreenSaver ?? false); - clbLgPower.SetItemChecked(6, device?.PowerOnAfterManualPowerOff ?? false); - clbLgPower.SetItemChecked(7, device?.TriggersEnabled ?? true); - clbLgPower.SetItemChecked(8, device?.PowerOffByWindows ?? false); - clbLgPower.SetItemChecked(9, device?.PowerOnByWindows ?? false); - clbLgPower.SetItemChecked(10, device?.UseSecureConnection ?? false); - } - finally - { - _disableEvents = false; - } - } - - private void FillLgApps(bool forced) - { - var lgApps = _lgService?.GetApps(); - if (forced && (lgApps == null || !lgApps.Any())) - { - MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); - return; - } - InitLgApps(); - } - - private void InitLgApps() - { - var lgApps = _lgService?.GetApps(); - - cbxLgApps.Items.Clear(); - if (lgApps != null) - { - cbxLgApps.Items.AddRange(lgApps.ToArray()); - } - - for (var i = 0; i < lvLgPresets.Items.Count; i++) - { - var item = lvLgPresets.Items[i]; - AddOrUpdateItemLg((LgPreset)item.Tag, item); - } - - var preset = GetSelectedLgPreset(); - if (preset != null) - { - cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId)); - } - } - - private void btnDeleteLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedLgPreset(); - - if (preset == null) - { - return; - } - - if (!string.IsNullOrEmpty(preset.shortcut)) - { - WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); - } - - _lgService.GetPresets().Remove(preset); - - var item = lvLgPresets.SelectedItems[0]; - lvLgPresets.Items.Remove(item); - } - - private void btnAddLg_Click(object sender, EventArgs e) - { - AddOrUpdateItemLg(_lgService.CreateNewPreset()); - } - - private void btnLgRefreshApps_Click(object sender, EventArgs e) - { - _lgService.RefreshApps(true).ContinueWith((task) => BeginInvoke(() => FillLgApps(true))); - } - - private void cbxLgDevices_SelectedIndexChanged(object sender, EventArgs e) - { - cbxLgPcHdmiPort.Enabled = cbxLgDevices.SelectedIndex > -1; - if (cbxLgDevices.SelectedIndex == -1) - { - _lgService.SelectedDevice = null; - cbxLgPcHdmiPort.SelectedIndex = 0; - } - else - { - _lgService.SelectedDevice = (LgDevice)cbxLgDevices.SelectedItem; - cbxLgPcHdmiPort.SelectedIndex = _lgService.SelectedDevice.HDMIPortNumber; - } - chkLgRemoteControlShow.Enabled = _lgService.SelectedDevice != null; - btnLgRemoveDevice.Enabled = _lgService.SelectedDevice != null && _lgService.SelectedDevice.IsCustom; - btnLgDeviceConvertToCustom.Enabled = _lgService.SelectedDevice != null && !_lgService.SelectedDevice.IsCustom; - - SetLgDevicePowerOptions(); - } - - private void lvLgPresets_DoubleClick(object sender, EventArgs e) - { - ApplySelectedLgPreset(); - } - - private async void ApplySelectedLgPreset() - { - var preset = GetSelectedLgPreset(); - await _lgService.ApplyPresetUi(preset); - } - - private void btnLgAddButton_Click(object sender, EventArgs e) - { - mnuLgButtons.ShowCustom(btnLgAddButton); - } - - private void miLgAddButton_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - - FormUtils.AddStepToTextBox(edtStepsLg, item.Text); - } - - private void miLgAddAction_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var action = item.Tag as LgDevice.InvokableAction; - - var value = ShowLgActionForm(action); - - var text = action.Name; - - if (!string.IsNullOrEmpty(value)) - { - text += $"({value})"; - } - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private string ShowLgActionForm(LgDevice.InvokableAction action) - { - var text = action.Name; - var title = action.Title ?? action.Name; - - var value = string.Empty; - - if (action.EnumType == null) - { - if (action.MinValue != action.MaxValue) - { - List fields = new(); - - if (action.NumberOfValues == 1) - { - fields.Add(new FieldDefinition - { - Label = "Enter desired " + title, - FieldType = FieldType.Numeric, - MinValue = action.MinValue, - MaxValue = action.MaxValue, - }); - } - else - { - var array = Enumerable.Range(0, action.NumberOfValues); - - fields.AddRange(array.Select(i => new FieldDefinition - { - Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()), - FieldType = FieldType.Numeric, - MinValue = action.MinValue, - MaxValue = action.MaxValue, - })); - } - - var values = MessageForms.ShowDialog($"Enter value for {title}", fields); - - if (!values.Any()) - { - return null; - } - - if (values.Count > 1) - { - value = string.Join("; ", values.Select(v => v.Value)); - } - else - { - value = values.First().Value.ToString(); - } - } - } - else - { - var dropDownValues = new List(); - foreach (var enumValue in Enum.GetValues(action.EnumType)) - { - var description = Utils.GetDescription(action.EnumType, enumValue as IConvertible) ?? enumValue.ToString().Replace("_", ""); - dropDownValues.Add(description); - } - - var values = MessageForms.ShowDialog("Choose value", new[] { - new FieldDefinition - { - Label = "Choose desired " + title, - FieldType = FieldType.DropDown, - Values = dropDownValues - } - }); - - if (!values.Any()) - { - return null; - } - - value = values.First().Value.ToString(); - var enumName = Utils.GetEnumNameByDescription(action.EnumType, value); - if (enumName != null) - { - value = enumName; - } - } - - return value; - } - - private void miLgAddNvPreset_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var preset = item.Tag as NvPreset; - - var text = $"NvPreset({preset.name})"; - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private void miLgAddAmdPreset_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var preset = item.Tag as AmdPreset; - - var text = $"AmdPreset({preset.name})"; - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) - { - if (_disableEvents) - { - return; - } - - var device = _lgService?.SelectedDevice; - if (device == null) - { - return; - } - - if (e.Index is 0 or 1 or 5 or 8 && !(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerOnAfterScreenSaver || device.PowerOnByWindows)) - { - MessageForms.InfoOk( + partial class LgPanel : UserControl, IModulePanel + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private Config _config; + private LgService _lgService; + private readonly ServiceManager _serviceManager; + private readonly GlobalContext _globalContext; + private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; + + private string _lgTabMessage; + private bool _disableEvents = false; + + public LgPanel(LgService lgService, ServiceManager serviceManager, GlobalContext globalContext, KeyboardShortcutDispatcher keyboardShortcutDispatcher) + { + _lgService = lgService; + _serviceManager = serviceManager; + _globalContext = globalContext; + _keyboardShortcutDispatcher = keyboardShortcutDispatcher; + _config = Shared.Common.GlobalContext.CurrentContext.Config; + + InitializeComponent(); + + FillLgPresets(); + + FormUtils.InitSortState(lvLgPresets, _config.LgPresetsSortState); + + //_lgService.AfterApplyPreset += LgServiceAfterApplyPreset; + _lgService.SelectedDeviceChangedEvent += _lgService_SelectedDeviceChangedEvent; + + edtLgDeviceFilter.Text = _lgService.Config.DeviceSearchKey; + + var values = Enum.GetValues(typeof(ButtonType)); + foreach (var button in values) + { + var text = button.ToString(); + if (text[0] == '_') + { + text = text.Substring(1); + } + + var item = mnuLgRcButtons.DropDownItems.AddCustom(text); + item.Click += miLgAddButton_Click; + } + } + + public void Init() + { + _ = Handle; + _ = AfterLgServiceRefreshDevices(); + } + + private void _lgService_SelectedDeviceChangedEvent(object sender, EventArgs e) + { + FormUtils.BeginInvokeCheck(this, () => SetLgDevicesSelectedIndex(sender)); + } + + private void SetLgDevicesSelectedIndex(object sender) + { + if (sender == null) + { + cbxLgDevices.SelectedIndex = -1; + return; + } + + cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(sender); + } + + private async Task AfterLgServiceRefreshDevices() + { + FillLgDevices(); + + var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; + + if (startUpParams.ExecuteLgPreset) + { + await _lgService.ApplyPreset(startUpParams.LgPresetName); + return; + } + } + + internal void Save() + { + FormUtils.SaveSortState(lvLgPresets.ListViewItemSorter, _config.LgPresetsSortState); + } + + private void edtShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); + } + + private void edtShortcut_KeyUp(object sender, KeyEventArgs e) + { + KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); + } + + private void FillLgPresets() + { + FormUtils.InitListView(lvLgPresets, LgPreset.GetColumnNames()); + + foreach (var preset in _lgService.GetPresets()) + { + AddOrUpdateItemLg(preset); + } + } + + private void AddOrUpdateItemLg(LgPreset preset = null, ListViewItem specItem = null) + { + ServiceFormUtils.AddOrUpdateListItem(lvLgPresets, _lgService.GetPresets(), _config, preset, specItem); + } + + private void btnCloneLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + var newPreset = preset.Clone(); + AddOrUpdateItemLg(newPreset); + } + + private LgPreset GetSelectedLgPreset() + { + if (lvLgPresets.SelectedItems.Count > 0) + { + var item = lvLgPresets.SelectedItems[0]; + return (LgPreset)item.Tag; + } + else + { + return null; + } + } + + private void lvLgPresets_SelectedIndexChanged(object sender, EventArgs e) + { + var enabled = lvLgPresets.SelectedItems.Count > 0; + btnApplyLg.Enabled = enabled; + btnCloneLg.Enabled = enabled; + edtNameLg.Enabled = enabled; + chkLgQuickAccess.Enabled = enabled; + edtLgPresetDescription.Enabled = enabled; + cbxLgPresetDevice.Enabled = enabled; + edtShortcutLg.Enabled = enabled; + btnSetShortcutLg.Enabled = enabled; + edtStepsLg.Enabled = enabled; + btnLgAddButton.Enabled = enabled; + cbxLgApps.Enabled = enabled; + btnLgRefreshApps.Enabled = enabled; + btnDeleteLg.Enabled = enabled; + cbxLgPresetTrigger.Enabled = enabled; + edtLgPresetTriggerConditions.Enabled = enabled; + btnLgPresetEditTriggerConditions.Enabled = enabled; + edtLgPresetIncludedProcesses.Enabled = enabled; + edtLgPresetExcludedProcesses.Enabled = enabled; + edtLgPresetConnectedDisplaysRegex.Enabled = enabled; + + var preset = GetSelectedLgPreset(); + + if (preset != null) + { + edtNameLg.Text = preset.name; + chkLgQuickAccess.Checked = preset.ShowInQuickAccess; + edtLgPresetDescription.Text = preset.Description; + var lgApps = _lgService?.GetApps(); + cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId)); + edtShortcutLg.Text = preset.shortcut; + edtStepsLg.Text = preset.steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); + + var index = -1; + + for (var i = 0; i < cbxLgPresetDevice.Items.Count; i++) + { + var pnpDev = (LgDevice)cbxLgPresetDevice.Items[i]; + if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase)) + { + index = i; + break; + } + } + + cbxLgPresetDevice.SelectedIndex = index; + + var trigger = preset.Triggers.FirstOrDefault(); + + FormUtils.SetComboBoxEnumIndex(cbxLgPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); + + edtLgPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); + edtLgPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; + + edtLgPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; + edtLgPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; + edtLgPresetConnectedDisplaysRegex.Text = trigger?.ConnectedDisplaysRegex ?? string.Empty; + } + else + { + edtNameLg.Text = string.Empty; + chkLgQuickAccess.Checked = false; + edtLgPresetDescription.Text = string.Empty; + cbxLgApps.SelectedIndex = -1; + edtShortcutLg.Text = string.Empty; + edtStepsLg.Text = string.Empty; + cbxLgPresetDevice.SelectedIndex = -1; + cbxLgPresetTrigger.SelectedIndex = -1; + edtLgPresetTriggerConditions.Text = string.Empty; + edtLgPresetTriggerConditions.Tag = 0; + edtLgPresetIncludedProcesses.Text = string.Empty; + edtLgPresetExcludedProcesses.Text = string.Empty; + edtLgPresetConnectedDisplaysRegex.Text = string.Empty; + } + } + + private async void btnApplyLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + await _lgService.ApplyPresetUi(preset); + } + + private void btnSetShortcutLg_Click(object sender, EventArgs e) + { + var shortcut = edtShortcutLg.Text.Trim(); + if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) + { + return; + } + + var preset = GetSelectedLgPreset(); + + var name = edtNameLg.Text.Trim(); + + if (name.Length == 0 || _lgService.GetPresets().Any(x => x.id != preset.id && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + MessageForms.WarningOk("The name can not be empty and must be unique."); + return; + } + + var clear = !string.IsNullOrEmpty(preset.shortcut); + + preset.name = name; + preset.ShowInQuickAccess = chkLgQuickAccess.Checked; + preset.Description = edtLgPresetDescription.Text.Trim(); + + if (cbxLgPresetDevice.SelectedIndex == -1) + { + preset.DeviceMacAddress = string.Empty; + } + else + { + var device = (LgDevice)cbxLgPresetDevice.SelectedItem; + preset.DeviceMacAddress = device.MacAddress; + } + + if (cbxLgApps.SelectedIndex == -1) + { + preset.appId = string.Empty; + } + else + { + var lgApp = (LgApp)cbxLgApps.SelectedItem; + preset.appId = lgApp.appId; + } + + var triggerType = FormUtils.GetComboBoxEnumItem(cbxLgPresetTrigger); + preset.UpdateTrigger(triggerType, + (PresetConditionType)edtLgPresetTriggerConditions.Tag, + edtLgPresetIncludedProcesses.Text, + edtLgPresetExcludedProcesses.Text, + edtLgPresetConnectedDisplaysRegex.Text); + + preset.shortcut = shortcut; + + var text = edtStepsLg.Text; + + Utils.ParseWords(preset.steps, text); + + AddOrUpdateItemLg(); + + _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); + } + + public void UpdateInfo() + { + if (_lgService == null) + { + return; + } + + if (scLgController.Panel2.Controls.Count == 0) + { + var rcPanel = new RemoteControlPanel(_lgService, _lgService.GetRemoteControlButtons()); + rcPanel.Parent = scLgController.Panel2; + rcPanel.Dock = DockStyle.Fill; + + if (DarkModeUtils.UseDarkMode) + { + DarkModeUtils.SetControlTheme(rcPanel); + } + } + chkLgRemoteControlShow.Checked = _lgService.Config.ShowRemoteControl; + scLgController.Panel2Collapsed = !_lgService.Config.ShowRemoteControl; + + FormUtils.BuildComboBox(cbxLgPresetTrigger, PresetTriggerType.Reserved5); + + if (!string.IsNullOrEmpty(_lgTabMessage)) + { + MessageForms.WarningOk(_lgTabMessage); + _lgTabMessage = null; + } + } + + private void FillLgDevices() + { + var devices = _lgService.Devices; + + cbxLgDevices.Items.Clear(); + cbxLgDevices.Items.AddRange(devices.ToArray()); + + cbxLgPresetDevice.Items.Clear(); + cbxLgPresetDevice.Items.AddRange(devices.ToArray()); + var globalDevice = new LgDevice("Globally selected device", string.Empty, string.Empty, true, true); + cbxLgPresetDevice.Items.Insert(0, globalDevice); + + var device = _lgService.SelectedDevice; + + if (device != null) + { + cbxLgDevices.SelectedIndex = cbxLgDevices.Items.IndexOf(device); + } + + if (!devices.Any()) + { + var message = "It seems there's no LG TV available! Please make sure it's connected to the same network as this PC."; + + if (Visible /*tcMain.SelectedTab == tabLG*/) + { + MessageForms.WarningOk(message); + } + else + { + _lgTabMessage = message; + } + } + + if (cbxLgApps.Items.Count == 0 && device != null) + { + _lgService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false))); + } + + btnLgDeviceConvertToCustom.Enabled = devices.Any(); + btnLgExpert.Enabled = devices.Any(); + btnLgGameBar.Enabled = devices.Any(); + + cbxLgDevices_SelectedIndexChanged(cbxLgDevices, null); + + SetLgDevicePowerOptions(); + } + + private void SetLgDevicePowerOptions() + { + var device = _lgService.SelectedDevice; + clbLgPower.Enabled = device != null; + + _disableEvents = true; + try + { + clbLgPower.SetItemChecked(0, device?.PowerOnAfterStartup ?? false); + clbLgPower.SetItemChecked(1, device?.PowerOnAfterResume ?? false); + clbLgPower.SetItemChecked(2, device?.PowerOffOnShutdown ?? false); + clbLgPower.SetItemChecked(3, device?.PowerOffOnStandby ?? false); + clbLgPower.SetItemChecked(4, device?.PowerOffOnScreenSaver ?? false); + clbLgPower.SetItemChecked(5, device?.PowerOnAfterScreenSaver ?? false); + clbLgPower.SetItemChecked(6, device?.PowerOnAfterManualPowerOff ?? false); + clbLgPower.SetItemChecked(7, device?.TriggersEnabled ?? true); + clbLgPower.SetItemChecked(8, device?.PowerOffByWindows ?? false); + clbLgPower.SetItemChecked(9, device?.PowerOnByWindows ?? false); + clbLgPower.SetItemChecked(10, device?.PowerOffAfterSessionLock ?? false); + clbLgPower.SetItemChecked(11, device?.PowerOnAfterSessionUnlock ?? false); + clbLgPower.SetItemChecked(12, device?.UseSecureConnection ?? false); + } + finally + { + _disableEvents = false; + } + } + + private void FillLgApps(bool forced) + { + var lgApps = _lgService?.GetApps(); + if (forced && (lgApps == null || !lgApps.Any())) + { + MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); + return; + } + InitLgApps(); + } + + private void InitLgApps() + { + var lgApps = _lgService?.GetApps(); + + cbxLgApps.Items.Clear(); + if (lgApps != null) + { + cbxLgApps.Items.AddRange(lgApps.ToArray()); + } + + for (var i = 0; i < lvLgPresets.Items.Count; i++) + { + var item = lvLgPresets.Items[i]; + AddOrUpdateItemLg((LgPreset)item.Tag, item); + } + + var preset = GetSelectedLgPreset(); + if (preset != null) + { + cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId)); + } + } + + private void btnDeleteLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); + + if (preset == null) + { + return; + } + + if (!string.IsNullOrEmpty(preset.shortcut)) + { + WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); + } + + _lgService.GetPresets().Remove(preset); + + var item = lvLgPresets.SelectedItems[0]; + lvLgPresets.Items.Remove(item); + } + + private void btnAddLg_Click(object sender, EventArgs e) + { + AddOrUpdateItemLg(_lgService.CreateNewPreset()); + } + + private void btnLgRefreshApps_Click(object sender, EventArgs e) + { + _lgService.RefreshApps(true).ContinueWith((task) => BeginInvoke(() => FillLgApps(true))); + } + + private void cbxLgDevices_SelectedIndexChanged(object sender, EventArgs e) + { + cbxLgPcHdmiPort.Enabled = cbxLgDevices.SelectedIndex > -1; + if (cbxLgDevices.SelectedIndex == -1) + { + _lgService.SelectedDevice = null; + cbxLgPcHdmiPort.SelectedIndex = 0; + } + else + { + _lgService.SelectedDevice = (LgDevice)cbxLgDevices.SelectedItem; + cbxLgPcHdmiPort.SelectedIndex = _lgService.SelectedDevice.HDMIPortNumber; + } + chkLgRemoteControlShow.Enabled = _lgService.SelectedDevice != null; + btnLgRemoveDevice.Enabled = _lgService.SelectedDevice != null && _lgService.SelectedDevice.IsCustom; + btnLgDeviceConvertToCustom.Enabled = _lgService.SelectedDevice != null && !_lgService.SelectedDevice.IsCustom; + + SetLgDevicePowerOptions(); + } + + private void lvLgPresets_DoubleClick(object sender, EventArgs e) + { + ApplySelectedLgPreset(); + } + + private async void ApplySelectedLgPreset() + { + var preset = GetSelectedLgPreset(); + await _lgService.ApplyPresetUi(preset); + } + + private void btnLgAddButton_Click(object sender, EventArgs e) + { + mnuLgButtons.ShowCustom(btnLgAddButton); + } + + private void miLgAddButton_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + + FormUtils.AddStepToTextBox(edtStepsLg, item.Text); + } + + private void miLgAddAction_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var action = item.Tag as LgDevice.InvokableAction; + + var value = ShowLgActionForm(action); + + var text = action.Name; + + if (!string.IsNullOrEmpty(value)) + { + text += $"({value})"; + } + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private string ShowLgActionForm(LgDevice.InvokableAction action) + { + var text = action.Name; + var title = action.Title ?? action.Name; + + var value = string.Empty; + + if (action.EnumType == null) + { + if (action.MinValue != action.MaxValue) + { + List fields = new(); + + if (action.NumberOfValues == 1) + { + fields.Add(new FieldDefinition + { + Label = "Enter desired " + title, + FieldType = FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + }); + } + else + { + var array = Enumerable.Range(0, action.NumberOfValues); + + fields.AddRange(array.Select(i => new FieldDefinition + { + Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()), + FieldType = FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + })); + } + + var values = MessageForms.ShowDialog($"Enter value for {title}", fields); + + if (!values.Any()) + { + return null; + } + + if (values.Count > 1) + { + value = string.Join("; ", values.Select(v => v.Value)); + } + else + { + value = values.First().Value.ToString(); + } + } + } + else + { + var dropDownValues = new List(); + foreach (var enumValue in Enum.GetValues(action.EnumType)) + { + var description = Utils.GetDescription(action.EnumType, enumValue as IConvertible) ?? enumValue.ToString().Replace("_", ""); + dropDownValues.Add(description); + } + + var values = MessageForms.ShowDialog("Choose value", new[] { + new FieldDefinition + { + Label = "Choose desired " + title, + FieldType = FieldType.DropDown, + Values = dropDownValues + } + }); + + if (!values.Any()) + { + return null; + } + + value = values.First().Value.ToString(); + var enumName = Utils.GetEnumNameByDescription(action.EnumType, value); + if (enumName != null) + { + value = enumName; + } + } + + return value; + } + + private void miLgAddNvPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as NvPreset; + + var text = $"NvPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void miLgAddAmdPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as AmdPreset; + + var text = $"AmdPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) + { + if (_disableEvents) + { + return; + } + + var device = _lgService?.SelectedDevice; + if (device == null) + { + return; + } + + if (e.Index is 0 or 1 or 5 or 8 && !(device.PowerOnAfterResume || device.PowerOnAfterStartup || device.PowerOnAfterScreenSaver || device.PowerOnByWindows || device.PowerOnAfterSessionUnlock)) + { + MessageForms.InfoOk( @"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: Connection > Mobile TV On > Turn on via Wi-Fi (Networked Standby Mode) See Options to test this functionality. You can also activate this option by using the Expert-button and selecting Wake-On-LAN > Enabled." - ); - } + ); + } - BeginInvoke(() => - { - device.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); - device.PowerOnAfterResume = clbLgPower.GetItemChecked(1); - device.PowerOffOnShutdown = clbLgPower.GetItemChecked(2); - device.PowerOffOnStandby = clbLgPower.GetItemChecked(3); - device.PowerOffOnScreenSaver = clbLgPower.GetItemChecked(4); - device.PowerOnAfterScreenSaver = clbLgPower.GetItemChecked(5); - device.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(6); - device.TriggersEnabled = clbLgPower.GetItemChecked(7); - device.PowerOffByWindows = clbLgPower.GetItemChecked(8); - device.PowerOnByWindows = clbLgPower.GetItemChecked(9); - device.UseSecureConnection = clbLgPower.GetItemChecked(10); + BeginInvoke(() => + { + device.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); + device.PowerOnAfterResume = clbLgPower.GetItemChecked(1); + device.PowerOffOnShutdown = clbLgPower.GetItemChecked(2); + device.PowerOffOnStandby = clbLgPower.GetItemChecked(3); + device.PowerOffOnScreenSaver = clbLgPower.GetItemChecked(4); + device.PowerOnAfterScreenSaver = clbLgPower.GetItemChecked(5); + device.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(6); + device.TriggersEnabled = clbLgPower.GetItemChecked(7); + device.PowerOffByWindows = clbLgPower.GetItemChecked(8); + device.PowerOnByWindows = clbLgPower.GetItemChecked(9); + device.PowerOffAfterSessionLock = clbLgPower.GetItemChecked(10); + device.PowerOnAfterSessionUnlock = clbLgPower.GetItemChecked(11); + device.UseSecureConnection = clbLgPower.GetItemChecked(12); - if (e.Index == 10) - { - _lgService.RefreshDevices().ContinueWith((_) => FormUtils.BeginInvokeCheck(this, () => FillLgDevices())); - } - }); - } + if (e.Index == 12) + { + _lgService.RefreshDevices().ContinueWith((_) => FormUtils.BeginInvokeCheck(this, () => FillLgDevices())); + } + }); + } - private void edtLgDeviceFilter_TextChanged(object sender, EventArgs e) - { - if (_lgService != null) - { - _lgService.Config.DeviceSearchKey = edtLgDeviceFilter.Text; - } - } + private void edtLgDeviceFilter_TextChanged(object sender, EventArgs e) + { + if (_lgService != null) + { + _lgService.Config.DeviceSearchKey = edtLgDeviceFilter.Text; + } + } - private void btnLgDeviceFilterRefresh_Click(object sender, EventArgs e) - { - RefreshLgDevices(); - } + private void btnLgDeviceFilterRefresh_Click(object sender, EventArgs e) + { + RefreshLgDevices(); + } - private void RefreshLgDevices() - { - _lgService.RefreshDevices(false).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillLgDevices())); - } + private void RefreshLgDevices() + { + _lgService.RefreshDevices(false).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillLgDevices())); + } - private void edtShortcutLg_TextChanged(object sender, EventArgs e) - { - var text = edtShortcutLg.Text; + private void edtShortcutLg_TextChanged(object sender, EventArgs e) + { + var text = edtShortcutLg.Text; - var preset = GetSelectedLgPreset(); + var preset = GetSelectedLgPreset(); - ServiceFormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); - } + ServiceFormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); + } - private async void btnLgAddDevice_Click(object sender, EventArgs e) - { - var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); - if (values.Any()) - { - var device = new LgDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); + private async void btnLgAddDevice_Click(object sender, EventArgs e) + { + var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); + if (values.Any()) + { + var device = new LgDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); - var form = MessageForms.ShowProgress("Connecting to device..."); + var form = MessageForms.ShowProgress("Connecting to device..."); - var result = false; - try - { - result = await device.TestConnection(); - } - finally - { - form.Close(); - } + var result = false; + try + { + result = await device.TestConnection(); + } + finally + { + form.Close(); + } - if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes) - { - return; - } + if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes) + { + return; + } - _lgService.AddCustomDevice(device); + _lgService.AddCustomDevice(device); - FillLgDevices(); - } - } + FillLgDevices(); + } + } - private string ValidateAddDevice(IEnumerable values) - { - if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) - { - return "Please fill in all the fields"; - } + private string ValidateAddDevice(IEnumerable values) + { + if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) + { + return "Please fill in all the fields"; + } - return null; - } + return null; + } - private void btnLgRemoveDevice_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes) - { - return; - } + private void btnLgRemoveDevice_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes) + { + return; + } - var device = _lgService.SelectedDevice; + var device = _lgService.SelectedDevice; - if (device != null) - { - _lgService.RemoveCustomDevice(device); + if (device != null) + { + _lgService.RemoveCustomDevice(device); - FillLgDevices(); - } - } + FillLgDevices(); + } + } - private void mnuLgButtons_Opening(object sender, CancelEventArgs e) - { - mnuLgActions.DropDownItems.Clear(); + private void mnuLgButtons_Opening(object sender, CancelEventArgs e) + { + mnuLgActions.DropDownItems.Clear(); - var preset = GetSelectedLgPreset(); + var preset = GetSelectedLgPreset(); - if (preset == null) - { - return; - } + if (preset == null) + { + return; + } - var device = _lgService.GetPresetDevice(preset); + var device = _lgService.GetPresetDevice(preset); - BuildLgActionMenu(device, mnuLgActions.DropDownItems, mnuLgActions.Name, miLgAddAction_Click); - ServiceFormUtils.BuildServicePresetsMenu(mnuLgNvPresets, _serviceManager.NvService, "NVIDIA", miLgAddNvPreset_Click); - ServiceFormUtils.BuildServicePresetsMenu(mnuLgAmdPresets, _serviceManager.AmdService, "AMD", miLgAddAmdPreset_Click); - } + BuildLgActionMenu(device, mnuLgActions.DropDownItems, mnuLgActions.Name, miLgAddAction_Click); + ServiceFormUtils.BuildServicePresetsMenu(mnuLgNvPresets, _serviceManager.NvService, "NVIDIA", miLgAddNvPreset_Click); + ServiceFormUtils.BuildServicePresetsMenu(mnuLgAmdPresets, _serviceManager.AmdService, "AMD", miLgAddAmdPreset_Click); + } - private void btnLgDeviceConvertToCustom_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo( + private void btnLgDeviceConvertToCustom_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo( @"This will convert the automatically detected device to a custom variant. This means that the device will remain here even if it is not detected anymore. Do you want to continue?" - ) != DialogResult.Yes) - { - return; - } + ) != DialogResult.Yes) + { + return; + } - _lgService.SelectedDevice.ConvertToCustom(); + _lgService.SelectedDevice.ConvertToCustom(); - FillLgDevices(); - } + FillLgDevices(); + } - private void chkLgRemoteControlShow_CheckedChanged(object sender, EventArgs e) - { - scLgController.Panel2Collapsed = !chkLgRemoteControlShow.Checked; - _lgService.Config.ShowRemoteControl = chkLgRemoteControlShow.Checked; - } + private void chkLgRemoteControlShow_CheckedChanged(object sender, EventArgs e) + { + scLgController.Panel2Collapsed = !chkLgRemoteControlShow.Checked; + _lgService.Config.ShowRemoteControl = chkLgRemoteControlShow.Checked; + } - private void mnuLgExpert_Opening(object sender, CancelEventArgs e) - { - var device = _lgService.SelectedDevice; + private void mnuLgExpert_Opening(object sender, CancelEventArgs e) + { + var device = _lgService.SelectedDevice; - var eligibleModels = new[] { "B9", "C9", "E9", "W9", "C2", "G2" }; - var eligibleModelsEnable = new[] { "B9", "C9", "E9", "W9" }; + var eligibleModels = new[] { "B9", "C9", "E9", "W9", "C2", "G2" }; + var eligibleModelsEnable = new[] { "B9", "C9", "E9", "W9" }; - var visible = eligibleModels.Any(m => device?.ModelName?.Contains(m) == true); - var enableVisible = eligibleModelsEnable.Any(m => device?.ModelName?.Contains(m) == true); - mnuLgOLEDMotionPro.Visible = visible; - miLgEnableMotionPro.Visible = enableVisible; + var visible = eligibleModels.Any(m => device?.ModelName?.Contains(m) == true); + var enableVisible = eligibleModelsEnable.Any(m => device?.ModelName?.Contains(m) == true); + mnuLgOLEDMotionPro.Visible = visible; + miLgEnableMotionPro.Visible = enableVisible; - var svcMenuFlagVisible = new[] - { - "A2", "B2", "C2", "G2", "CS2", - "A3", "B3", "C3", "G3", "CS3", - "C4", "G4", "M4", "CS4" - }.Any(m => device?.ModelName?.Contains(m) == true); + var svcMenuFlagVisible = new[] + { + "A2", "B2", "C2", "G2", "CS2", + "A3", "B3", "C3", "G3", "CS3", + "C4", "G4", "M4", "CS4" + }.Any(m => device?.ModelName?.Contains(m) == true); - mnuLgSetSvcMenuFlag.Visible = svcMenuFlagVisible; + mnuLgSetSvcMenuFlag.Visible = svcMenuFlagVisible; - miLgExpertSeparator1.Visible = visible || svcMenuFlagVisible; + miLgExpertSeparator1.Visible = visible || svcMenuFlagVisible; - BuildLgActionMenu(device, mnuLgExpert.Items, mnuLgExpert.Name, btnLgExpertColorGamut_Click, _lgService.Config.ShowAdvancedActions, true); - } + BuildLgActionMenu(device, mnuLgExpert.Items, mnuLgExpert.Name, btnLgExpertColorGamut_Click, _lgService.Config.ShowAdvancedActions, true); + } - private void BuildLgActionMenu(LgDevice device, ToolStripItemCollection parent, string parentName, EventHandler clickEvent, bool showAdvanced = false, bool showGameBar = false) - { - if (device == null) - { - return; - } + private void BuildLgActionMenu(LgDevice device, ToolStripItemCollection parent, string parentName, EventHandler clickEvent, bool showAdvanced = false, bool showGameBar = false) + { + if (device == null) + { + return; + } - var actions = device.GetInvokableActions(showAdvanced); - var gameBarActions = device.GetInvokableActionsForGameBar(); - var activatedGameBarActions = device.GetActionsForGameBar(); + var actions = device.GetInvokableActions(showAdvanced); + var gameBarActions = device.GetInvokableActionsForGameBar(); + var activatedGameBarActions = device.GetActionsForGameBar(); - const string gameBarName = "miGameBar"; + const string gameBarName = "miGameBar"; - var expertActions = actions.Where(a => a.EnumType != null || a.MaxValue > a.MinValue || a.AsyncFunction != null).ToList(); + var expertActions = actions.Where(a => a.EnumType != null || a.MaxValue > a.MinValue || a.AsyncFunction != null).ToList(); - var categories = expertActions.Select(a => a.Category ?? "misc").Where(c => !string.IsNullOrEmpty(c)).Distinct(); + var categories = expertActions.Select(a => a.Category ?? "misc").Where(c => !string.IsNullOrEmpty(c)).Distinct(); - foreach (var category in categories) - { - var catMenuItem = FormUtils.BuildDropDownMenuEx(parent, parentName, Utils.FirstCharUpperCase(category), null, null, category); + foreach (var category in categories) + { + var catMenuItem = FormUtils.BuildDropDownMenuEx(parent, parentName, Utils.FirstCharUpperCase(category), null, null, category); - foreach (var action in expertActions.Where(a => (a.Category ?? "misc") == category)) - { - if (!showGameBar) - { - var text = action.Title ?? action.Name; + foreach (var action in expertActions.Where(a => (a.Category ?? "misc") == category)) + { + if (!showGameBar) + { + var text = action.Title ?? action.Name; - var item = catMenuItem.DropDownItems.AddCustom(text); - item.Tag = action; - item.Click += clickEvent; - continue; - } + var item = catMenuItem.DropDownItems.AddCustom(text); + item.Tag = action; + item.Click += clickEvent; + continue; + } - var menu = FormUtils.BuildDropDownMenuEx(catMenuItem.DropDownItems, catMenuItem.Name, action.Title, action.EnumType, clickEvent, action, (int)action.MinValue, (int)action.MaxValue, action.NumberOfValues > 1); + var menu = FormUtils.BuildDropDownMenuEx(catMenuItem.DropDownItems, catMenuItem.Name, action.Title, action.EnumType, clickEvent, action, (int)action.MinValue, (int)action.MaxValue, action.NumberOfValues > 1); - if (!gameBarActions.Contains(action)) - { - continue; - } + if (!gameBarActions.Contains(action)) + { + continue; + } - var itemName = $"{menu.Name}_{gameBarName}"; - var gameBarItem = (ToolStripMenuItem)menu.DropDownItems.Find(itemName, false).FirstOrDefault(); + var itemName = $"{menu.Name}_{gameBarName}"; + var gameBarItem = (ToolStripMenuItem)menu.DropDownItems.Find(itemName, false).FirstOrDefault(); - if (gameBarItem == null) - { - var separator = new ToolStripSeparator(); - menu.DropDownItems.Add(separator); + if (gameBarItem == null) + { + var separator = new ToolStripSeparator(); + menu.DropDownItems.Add(separator); - gameBarItem = (ToolStripMenuItem)menu.DropDownItems.Add("Show in Game Bar", null, miLgGameBarToggle_Click); - gameBarItem.CheckOnClick = true; - gameBarItem.Checked = activatedGameBarActions.Contains(action); - gameBarItem.Name = itemName; - gameBarItem.Tag = action.Name; - gameBarItem.ForeColor = FormUtils.MenuItemForeColor; - } - } - } + gameBarItem = (ToolStripMenuItem)menu.DropDownItems.Add("Show in Game Bar", null, miLgGameBarToggle_Click); + gameBarItem.CheckOnClick = true; + gameBarItem.Checked = activatedGameBarActions.Contains(action); + gameBarItem.Name = itemName; + gameBarItem.Tag = action.Name; + gameBarItem.ForeColor = FormUtils.MenuItemForeColor; + } + } + } - if (!showAdvanced) - { - return; - } + if (!showAdvanced) + { + return; + } - var presetActions = actions.Where(a => a.Preset != null); + var presetActions = actions.Where(a => a.Preset != null); - if (presetActions.Any() && parent.Find("miLgExpertActionsSeparator", false).Length == 0) - { - parent.Add(new ToolStripSeparator - { - Name = "miLgExpertActionsSeparator" - }); - } + if (presetActions.Any() && parent.Find("miLgExpertActionsSeparator", false).Length == 0) + { + parent.Add(new ToolStripSeparator + { + Name = "miLgExpertActionsSeparator" + }); + } - foreach (var presetAction in presetActions) - { - var menu = FormUtils.BuildDropDownMenuEx(parent, parentName, presetAction.Name, null, btnLgExpertPresetAction_Click, presetAction.Preset); - } - } + foreach (var presetAction in presetActions) + { + var menu = FormUtils.BuildDropDownMenuEx(parent, parentName, presetAction.Name, null, btnLgExpertPresetAction_Click, presetAction.Preset); + } + } - private void miLgGameBarToggle_Click(object sender, EventArgs e) - { - var device = _lgService.SelectedDevice; + private void miLgGameBarToggle_Click(object sender, EventArgs e) + { + var device = _lgService.SelectedDevice; - if (device == null) - { - return; - } + if (device == null) + { + return; + } - var item = sender as ToolStripMenuItem; - if (item.Checked) - { - device.AddGameBarAction((string)item.Tag); - } - else - { - device.RemoveGameBarAction((string)item.Tag); - } - } + var item = sender as ToolStripMenuItem; + if (item.Checked) + { + device.AddGameBarAction((string)item.Tag); + } + else + { + device.RemoveGameBarAction((string)item.Tag); + } + } - private void btnLgExpert_Click(object sender, EventArgs e) - { - mnuLgExpert.ShowCustom(btnLgExpert); - } + private void btnLgExpert_Click(object sender, EventArgs e) + { + mnuLgExpert.ShowCustom(btnLgExpert); + } - private async void btnLgExpertColorGamut_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var action = item.Tag as LgDevice.InvokableAction; - var value = item.AccessibleName ?? item.Text; + private async void btnLgExpertColorGamut_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var action = item.Tag as LgDevice.InvokableAction; + var value = item.AccessibleName ?? item.Text; - if (action.NumberOfValues > 1) - { - ApplyLgExpertValueRange(action); - return; - } + if (action.NumberOfValues > 1) + { + ApplyLgExpertValueRange(action); + return; + } - try - { - await _lgService.SelectedDevice?.ExecuteActionAsync(action, new[] { value }); - } - catch (InvalidOperationException ex) - { - Logger.Error(ex); + try + { + await _lgService.SelectedDevice?.ExecuteActionAsync(action, new[] { value }); + } + catch (InvalidOperationException ex) + { + Logger.Error(ex); - MessageForms.ErrorOk("Error exectuting action: " + ex.Message); - } - } + MessageForms.ErrorOk("Error exectuting action: " + ex.Message); + } + } - private async void ApplyLgExpertValueRange(LgDevice.InvokableAction action) - { - var value = ShowLgActionForm(action); + private async void ApplyLgExpertValueRange(LgDevice.InvokableAction action) + { + var value = ShowLgActionForm(action); - if (value == null) - { - return; - } + if (value == null) + { + return; + } - var values = value.Split(";"); + var values = value.Split(";"); - await _lgService.SelectedDevice?.ExecuteActionAsync(action, values); - } + await _lgService.SelectedDevice?.ExecuteActionAsync(action, values); + } - private async void btnLgExpertPresetAction_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var preset = (LgPreset)item.Tag; + private async void btnLgExpertPresetAction_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = (LgPreset)item.Tag; - await _lgService.ApplyPreset(preset); - } + await _lgService.ApplyPreset(preset); + } - private async void miLgEnableMotionPro_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo("Are you sure you want to enable OLED Motion Pro? This app and its creator are in no way accountable for any damages it may cause to your tv.") == DialogResult.Yes) - { - await _lgService.SelectedDevice?.SetOLEDMotionPro("OLED Motion Pro"); + private async void miLgEnableMotionPro_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo("Are you sure you want to enable OLED Motion Pro? This app and its creator are in no way accountable for any damages it may cause to your tv.") == DialogResult.Yes) + { + await _lgService.SelectedDevice?.SetOLEDMotionPro("OLED Motion Pro"); - MessageForms.InfoOk("Setting applied."); - } - } + MessageForms.InfoOk("Setting applied."); + } + } - private async void miLgDisableMotionPro_Click(object sender, EventArgs e) - { - await _lgService.SelectedDevice?.SetOLEDMotionPro("OLED Motion"); + private async void miLgDisableMotionPro_Click(object sender, EventArgs e) + { + await _lgService.SelectedDevice?.SetOLEDMotionPro("OLED Motion"); - MessageForms.InfoOk("Setting applied."); - } + MessageForms.InfoOk("Setting applied."); + } - private void cbxLgApps_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == ((char)Keys.Back)) - { - cbxLgApps.SelectedIndex = -1; - } - } + private void cbxLgApps_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == ((char)Keys.Back)) + { + cbxLgApps.SelectedIndex = -1; + } + } - private void btnLgPresetEditTriggerConditions_Click(object sender, EventArgs e) - { - var preset = GetSelectedLgPreset(); + private void btnLgPresetEditTriggerConditions_Click(object sender, EventArgs e) + { + var preset = GetSelectedLgPreset(); - if (preset == null) - { - return; - } + if (preset == null) + { + return; + } - var dropDownValues = Utils.GetDescriptions(fromValue: 1); + var dropDownValues = Utils.GetDescriptions(fromValue: 1); - var values = MessageForms.ShowDialog("Set trigger conditions", new[] { - new FieldDefinition - { - Label = "Set desired trigger conditions", - FieldType = FieldType.Flags, - Values = dropDownValues, - Value = edtLgPresetTriggerConditions.Tag ?? 0 - } - }); + var values = MessageForms.ShowDialog("Set trigger conditions", new[] { + new FieldDefinition + { + Label = "Set desired trigger conditions", + FieldType = FieldType.Flags, + Values = dropDownValues, + Value = edtLgPresetTriggerConditions.Tag ?? 0 + } + }); - if (!values.Any()) - { - return; - } + if (!values.Any()) + { + return; + } - var value = (PresetConditionType)values.First().Value; - edtLgPresetTriggerConditions.Tag = (PresetConditionType)values.First().Value; - edtLgPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); - } + var value = (PresetConditionType)values.First().Value; + edtLgPresetTriggerConditions.Tag = (PresetConditionType)values.First().Value; + edtLgPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); + } - private void btnLgGameBar_Click(object sender, EventArgs e) - { - _lgService.ToggleGameBar(); - } + private void btnLgGameBar_Click(object sender, EventArgs e) + { + _lgService.ToggleGameBar(); + } - private void edtLgGameBarShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } + private void edtLgGameBarShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); + } - private void edtLgGameBarShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } + private void edtLgGameBarShortcut_KeyUp(object sender, KeyEventArgs e) + { + KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); + } - private void btnLgDeviceOptionsHelp_Click(object sender, EventArgs e) - { - MessageForms.InfoOk( + private void btnLgDeviceOptionsHelp_Click(object sender, EventArgs e) + { + MessageForms.InfoOk( @"Notes: - power on after startup requires ""Automatically start after login"" - see Options - power on after resume from standby may need some retries for waking TV - see Options - power off on shutdown: because this app cannot detect a restart, restarting could also trigger this. Hold down Ctrl on restart to prevent power off. - Use Windows power settings: powering on/off of TV will follow Windows settings. If activated, it's better to disable all other power options except power off on shutdown. "); - } + } - private void cbxLgPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) - { - if (_lgService?.SelectedDevice == null) - { - return; - } + private void cbxLgPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) + { + if (_lgService?.SelectedDevice == null) + { + return; + } - _lgService.SelectedDevice.HDMIPortNumber = cbxLgPcHdmiPort.SelectedIndex; - } + _lgService.SelectedDevice.HDMIPortNumber = cbxLgPcHdmiPort.SelectedIndex; + } - private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e) - { - FormUtils.ListViewSort(sender, e); - } + private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e) + { + FormUtils.ListViewSort(sender, e); + } - private void mnuLgProgram_Click(object sender, EventArgs e) - { - var file = Utils.SelectFile(); + private void mnuLgProgram_Click(object sender, EventArgs e) + { + var file = Utils.SelectFile(); - if (file != null) - { - FormUtils.AddStepToTextBox(edtStepsLg, $"StartProgram({file.FullName})"); - } - } + if (file != null) + { + FormUtils.AddStepToTextBox(edtStepsLg, $"StartProgram({file.FullName})"); + } + } - private void btnLgSettings_Click(object sender, EventArgs e) - { - mnuLgSettings.ShowCustom(btnLgSettings); - } + private void btnLgSettings_Click(object sender, EventArgs e) + { + mnuLgSettings.ShowCustom(btnLgSettings); + } - private void lvLgPresets_ItemChecked(object sender, ItemCheckedEventArgs e) - { - ServiceFormUtils.ListViewItemChecked(lvLgPresets, e); - } + private void lvLgPresets_ItemChecked(object sender, ItemCheckedEventArgs e) + { + ServiceFormUtils.ListViewItemChecked(lvLgPresets, e); + } - private async void miTestPowerOffOn_Click(object sender, EventArgs e) - { - var text = + private async void miTestPowerOffOn_Click(object sender, EventArgs e) + { + var text = @"The TV will now power off. Please wait for the TV to be powered off completely (relay click) and press ENTER to wake it again. For waking up to work, you need to activate the following setting on the TV: @@ -1108,128 +1112,128 @@ You can also activate this option by using the Expert-button and selecting Wake- Do you want to continue?"; - if (MessageForms.QuestionYesNo(text) == DialogResult.Yes) - { - await _lgService.PowerOff(); + if (MessageForms.QuestionYesNo(text) == DialogResult.Yes) + { + await _lgService.PowerOff(); - MessageForms.InfoOk("Press ENTER to wake the TV."); + MessageForms.InfoOk("Press ENTER to wake the TV."); - _lgService.WakeSelectedDevice(); - } - } + _lgService.WakeSelectedDevice(); + } + } - private void miNvSettings_Click(object sender, EventArgs e) - { - var advancedWasEnabled = _lgService.Config.ShowAdvancedActions; + private void miNvSettings_Click(object sender, EventArgs e) + { + var advancedWasEnabled = _lgService.Config.ShowAdvancedActions; - var retriesField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Maximum number of retries powering on after startup/resume.", - SubLabel = "Retries are necessary to wait for the network link of your pc to be established.", - MinValue = 1, - MaxValue = 40, - Value = _lgService.Config.PowerOnRetries - }; + var retriesField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Maximum number of retries powering on after startup/resume.", + SubLabel = "Retries are necessary to wait for the network link of your pc to be established.", + MinValue = 1, + MaxValue = 40, + Value = _lgService.Config.PowerOnRetries + }; - var restartDelayField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Delay when shutting down/restarting PC (milliseconds).", - SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", - MinValue = 0, - MaxValue = 5000, - Value = _lgService.Config.ShutdownDelay - }; + var restartDelayField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Delay when shutting down/restarting PC (milliseconds).", + SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", + MinValue = 0, + MaxValue = 5000, + Value = _lgService.Config.ShutdownDelay + }; - var quickAccessField = new FieldDefinition - { - FieldType = FieldType.Shortcut, - Label = "Quick Access shortcut", - Value = _lgService.Config.QuickAccessShortcut - }; + var quickAccessField = new FieldDefinition + { + FieldType = FieldType.Shortcut, + Label = "Quick Access shortcut", + Value = _lgService.Config.QuickAccessShortcut + }; - var gameBarShortcutField = new FieldDefinition - { - FieldType = FieldType.Shortcut, - Label = "Game Bar shortcut", - Value = _lgService.Config.GameBarShortcut - }; + var gameBarShortcutField = new FieldDefinition + { + FieldType = FieldType.Shortcut, + Label = "Game Bar shortcut", + Value = _lgService.Config.GameBarShortcut + }; - var gameBarTimeoutField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Game Bar showing time (milliseconds)", - MinValue = 1000, - MaxValue = 60000, - Value = _lgService.Config.GameBarShowingTime - }; + var gameBarTimeoutField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Game Bar showing time (milliseconds)", + MinValue = 1000, + MaxValue = 60000, + Value = _lgService.Config.GameBarShowingTime + }; - var advancedField = new FieldDefinition - { - FieldType = FieldType.CheckBox, - Label = "Show advanced actions under the Expert-button (InStart, EzAdjust, Software Update)", - Value = _lgService.Config.ShowAdvancedActions - }; + var advancedField = new FieldDefinition + { + FieldType = FieldType.CheckBox, + Label = "Show advanced actions under the Expert-button (InStart, EzAdjust, Software Update)", + Value = _lgService.Config.ShowAdvancedActions + }; - var setSelectedDeviceField = new FieldDefinition - { - FieldType = FieldType.CheckBox, - Label = "Automatically set selected device to last powered on", - Value = _lgService.Config.SetSelectedDeviceByPowerOn - }; + var setSelectedDeviceField = new FieldDefinition + { + FieldType = FieldType.CheckBox, + Label = "Automatically set selected device to last powered on", + Value = _lgService.Config.SetSelectedDeviceByPowerOn + }; - var buttonDelayField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Default delay between remote control button presses (milliseconds).", - SubLabel = "Increasing this delay may prevent skipped button presses.", - MinValue = 100, - MaxValue = 2000, - Value = _lgService.Config.DefaultButtonDelay - }; + var buttonDelayField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Default delay between remote control button presses (milliseconds).", + SubLabel = "Increasing this delay may prevent skipped button presses.", + MinValue = 100, + MaxValue = 2000, + Value = _lgService.Config.DefaultButtonDelay + }; - var fields = new FieldDefinition[] - { - retriesField, restartDelayField, quickAccessField, gameBarShortcutField, gameBarTimeoutField, advancedField, setSelectedDeviceField, buttonDelayField - }; + var fields = new FieldDefinition[] + { + retriesField, restartDelayField, quickAccessField, gameBarShortcutField, gameBarTimeoutField, advancedField, setSelectedDeviceField, buttonDelayField + }; - var values = MessageForms.ShowDialog("LG controller settings", fields); + var values = MessageForms.ShowDialog("LG controller settings", fields); - if (values?.Any() != true) - { - return; - } + if (values?.Any() != true) + { + return; + } - _lgService.Config.PowerOnRetries = retriesField.ValueAsInt; - _lgService.Config.ShutdownDelay = restartDelayField.ValueAsInt; + _lgService.Config.PowerOnRetries = retriesField.ValueAsInt; + _lgService.Config.ShutdownDelay = restartDelayField.ValueAsInt; - var shortcutQA = quickAccessField.Value.ToString(); + var shortcutQA = quickAccessField.Value.ToString(); - _lgService.Config.QuickAccessShortcut = shortcutQA; + _lgService.Config.QuickAccessShortcut = shortcutQA; - _keyboardShortcutDispatcher.RegisterShortcut(LgService.SHORTCUTID_LGQA, _lgService.Config.QuickAccessShortcut); + _keyboardShortcutDispatcher.RegisterShortcut(LgService.SHORTCUTID_LGQA, _lgService.Config.QuickAccessShortcut); - var shortcutGB = gameBarShortcutField.Value.ToString(); - _lgService.Config.GameBarShortcut = shortcutGB; - _lgService.Config.GameBarShowingTime = gameBarTimeoutField.ValueAsInt; + var shortcutGB = gameBarShortcutField.Value.ToString(); + _lgService.Config.GameBarShortcut = shortcutGB; + _lgService.Config.GameBarShowingTime = gameBarTimeoutField.ValueAsInt; - if (string.IsNullOrEmpty(shortcutGB)) - { - WinApi.UnregisterHotKey(_globalContext.MainHandle, LgService.SHORTCUTID_GAMEBAR); - } - else - { - _keyboardShortcutDispatcher.RegisterShortcut(LgService.SHORTCUTID_GAMEBAR, shortcutGB); - } + if (string.IsNullOrEmpty(shortcutGB)) + { + WinApi.UnregisterHotKey(_globalContext.MainHandle, LgService.SHORTCUTID_GAMEBAR); + } + else + { + _keyboardShortcutDispatcher.RegisterShortcut(LgService.SHORTCUTID_GAMEBAR, shortcutGB); + } - _lgService.Config.ShowAdvancedActions = advancedField.ValueAsBool; - _lgService.Config.SetSelectedDeviceByPowerOn = setSelectedDeviceField.ValueAsBool; - _lgService.Config.DefaultButtonDelay = buttonDelayField.ValueAsInt; + _lgService.Config.ShowAdvancedActions = advancedField.ValueAsBool; + _lgService.Config.SetSelectedDeviceByPowerOn = setSelectedDeviceField.ValueAsBool; + _lgService.Config.DefaultButtonDelay = buttonDelayField.ValueAsInt; - if (!advancedWasEnabled && _lgService.Config.ShowAdvancedActions) - { - if (MessageForms.QuestionYesNo( + if (!advancedWasEnabled && _lgService.Config.ShowAdvancedActions) + { + if (MessageForms.QuestionYesNo( @"Are you sure you want to enable the advanced actions under the Expert-button? These actions include: - InStart service menu @@ -1238,82 +1242,82 @@ These actions include: These features may cause irreversible damage to your tv and will void your warranty. This app and its creator are in no way accountable for any damages it may cause to your tv." - ) != DialogResult.Yes) - { - _lgService.Config.ShowAdvancedActions = false; - return; - } - MessageForms.InfoOk( + ) != DialogResult.Yes) + { + _lgService.Config.ShowAdvancedActions = false; + return; + } + MessageForms.InfoOk( @"Advanced actions activated. The InStart, EzAdjust and Software Update items are now visible under the Expert-button." - ); - } + ); + } - } + } - private void clbLgPower_SelectedIndexChanged(object sender, EventArgs e) - { - btnLgDeviceOptionsSettings.Enabled = clbLgPower.SelectedIndex == 4; - } + private void clbLgPower_SelectedIndexChanged(object sender, EventArgs e) + { + btnLgDeviceOptionsSettings.Enabled = clbLgPower.SelectedIndex == 4; + } - private void btnLgDeviceOptionsSettings_Click(object sender, EventArgs e) - { - var device = _lgService.SelectedDevice; - var selectedItem = clbLgPower.SelectedItem; + private void btnLgDeviceOptionsSettings_Click(object sender, EventArgs e) + { + var device = _lgService.SelectedDevice; + var selectedItem = clbLgPower.SelectedItem; - var durationField = new FieldDefinition - { - Label = "Enter the minimal duration the screen saver must be running (in seconds)", - FieldType = FieldType.Numeric, - MinValue = 0, - MaxValue = 3600, - Value = device.ScreenSaverMinimalDuration - }; - var turnOffField = new FieldDefinition - { - Label = "Turn screen off instead of power off", - FieldType = FieldType.CheckBox, - Value = device.TurnScreenOffOnScreenSaver - }; - var turnOnField = new FieldDefinition - { - Label = "Turn screen on instead of power on", - FieldType = FieldType.CheckBox, - Value = device.TurnScreenOnAfterScreenSaver - }; - var manualField = new FieldDefinition - { - Label = "Perform action even on manually executed screen saver", - FieldType = FieldType.CheckBox, - Value = device.HandleManualScreenSaver - }; + var durationField = new FieldDefinition + { + Label = "Enter the minimal duration the screen saver must be running (in seconds)", + FieldType = FieldType.Numeric, + MinValue = 0, + MaxValue = 3600, + Value = device.ScreenSaverMinimalDuration + }; + var turnOffField = new FieldDefinition + { + Label = "Turn screen off instead of power off", + FieldType = FieldType.CheckBox, + Value = device.TurnScreenOffOnScreenSaver + }; + var turnOnField = new FieldDefinition + { + Label = "Turn screen on instead of power on", + FieldType = FieldType.CheckBox, + Value = device.TurnScreenOnAfterScreenSaver + }; + var manualField = new FieldDefinition + { + Label = "Perform action even on manually executed screen saver", + FieldType = FieldType.CheckBox, + Value = device.HandleManualScreenSaver + }; - if (!MessageForms.ShowDialog($"Settings - {selectedItem}", new[] { durationField, turnOffField, turnOnField, manualField }).Any()) - { - return; - } + if (!MessageForms.ShowDialog($"Settings - {selectedItem}", new[] { durationField, turnOffField, turnOnField, manualField }).Any()) + { + return; + } - device.ScreenSaverMinimalDuration = durationField.ValueAsInt; - device.TurnScreenOffOnScreenSaver = turnOffField.ValueAsBool; - device.TurnScreenOnAfterScreenSaver = turnOnField.ValueAsBool; - device.HandleManualScreenSaver = manualField.ValueAsBool; - } + device.ScreenSaverMinimalDuration = durationField.ValueAsInt; + device.TurnScreenOffOnScreenSaver = turnOffField.ValueAsBool; + device.TurnScreenOnAfterScreenSaver = turnOnField.ValueAsBool; + device.HandleManualScreenSaver = manualField.ValueAsBool; + } - private async void miLgFullServiceMenuEnable_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo("Are you sure you want to enable the full service menu (instead of the simplified one)? This app and its creator are in no way accountable for any damages it may cause to your tv.") == DialogResult.Yes) - { - await _lgService.SelectedDevice?.SetSvcMenuFlag(false); + private async void miLgFullServiceMenuEnable_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo("Are you sure you want to enable the full service menu (instead of the simplified one)? This app and its creator are in no way accountable for any damages it may cause to your tv.") == DialogResult.Yes) + { + await _lgService.SelectedDevice?.SetSvcMenuFlag(false); - MessageForms.InfoOk("Setting applied."); - } - } + MessageForms.InfoOk("Setting applied."); + } + } - private async void miLgFullServiceMenuDisable_Click(object sender, EventArgs e) - { - await _lgService.SelectedDevice?.SetSvcMenuFlag(true); + private async void miLgFullServiceMenuDisable_Click(object sender, EventArgs e) + { + await _lgService.SelectedDevice?.SetSvcMenuFlag(true); - MessageForms.InfoOk("Setting applied."); - } - } + MessageForms.InfoOk("Setting applied."); + } + } } diff --git a/ColorControl/Services/LG/LgService.cs b/ColorControl/Services/LG/LgService.cs index ebde5e5..364997d 100644 --- a/ColorControl/Services/LG/LgService.cs +++ b/ColorControl/Services/LG/LgService.cs @@ -487,6 +487,7 @@ namespace ColorControl.Services.LG _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOn, PowerModeChanged); _sessionSwitchDispatcher.RegisterEventHandler(SessionSwitchDispatcher.Event_SessionSwitch, SessionSwitched); + _sessionSwitchDispatcher.RegisterAsyncEventHandler(SessionSwitchDispatcher.Event_SessionSwitch, SessionSwitchedAsync); _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); @@ -534,6 +535,20 @@ namespace ColorControl.Services.LG } } + private async Task SessionSwitchedAsync(object sender, SessionSwitchEventArgs e, CancellationToken token) + { + if (e.Reason == SessionSwitchReason.SessionLock) + { + var devices = Devices.Where(d => d.PowerOffAfterSessionLock); + PowerOffDevices(devices, PowerOnOffState.StandBy); + } + else if (e.Reason == SessionSwitchReason.SessionUnlock) + { + var devices = Devices.Where(d => d.PowerOnAfterSessionUnlock).ToList(); + await PowerOnDevicesTask(devices); + } + } + private async Task PowerModeResume(object sender, PowerStateChangedEventArgs e, CancellationToken _) { Logger.Debug($"PowerModeChanged: {e.State}"); @@ -557,7 +572,7 @@ namespace ColorControl.Services.LG { case PowerOnOffState.StandBy: { - ExecutePresetsForEvent(PresetTriggerType.Standby); + _ = ExecutePresetsForEvent(PresetTriggerType.Standby); var devices = Devices.Where(d => d.PowerOffOnStandby); PowerOffDevices(devices, PowerOnOffState.StandBy); @@ -565,7 +580,7 @@ namespace ColorControl.Services.LG } case PowerOnOffState.ShutDown: { - ExecutePresetsForEvent(PresetTriggerType.Shutdown); + _ = ExecutePresetsForEvent(PresetTriggerType.Shutdown); var devices = Devices.Where(d => d.PowerOffOnShutdown); @@ -648,12 +663,12 @@ namespace ColorControl.Services.LG return; } - var applicableDevices = Devices.Where(d => - d.PowerOffOnScreenSaver - || d.PowerOnAfterScreenSaver - || (d.TriggersEnabled - // only these trigger types are ever triggered by a process changed event - && _presets.Any(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch || t.Trigger == PresetTriggerType.ScreensaverStart || t.Trigger == PresetTriggerType.ScreensaverStop) + var applicableDevices = Devices.Where(d => + d.PowerOffOnScreenSaver + || d.PowerOnAfterScreenSaver + || (d.TriggersEnabled + // only these trigger types are ever triggered by a process changed event + && _presets.Any(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch || t.Trigger == PresetTriggerType.ScreensaverStart || t.Trigger == PresetTriggerType.ScreensaverStop) && ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))))) .ToList(); @@ -1084,6 +1099,8 @@ namespace ColorControl.Services.LG PowerOnAfterScreenSaver = d.PowerOnAfterScreenSaver, PowerOnAfterStartup = d.PowerOnAfterStartup, PowerOnByWindows = d.PowerOnByWindows, + PowerOffAfterSessionLock = d.PowerOffAfterSessionLock, + PowerOnAfterSessionUnlock = d.PowerOnAfterSessionUnlock, ScreenSaverMinimalDuration = d.ScreenSaverMinimalDuration, TriggersEnabled = d.TriggersEnabled, TurnScreenOffOnScreenSaver = d.TurnScreenOffOnScreenSaver, @@ -1120,7 +1137,7 @@ namespace ColorControl.Services.LG } } - SavePresets(); + SaveConfig(); return true; } diff --git a/ColorControl/Services/NVIDIA/Kernel32.cs b/ColorControl/Services/NVIDIA/Kernel32.cs index af1948b..4fa7b90 100644 --- a/ColorControl/Services/NVIDIA/Kernel32.cs +++ b/ColorControl/Services/NVIDIA/Kernel32.cs @@ -14,1544 +14,1544 @@ namespace ColorControl.Services.NVIDIA; public class Kernel32 { - public const int ERROR_SERVICE_ALREADY_RUNNING = unchecked((int)0x80070420); - - public const int ERROR_SERVICE_EXISTS = unchecked((int)0x80070431); - - internal const uint BATTERY_UNKNOWN_TIME = 0xFFFFFFFF; - internal const string IntelNVMeMiniPortSignature1 = "NvmeMini"; - internal const string IntelNVMeMiniPortSignature2 = "IntelNvm"; - - internal const uint LPTR = 0x0000 | 0x0040; - - internal const int MAX_DRIVE_ATTRIBUTES = 512; - internal const uint NVME_PASS_THROUGH_SRB_IO_CODE = 0xe0002000; - internal const byte SMART_LBA_HI = 0xC2; - internal const byte SMART_LBA_HI_EXCEEDED = 0x2C; - internal const byte SMART_LBA_MID = 0x4F; - internal const byte SMART_LBA_MID_EXCEEDED = 0xF4; - - private const string DllName = "kernel32.dll"; - - [Flags] - public enum NVME_CRITICAL_WARNING - { - None = 0x00, - - /// - /// If set to 1, then the available spare space has fallen below the threshold. - /// - AvailableSpaceLow = 0x01, - - /// - /// If set to 1, then a temperature is above an over temperature threshold or below an under temperature threshold. - /// - TemperatureThreshold = 0x02, - - /// - /// If set to 1, then the device reliability has been degraded due to significant media related errors or any internal error that degrades device reliability. - /// - ReliabilityDegraded = 0x04, - - /// - /// If set to 1, then the media has been placed in read only mode - /// - ReadOnly = 0x08, - - /// - /// If set to 1, then the volatile memory backup device has failed. This field is only valid if the controller has a volatile memory backup solution. - /// - VolatileMemoryBackupDeviceFailed = 0x10 - } - - /// - /// Create a instance from a struct with zero initialized memory arrays - /// no need to init every inner array with the correct sizes - /// - /// type of struct that is needed - /// - internal static T CreateStruct() - { - int size = Marshal.SizeOf(); - IntPtr ptr = Marshal.AllocHGlobal(size); - RtlZeroMemory(ptr, size); - T result = Marshal.PtrToStructure(ptr); - Marshal.FreeHGlobal(ptr); - return result; - } - - internal static SafeHandle OpenDevice(string devicePath) - { - SafeHandle hDevice = CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); - if (hDevice.IsInvalid || hDevice.IsClosed) - hDevice = null; - - return hDevice; - } - - [DllImport(DllName, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern SafeFileHandle CreateFile - ( - [MarshalAs(UnmanagedType.LPTStr)] string lpFileName, - [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, - [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, - IntPtr lpSecurityAttributes, - [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, - [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - DFP dwIoControlCode, - ref SENDCMDINPARAMS lpInBuffer, - int nInBufferSize, - out ATTRIBUTECMDOUTPARAMS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - DFP dwIoControlCode, - ref SENDCMDINPARAMS lpInBuffer, - int nInBufferSize, - out THRESHOLDCMDOUTPARAMS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - DFP dwIoControlCode, - ref SENDCMDINPARAMS lpInBuffer, - int nInBufferSize, - out SENDCMDOUTPARAMS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - DFP dwIoControlCode, - ref SENDCMDINPARAMS lpInBuffer, - int nInBufferSize, - out IDENTIFYCMDOUTPARAMS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - DFP dwIoControlCode, - ref SENDCMDINPARAMS lpInBuffer, - int nInBufferSize, - out STATUSCMDOUTPARAMS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - IOCTL dwIoControlCode, - ref STORAGE_PROPERTY_QUERY lpInBuffer, - int nInBufferSize, - out STORAGE_DEVICE_DESCRIPTOR_HEADER lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - IOCTL dwIoControlCode, - ref STORAGE_PROPERTY_QUERY lpInBuffer, - int nInBufferSize, - IntPtr lpOutBuffer, - uint nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeHandle hDevice, - IOCTL dwIoControlCode, - IntPtr lpInBuffer, - int nInBufferSize, - IntPtr lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle hDevice, - IOCTL dwIoControlCode, - ref BATTERY_QUERY_INFORMATION lpInBuffer, - int nInBufferSize, - ref BATTERY_INFORMATION lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle hDevice, - IOCTL dwIoControlCode, - ref uint lpInBuffer, - int nInBufferSize, - ref uint lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle hDevice, - IOCTL dwIoControlCode, - ref BATTERY_WAIT_STATUS lpInBuffer, - int nInBufferSize, - ref BATTERY_STATUS lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle hDevice, - IOCTL dwIoControlCode, - ref BATTERY_QUERY_INFORMATION lpInBuffer, - int nInBufferSize, - IntPtr lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle hDevice, - IOCTL dwIoControlCode, - ref BATTERY_QUERY_INFORMATION lpInBuffer, - int nInBufferSize, - ref uint lpOutBuffer, - int nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr LocalAlloc(uint uFlags, ulong uBytes); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr LocalFree(IntPtr hMem); - - [DllImport(DllName, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern void RtlZeroMemory(IntPtr Destination, int Length); - - [DllImport(DllName, SetLastError = false)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern void RtlCopyMemory(IntPtr Destination, IntPtr Source, uint Length); - - [DllImport(DllName, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr LoadLibrary(string lpFileName); - - [DllImport(DllName, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr GetProcAddress(IntPtr module, string methodName); - - [DllImport(DllName)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool FreeLibrary(IntPtr module); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr GetCurrentThread(); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern ushort GetActiveProcessorGroupCount(); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool SetThreadGroupAffinity(IntPtr thread, ref GROUP_AFFINITY groupAffinity, out GROUP_AFFINITY previousGroupAffinity); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, MEM flAllocationType, PAGE flProtect); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, MEM dwFreeType); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern bool DeviceIoControl - ( - SafeFileHandle device, - IOControlCode ioControlCode, - [MarshalAs(UnmanagedType.AsAny)][In] object inBuffer, - uint inBufferSize, - [MarshalAs(UnmanagedType.AsAny)][Out] object outBuffer, - uint nOutBufferSize, - out uint bytesReturned, - IntPtr overlapped); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern IntPtr CreateFile - ( - string lpFileName, - uint dwDesiredAccess, - FileShare dwShareMode, - IntPtr lpSecurityAttributes, - FileMode dwCreationDisposition, - FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern int EnumSystemFirmwareTables(Provider firmwareTableProviderSignature, IntPtr firmwareTableBuffer, int bufferSize); - - [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - internal static extern int GetSystemFirmwareTable(Provider firmwareTableProviderSignature, int firmwareTableID, IntPtr firmwareTableBuffer, int bufferSize); - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct GROUP_AFFINITY - { - public UIntPtr Mask; - - [MarshalAs(UnmanagedType.U2)] - public ushort Group; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U2)] - public ushort[] Reserved; - } - - internal enum DFP : uint - { - DFP_GET_VERSION = 0x00074080, - DFP_SEND_DRIVE_COMMAND = 0x0007c084, - DFP_RECEIVE_DRIVE_DATA = 0x0007c088 - } - - internal enum IOCTL : uint - { - IOCTL_SCSI_PASS_THROUGH = 0x04d004, - IOCTL_SCSI_MINIPORT = 0x04d008, - IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x04d014, - IOCTL_SCSI_GET_ADDRESS = 0x41018, - IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400, - IOCTL_BATTERY_QUERY_TAG = 0x294040, - IOCTL_BATTERY_QUERY_INFORMATION = 0x294044, - IOCTL_BATTERY_QUERY_STATUS = 0x29404C - } - - [Flags] - internal enum BatteryCapabilities : uint - { - BATTERY_CAPACITY_RELATIVE = 0x40000000, - BATTERY_IS_SHORT_TERM = 0x20000000, - BATTERY_SET_CHARGE_SUPPORTED = 0x00000001, - BATTERY_SET_DISCHARGE_SUPPORTED = 0x00000002, - BATTERY_SYSTEM_BATTERY = 0x80000000 - } - - internal enum BATTERY_QUERY_INFORMATION_LEVEL - { - BatteryInformation, - BatteryGranularityInformation, - BatteryTemperature, - BatteryEstimatedTime, - BatteryDeviceName, - BatteryManufactureDate, - BatteryManufactureName, - BatteryUniqueID, - BatterySerialNumber - } - - [StructLayout(LayoutKind.Sequential)] - internal struct BATTERY_QUERY_INFORMATION - { - public uint BatteryTag; - public BATTERY_QUERY_INFORMATION_LEVEL InformationLevel; - public uint AtRate; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct BATTERY_INFORMATION - { - public BatteryCapabilities Capabilities; - public byte Technology; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] Reserved; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public char[] Chemistry; - - public uint DesignedCapacity; - public uint FullChargedCapacity; - public uint DefaultAlert1; - public uint DefaultAlert2; - public uint CriticalBias; - public uint CycleCount; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct BATTERY_WAIT_STATUS - { - public uint BatteryTag; - public uint Timeout; - public uint PowerState; - public uint LowCapacity; - public uint HighCapacity; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct BATTERY_STATUS - { - public uint PowerState; - public uint Capacity; - public uint Voltage; - public int Rate; - } - - [Flags] - internal enum NVME_DIRECTION : uint - { - NVME_FROM_HOST_TO_DEV = 1, - NVME_FROM_DEV_TO_HOST = 2, - NVME_BI_DIRECTION = NVME_FROM_DEV_TO_HOST | NVME_FROM_HOST_TO_DEV - } - - internal enum NVME_LOG_PAGES - { - NVME_LOG_PAGE_ERROR_INFO = 0x01, - NVME_LOG_PAGE_HEALTH_INFO = 0x02, - NVME_LOG_PAGE_FIRMWARE_SLOT_INFO = 0x03, - NVME_LOG_PAGE_CHANGED_NAMESPACE_LIST = 0x04, - NVME_LOG_PAGE_COMMAND_EFFECTS = 0x05, - NVME_LOG_PAGE_DEVICE_SELF_TEST = 0x06, - NVME_LOG_PAGE_TELEMETRY_HOST_INITIATED = 0x07, - NVME_LOG_PAGE_TELEMETRY_CTLR_INITIATED = 0x08, - NVME_LOG_PAGE_RESERVATION_NOTIFICATION = 0x80, - NVME_LOG_PAGE_SANITIZE_STATUS = 0x81 - } - - internal enum ATA_COMMAND : byte - { - /// - /// SMART data requested. - /// - ATA_SMART = 0xB0, - - /// - /// Identify data is requested. - /// - ATA_IDENTIFY_DEVICE = 0xEC - } - - internal enum SCSI_IOCTL_DATA - { - SCSI_IOCTL_DATA_OUT = 0, - SCSI_IOCTL_DATA_IN = 1, - SCSI_IOCTL_DATA_UNSPECIFIED = 2 - } - - internal enum SMART_FEATURES : byte - { - /// - /// Read SMART data. - /// - SMART_READ_DATA = 0xD0, - - /// - /// Read SMART thresholds. - /// obsolete - /// - READ_THRESHOLDS = 0xD1, - - /// - /// Autosave SMART data. - /// - ENABLE_DISABLE_AUTOSAVE = 0xD2, - - /// - /// Save SMART attributes. - /// - SAVE_ATTRIBUTE_VALUES = 0xD3, - - /// - /// Set SMART to offline immediately. - /// - EXECUTE_OFFLINE_DIAGS = 0xD4, - - /// - /// Read SMART log. - /// - SMART_READ_LOG = 0xD5, - - /// - /// Write SMART log. - /// - SMART_WRITE_LOG = 0xD6, - - /// - /// Write SMART thresholds. - /// obsolete - /// - WRITE_THRESHOLDS = 0xD7, - - /// - /// Enable SMART. - /// - ENABLE_SMART = 0xD8, - - /// - /// Disable SMART. - /// - DISABLE_SMART = 0xD9, - - /// - /// Get SMART status. - /// - RETURN_SMART_STATUS = 0xDA, - - /// - /// Set SMART to offline automatically. - /// - ENABLE_DISABLE_AUTO_OFFLINE = 0xDB /* obsolete */ - } - - internal enum STORAGE_BUS_TYPE - { - BusTypeUnknown = 0x00, - BusTypeScsi, - BusTypeAtapi, - BusTypeAta, - BusType1394, - BusTypeSsa, - BusTypeFibre, - BusTypeUsb, - BusTypeRAID, - BusTypeiScsi, - BusTypeSas, - BusTypeSata, - BusTypeSd, - BusTypeMmc, - BusTypeVirtual, - BusTypeFileBackedVirtual, - BusTypeSpaces, - BusTypeNvme, - BusTypeSCM, - BusTypeMax, - BusTypeMaxReserved = 0x7F - } - - internal enum STORAGE_PROPERTY_ID - { - StorageDeviceProperty = 0, - StorageAdapterProperty, - StorageDeviceIdProperty, - StorageDeviceUniqueIdProperty, - StorageDeviceWriteCacheProperty, - StorageMiniportProperty, - StorageAccessAlignmentProperty, - StorageDeviceSeekPenaltyProperty, - StorageDeviceTrimProperty, - StorageDeviceWriteAggregationProperty, - StorageDeviceDeviceTelemetryProperty, - StorageDeviceLBProvisioningProperty, - StorageDevicePowerProperty, - StorageDeviceCopyOffloadProperty, - StorageDeviceResiliencyProperty, - StorageDeviceMediumProductType, - StorageAdapterRpmbProperty, - StorageDeviceIoCapabilityProperty = 48, - StorageAdapterProtocolSpecificProperty, - StorageDeviceProtocolSpecificProperty, - StorageAdapterTemperatureProperty, - StorageDeviceTemperatureProperty, - StorageAdapterPhysicalTopologyProperty, - StorageDevicePhysicalTopologyProperty, - StorageDeviceAttributesProperty, - StorageDeviceManagementStatus, - StorageAdapterSerialNumberProperty, - StorageDeviceLocationProperty - } - - internal enum STORAGE_PROTOCOL_NVME_DATA_TYPE - { - NVMeDataTypeUnknown = 0, - NVMeDataTypeIdentify, - NVMeDataTypeLogPage, - NVMeDataTypeFeature - } - - internal enum STORAGE_PROTOCOL_NVME_PROTOCOL_DATA_REQUEST_VALUE - { - NVMeIdentifyCnsSpecificNamespace = 0, - NVMeIdentifyCnsController = 1, - NVMeIdentifyCnsActiveNamespaces = 2 - } - - internal enum STORAGE_PROTOCOL_TYPE - { - ProtocolTypeUnknown = 0x00, - ProtocolTypeScsi, - ProtocolTypeAta, - ProtocolTypeNvme, - ProtocolTypeSd, - ProtocolTypeProprietary = 0x7E, - ProtocolTypeMaxReserved = 0x7F - } - - internal enum STORAGE_QUERY_TYPE - { - PropertyStandardQuery = 0, - PropertyExistsQuery, - PropertyMaskQuery, - PropertyQueryMaxDefined - } - - [StructLayout(LayoutKind.Sequential)] - internal struct MEMORYSTATUSEX - { - public uint dwLength; - public uint dwMemoryLoad; - public ulong ullTotalPhys; - public ulong ullAvailPhys; - public ulong ullTotalPageFile; - public ulong ullAvailPageFile; - public ulong ullTotalVirtual; - public ulong ullAvailVirtual; - public ulong ullAvailExtendedVirtual; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct SMART_ATTRIBUTE - { - public byte Id; - public short Flags; - public byte CurrentValue; - public byte WorstValue; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - public byte[] RawValue; - - public byte Reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct SMART_THRESHOLD - { - public byte Id; - public byte Threshold; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] - public byte[] Reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct SENDCMDINPARAMS - { - public uint cBufferSize; - public IDEREGS irDriveRegs; - public byte bDriveNumber; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] bReserved; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public uint[] dwReserved; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] - public byte[] bBuffer; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct IDEREGS - { - public SMART_FEATURES bFeaturesReg; - public byte bSectorCountReg; - public byte bSectorNumberReg; - public byte bCylLowReg; - public byte bCylHighReg; - public byte bDriveHeadReg; - public ATA_COMMAND bCommandReg; - public byte bReserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct DRIVERSTATUS - { - public byte bDriverError; - public byte bIDEError; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] - public byte[] Reserved; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct SENDCMDOUTPARAMS - { - public uint cBufferSize; - public DRIVERSTATUS DriverStatus; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] - public byte[] bBuffer; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct ATTRIBUTECMDOUTPARAMS - { - public uint cBufferSize; - public DRIVERSTATUS DriverStatus; - public byte Version; - public byte Reserved; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DRIVE_ATTRIBUTES)] - public SMART_ATTRIBUTE[] Attributes; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct THRESHOLDCMDOUTPARAMS - { - public uint cBufferSize; - public DRIVERSTATUS DriverStatus; - public byte Version; - public byte Reserved; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DRIVE_ATTRIBUTES)] - public SMART_THRESHOLD[] Thresholds; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct STATUSCMDOUTPARAMS - { - public uint cBufferSize; - public DRIVERSTATUS DriverStatus; - public IDEREGS irDriveRegs; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct IDENTIFY_DATA - { - public ushort GeneralConfiguration; - public ushort NumberOfCylinders; - public ushort Reserved1; - public ushort NumberOfHeads; - public ushort UnformattedBytesPerTrack; - public ushort UnformattedBytesPerSector; - public ushort SectorsPerTrack; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public ushort[] VendorUnique; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] SerialNumber; - - public ushort BufferType; - public ushort BufferSectorSize; - public ushort NumberOfEccBytes; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] FirmwareRevision; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] - public byte[] ModelNumber; - - public byte MaximumBlockTransfer; - public byte VendorUnique2; - public ushort DoubleWordIo; - public ushort Capabilities; - public ushort Reserved2; - public byte VendorUnique3; - public byte PioCycleTimingMode; - public byte VendorUnique4; - public byte DmaCycleTimingMode; - public ushort TranslationFieldsValid; - public ushort NumberOfCurrentCylinders; - public ushort NumberOfCurrentHeads; - public ushort CurrentSectorsPerTrack; - public uint CurrentSectorCapacity; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 197)] - public ushort[] Reserved3; - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct IDENTIFYCMDOUTPARAMS - { - public uint cBufferSize; - public DRIVERSTATUS DriverStatus; - public IDENTIFY_DATA Identify; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct STORAGE_PROPERTY_QUERY - { - public STORAGE_PROPERTY_ID PropertyId; - public STORAGE_QUERY_TYPE QueryType; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] - public byte[] AdditionalParameters; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct STORAGE_DEVICE_DESCRIPTOR_HEADER - { - public uint Version; - public uint Size; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct STORAGE_DEVICE_DESCRIPTOR - { - public uint Version; - public uint Size; - public byte DeviceType; - public byte DeviceTypeModifier; - - [MarshalAs(UnmanagedType.U1)] - public bool RemovableMedia; - - [MarshalAs(UnmanagedType.U1)] - public bool CommandQueueing; - - public uint VendorIdOffset; - public uint ProductIdOffset; - public uint ProductRevisionOffset; - public uint SerialNumberOffset; - public STORAGE_BUS_TYPE BusType; - public uint RawPropertiesLength; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SRB_IO_CONTROL - { - public uint HeaderLenght; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] Signature; - - public uint Timeout; - public uint ControlCode; - public uint ReturnCode; - public uint Length; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NVME_PASS_THROUGH_IOCTL - { - public SRB_IO_CONTROL srb; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - public uint[] VendorSpecific; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public uint[] NVMeCmd; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public uint[] CplEntry; - - public NVME_DIRECTION Direction; - public uint QueueId; - public uint DataBufferLen; - public uint MetaDataLen; - public uint ReturnBufferLen; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] - public byte[] DataBuffer; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SCSI_PASS_THROUGH - { - [MarshalAs(UnmanagedType.U2)] - public ushort Length; - - public byte ScsiStatus; - public byte PathId; - public byte TargetId; - public byte Lun; - public byte CdbLength; - public byte SenseInfoLength; - public byte DataIn; - public uint DataTransferLength; - public uint TimeOutValue; - public IntPtr DataBufferOffset; - public uint SenseInfoOffset; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] Cdb; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct SCSI_PASS_THROUGH_WITH_BUFFERS - { - public SCSI_PASS_THROUGH Spt; - - public uint Filler; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public byte[] SenseBuf; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] - public byte[] DataBuf; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NVME_POWER_STATE_DESC - { - /// - /// bit 0:15 Maximum Power (MP) in centiwatts - /// - public ushort MP; - - /// - /// bit 16:23 - /// - public byte Reserved0; - - /// - /// bit 24 Max Power Scale (MPS), bit 25 Non-Operational State (NOPS) - /// - public byte MPS_NOPS; - - /// - /// bit 32:63 Entry Latency (ENLAT) in microseconds - /// - public uint ENLAT; - - /// - /// bit 64:95 Exit Latency (EXLAT) in microseconds - /// - public uint EXLAT; - - /// - /// bit 96:100 Relative Read Throughput (RRT) - /// - public byte RRT; - - /// - /// bit 104:108 Relative Read Latency (RRL) - /// - public byte RRL; - - /// - /// bit 112:116 Relative Write Throughput (RWT) - /// - public byte RWT; - - /// - /// bit 120:124 Relative Write Latency (RWL) - /// - public byte RWL; - - /// - /// bit 128:143 Idle Power (IDLP) - /// - public ushort IDLP; - - /// - /// bit 150:151 Idle Power Scale (IPS) - /// - public byte IPS; - - /// - /// bit 152:159 - /// - public byte Reserved7; - - /// - /// bit 160:175 Active Power (ACTP) - /// - public ushort ACTP; - - /// - /// bit 176:178 Active Power Workload (APW), bit 182:183 Active Power Scale (APS) - /// - public byte APW_APS; - - /// - /// bit 184:255. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] - public byte[] Reserved9; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NVME_IDENTIFY_CONTROLLER_DATA - { - /// - /// byte 0:1 M - PCI Vendor ID (VID) - /// - public ushort VID; - - /// - /// byte 2:3 M - PCI Subsystem Vendor ID (SSVID) - /// - public ushort SSVID; - - /// - /// byte 4: 23 M - Serial Number (SN) - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] - public byte[] SN; - - /// - /// byte 24:63 M - Model Number (MN) - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] - public byte[] MN; - - /// - /// byte 64:71 M - Firmware Revision (FR) - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public byte[] FR; - - /// - /// byte 72 M - Recommended Arbitration Burst (RAB) - /// - public byte RAB; - - /// - /// byte 73:75 M - IEEE OUI Identifier (IEEE). Controller Vendor code. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - public byte[] IEEE; - - /// - /// byte 76 O - Controller Multi-Path I/O and Namespace Sharing Capabilities (CMIC) - /// - public byte CMIC; - - /// - /// byte 77 M - Maximum Data Transfer Size (MDTS) - /// - public byte MDTS; - - /// - /// byte 78:79 M - Controller ID (CNTLID) - /// - public ushort CNTLID; - - /// - /// byte 80:83 M - Version (VER) - /// - public uint VER; - - /// - /// byte 84:87 M - RTD3 Resume Latency (RTD3R) - /// - public uint RTD3R; - - /// - /// byte 88:91 M - RTD3 Entry Latency (RTD3E) - /// - public uint RTD3E; - - /// - /// byte 92:95 M - Optional Asynchronous Events Supported (OAES) - /// - public uint OAES; - - /// - /// byte 96:239. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 144)] - public byte[] Reserved0; - - /// - /// byte 240:255. Refer to the NVMe Management Interface Specification for definition. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] ReservedForManagement; - - /// - /// byte 256:257 M - Optional Admin Command Support (OACS) - /// - public ushort OACS; - - /// - /// byte 258 M - Abort Command Limit (ACL) - /// - public byte ACL; - - /// - /// byte 259 M - Asynchronous Event Request Limit (AERL) - /// - public byte AERL; - - /// - /// byte 260 M - Firmware Updates (FRMW) - /// - public byte FRMW; - - /// - /// byte 261 M - Log Page Attributes (LPA) - /// - public byte LPA; - - /// - /// byte 262 M - Error Log Page Entries (ELPE) - /// - public byte ELPE; - - /// - /// byte 263 M - Number of Power States Support (NPSS) - /// - public byte NPSS; - - /// - /// byte 264 M - Admin Vendor Specific Command Configuration (AVSCC) - /// - public byte AVSCC; - - /// - /// byte 265 O - Autonomous Power State Transition Attributes (APSTA) - /// - public byte APSTA; - - /// - /// byte 266:267 M - Warning Composite Temperature Threshold (WCTEMP) - /// - public ushort WCTEMP; - - /// - /// byte 268:269 M - Critical Composite Temperature Threshold (CCTEMP) - /// - public ushort CCTEMP; - - /// - /// byte 270:271 O - Maximum Time for Firmware Activation (MTFA) - /// - public ushort MTFA; - - /// - /// byte 272:275 O - Host Memory Buffer Preferred Size (HMPRE) - /// - public uint HMPRE; - - /// - /// byte 276:279 O - Host Memory Buffer Minimum Size (HMMIN) - /// - public uint HMMIN; - - /// - /// byte 280:295 O - Total NVM Capacity (TNVMCAP) - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] TNVMCAP; - - /// - /// byte 296:311 O - Unallocated NVM Capacity (UNVMCAP) - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] UNVMCAP; - - /// - /// byte 312:315 O - Replay Protected Memory Block Support (RPMBS) - /// - public uint RPMBS; - - /// - /// byte 316:511 - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 196)] - public byte[] Reserved1; - - /// - /// byte 512 M - Submission Queue Entry Size (SQES) - /// - public byte SQES; - - /// - /// byte 513 M - Completion Queue Entry Size (CQES) - /// - public byte CQES; - - /// - /// byte 514:515 - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] Reserved2; - - /// - /// byte 516:519 M - Number of Namespaces (NN) - /// - public uint NN; - - /// - /// byte 520:521 M - Optional NVM Command Support (ONCS) - /// - public ushort ONCS; - - /// - /// byte 522:523 M - Fused Operation Support (FUSES) - /// - public ushort FUSES; - - /// - /// byte 524 M - Format NVM Attributes (FNA) - /// - public byte FNA; - - /// - /// byte 525 M - Volatile Write Cache (VWC) - /// - public byte VWC; - - /// - /// byte 526:527 M - Atomic Write Unit Normal (AWUN) - /// - public ushort AWUN; - - /// - /// byte 528:529 M - Atomic Write Unit Power Fail (AWUPF) - /// - public ushort AWUPF; - - /// - /// byte 530 M - NVM Vendor Specific Command Configuration (NVSCC) - /// - public byte NVSCC; - - /// - /// byte 531 - /// - public byte Reserved3; - - /// - /// byte 532:533 O - Atomic Compare and Write Unit (ACWU) - /// - public ushort ACWU; - - /// - /// byte 534:535 - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] Reserved4; - - /// - /// byte 536:539 O - SGL Support (SGLS) - /// - public uint SGLS; - - /// - /// byte 540:703 - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 164)] - public byte[] Reserved5; - - /// - /// byte 704:2047 - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1344)] - public byte[] Reserved6; - - /// - /// byte 2048:3071 Power State Descriptors - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public NVME_POWER_STATE_DESC[] PDS; - - /// - /// byte 3072:4095 Vendor Specific - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] - public byte[] VS; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct NVME_HEALTH_INFO_LOG - { - /// - /// This field indicates critical warnings for the state of the controller. - /// Each bit corresponds to a critical warning type; multiple bits may be set. - /// - public byte CriticalWarning; - - /// - /// Composite Temperature: Contains the temperature of the overall device (controller and NVM included) in units of Kelvin. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - public byte[] CompositeTemp; - - /// - /// Available Spare: Contains a normalized percentage (0 to 100%) of the remaining spare capacity available - /// - public byte AvailableSpare; - - /// - /// Available Spare Threshold: When the Available Spare falls below the threshold indicated in this field, - /// an asynchronous event completion may occur. The value is indicated as a normalized percentage (0 to 100%). - /// - public byte AvailableSpareThreshold; - - /// - /// Percentage Used: Contains a vendor specific estimate of the percentage of NVM subsystem life used based on - /// the actual usage and the manufacturer’s prediction of NVM life. A value of 100 indicates that the estimated endurance of - /// the NVM in the NVM subsystem has been consumed, but may not indicate an NVM subsystem failure. The value is allowed to exceed 100. - /// - public byte PercentageUsed; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)] - public byte[] Reserved1; - - /// - /// Data Units Read: Contains the number of 512 byte data units the host has read from the controller; - /// this value does not include metadata. This value is reported in thousands - /// (i.e., a value of 1 corresponds to 1000 units of 512 bytes read) and is rounded up. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] DataUnitRead; - - /// - /// Data Units Written: Contains the number of 512 byte data units the host has written to the controller; - /// this value does not include metadata. This value is reported in thousands - /// (i.e., a value of 1 corresponds to 1000 units of 512 bytes written) and is rounded up. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] DataUnitWritten; - - /// - /// Host Read Commands: Contains the number of read commands completed by the controller. - /// For the NVM command set, this is the number of Compare and Read commands. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] HostReadCommands; - - /// - /// Host Write Commands: Contains the number of write commands completed by the controller. - /// For the NVM command set, this is the number of Write commands. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] HostWriteCommands; - - /// - /// Controller Busy Time: Contains the amount of time the controller is busy with I/O commands. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] ControllerBusyTime; - - /// - /// Power Cycles: Contains the number of power cycles. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] PowerCycles; - - /// - /// Power On Hours: Contains the number of power-on hours. - /// This does not include time that the controller was powered and in a low power state condition. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] PowerOnHours; - - /// - /// Unsafe Shutdowns: Contains the number of unsafe shutdowns. - /// This count is incremented when a shutdown notification is not received prior to loss of power. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] UnsafeShutdowns; - - /// - /// Media Errors: Contains the number of occurrences where the controller detected an unrecoverable data integrity error. - /// Errors such as uncorrectable ECC, CRC checksum failure, or LBA tag mismatch are included in this field. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] MediaAndDataIntegrityErrors; - - /// - /// Number of Error Information Log Entries: Contains the number of Error Information log entries over the life of the controller - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] - public byte[] NumberErrorInformationLogEntries; - - /// - /// Warning Composite Temperature Time: Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater than or equal to the Warning Composite - /// Temperature Threshold. - /// - public uint WarningCompositeTemperatureTime; - - /// - /// Critical Composite Temperature Time: Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater than the Critical Composite Temperature - /// Threshold. - /// - public uint CriticalCompositeTemperatureTime; - - /// - /// Contains the current temperature reported by temperature sensor 1-8. - /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public ushort[] TemperatureSensor; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 296)] - internal byte[] Reserved2; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct STORAGE_PROTOCOL_SPECIFIC_DATA - { - public STORAGE_PROTOCOL_TYPE ProtocolType; - public uint DataType; - public uint ProtocolDataRequestValue; - public uint ProtocolDataRequestSubValue; - public uint ProtocolDataOffset; - public uint ProtocolDataLength; - public uint FixedProtocolReturnData; - public uint ProtocolDataRequestSubValue2; - public uint ProtocolDataRequestSubValue3; - public uint Reserved; - } - - [StructLayout(LayoutKind.Sequential)] - internal struct STORAGE_QUERY_BUFFER - { - public STORAGE_PROPERTY_ID PropertyId; - public STORAGE_QUERY_TYPE QueryType; - public STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecific; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] - internal byte[] Buffer; - } - - [Flags] - internal enum MEM : uint - { - MEM_COMMIT = 0x1000, - MEM_RESERVE = 0x2000, - MEM_DECOMMIT = 0x4000, - MEM_RELEASE = 0x8000, - MEM_RESET = 0x80000, - MEM_LARGE_PAGES = 0x20000000, - MEM_PHYSICAL = 0x400000, - MEM_TOP_DOWN = 0x100000, - MEM_WRITE_WATCH = 0x200000 - } - - [Flags] - internal enum PAGE : uint - { - PAGE_EXECUTE = 0x10, - PAGE_EXECUTE_READ = 0x20, - PAGE_EXECUTE_READWRITE = 0x40, - PAGE_EXECUTE_WRITECOPY = 0x80, - PAGE_NOACCESS = 0x01, - PAGE_READONLY = 0x02, - PAGE_READWRITE = 0x04, - PAGE_WRITECOPY = 0x08, - PAGE_GUARD = 0x100, - PAGE_NOCACHE = 0x200, - PAGE_WRITECOMBINE = 0x400 - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal struct IOControlCode - { - /// - /// Gets the resulting IO control code. - /// - public uint Code { get; } - - /// - /// Initializes a new instance of the struct. - /// - /// Type of the device. - /// The function. - /// The access. - public IOControlCode(uint deviceType, uint function, Access access) : this(deviceType, function, Method.Buffered, access) - { } - - /// - /// Initializes a new instance of the struct. - /// - /// Type of the device. - /// The function. - /// The method. - /// The access. - public IOControlCode(uint deviceType, uint function, Method method, Access access) - { - Code = (deviceType << 16) | ((uint)access << 14) | (function << 2) | (uint)method; - } - - public enum Method : uint - { - Buffered = 0, - InDirect = 1, - OutDirect = 2, - Neither = 3 - } - - public enum Access : uint - { - Any = 0, - Read = 1, - Write = 2 - } - } - - internal enum Provider - { - ACPI = (byte)'A' << 24 | (byte)'C' << 16 | (byte)'P' << 8 | (byte)'I', - FIRM = (byte)'F' << 24 | (byte)'I' << 16 | (byte)'R' << 8 | (byte)'M', - RSMB = (byte)'R' << 24 | (byte)'S' << 16 | (byte)'M' << 8 | (byte)'B' - } + public const int ERROR_SERVICE_ALREADY_RUNNING = unchecked((int)0x80070420); + + public const int ERROR_SERVICE_EXISTS = unchecked((int)0x80070431); + + internal const uint BATTERY_UNKNOWN_TIME = 0xFFFFFFFF; + internal const string IntelNVMeMiniPortSignature1 = "NvmeMini"; + internal const string IntelNVMeMiniPortSignature2 = "IntelNvm"; + + internal const uint LPTR = 0x0000 | 0x0040; + + internal const int MAX_DRIVE_ATTRIBUTES = 512; + internal const uint NVME_PASS_THROUGH_SRB_IO_CODE = 0xe0002000; + internal const byte SMART_LBA_HI = 0xC2; + internal const byte SMART_LBA_HI_EXCEEDED = 0x2C; + internal const byte SMART_LBA_MID = 0x4F; + internal const byte SMART_LBA_MID_EXCEEDED = 0xF4; + + private const string DllName = "kernel32.dll"; + + [Flags] + public enum NVME_CRITICAL_WARNING + { + None = 0x00, + + /// + /// If set to 1, then the available spare space has fallen below the threshold. + /// + AvailableSpaceLow = 0x01, + + /// + /// If set to 1, then a temperature is above an over temperature threshold or below an under temperature threshold. + /// + TemperatureThreshold = 0x02, + + /// + /// If set to 1, then the device reliability has been degraded due to significant media related errors or any internal error that degrades device reliability. + /// + ReliabilityDegraded = 0x04, + + /// + /// If set to 1, then the media has been placed in read only mode + /// + ReadOnly = 0x08, + + /// + /// If set to 1, then the volatile memory backup device has failed. This field is only valid if the controller has a volatile memory backup solution. + /// + VolatileMemoryBackupDeviceFailed = 0x10 + } + + /// + /// Create a instance from a struct with zero initialized memory arrays + /// no need to init every inner array with the correct sizes + /// + /// type of struct that is needed + /// + internal static T CreateStruct() + { + int size = Marshal.SizeOf(); + IntPtr ptr = Marshal.AllocHGlobal(size); + RtlZeroMemory(ptr, size); + T result = Marshal.PtrToStructure(ptr); + Marshal.FreeHGlobal(ptr); + return result; + } + + internal static SafeHandle OpenDevice(string devicePath) + { + SafeHandle hDevice = CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); + if (hDevice.IsInvalid || hDevice.IsClosed) + hDevice = null; + + return hDevice; + } + + [DllImport(DllName, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern SafeFileHandle CreateFile + ( + [MarshalAs(UnmanagedType.LPTStr)] string lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out ATTRIBUTECMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out THRESHOLDCMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out SENDCMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out IDENTIFYCMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out STATUSCMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + IOCTL dwIoControlCode, + ref STORAGE_PROPERTY_QUERY lpInBuffer, + int nInBufferSize, + out STORAGE_DEVICE_DESCRIPTOR_HEADER lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + IOCTL dwIoControlCode, + ref STORAGE_PROPERTY_QUERY lpInBuffer, + int nInBufferSize, + IntPtr lpOutBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + IOCTL dwIoControlCode, + IntPtr lpInBuffer, + int nInBufferSize, + IntPtr lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_QUERY_INFORMATION lpInBuffer, + int nInBufferSize, + ref BATTERY_INFORMATION lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref uint lpInBuffer, + int nInBufferSize, + ref uint lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_WAIT_STATUS lpInBuffer, + int nInBufferSize, + ref BATTERY_STATUS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_QUERY_INFORMATION lpInBuffer, + int nInBufferSize, + IntPtr lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeFileHandle hDevice, + IOCTL dwIoControlCode, + ref BATTERY_QUERY_INFORMATION lpInBuffer, + int nInBufferSize, + ref uint lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr LocalAlloc(uint uFlags, ulong uBytes); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr LocalFree(IntPtr hMem); + + [DllImport(DllName, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern void RtlZeroMemory(IntPtr Destination, int Length); + + [DllImport(DllName, SetLastError = false)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern void RtlCopyMemory(IntPtr Destination, IntPtr Source, uint Length); + + [DllImport(DllName, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr LoadLibrary(string lpFileName); + + [DllImport(DllName, ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr GetProcAddress(IntPtr module, string methodName); + + [DllImport(DllName)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FreeLibrary(IntPtr module); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr GetCurrentThread(); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern ushort GetActiveProcessorGroupCount(); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern bool SetThreadGroupAffinity(IntPtr thread, ref GROUP_AFFINITY groupAffinity, out GROUP_AFFINITY previousGroupAffinity); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, MEM flAllocationType, PAGE flProtect); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, MEM dwFreeType); + + //[DllImport(DllName, CallingConvention = CallingConvention.Winapi)] + //[DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + //internal static extern bool DeviceIoControl + //( + // SafeFileHandle device, + // IOControlCode ioControlCode, + // [MarshalAs(UnmanagedType.AsAny)][In] object inBuffer, + // uint inBufferSize, + // [MarshalAs(UnmanagedType.AsAny)][Out] object outBuffer, + // uint nOutBufferSize, + // out uint bytesReturned, + // IntPtr overlapped); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern IntPtr CreateFile + ( + string lpFileName, + uint dwDesiredAccess, + FileShare dwShareMode, + IntPtr lpSecurityAttributes, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern int EnumSystemFirmwareTables(Provider firmwareTableProviderSignature, IntPtr firmwareTableBuffer, int bufferSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + internal static extern int GetSystemFirmwareTable(Provider firmwareTableProviderSignature, int firmwareTableID, IntPtr firmwareTableBuffer, int bufferSize); + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct GROUP_AFFINITY + { + public UIntPtr Mask; + + [MarshalAs(UnmanagedType.U2)] + public ushort Group; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U2)] + public ushort[] Reserved; + } + + internal enum DFP : uint + { + DFP_GET_VERSION = 0x00074080, + DFP_SEND_DRIVE_COMMAND = 0x0007c084, + DFP_RECEIVE_DRIVE_DATA = 0x0007c088 + } + + internal enum IOCTL : uint + { + IOCTL_SCSI_PASS_THROUGH = 0x04d004, + IOCTL_SCSI_MINIPORT = 0x04d008, + IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x04d014, + IOCTL_SCSI_GET_ADDRESS = 0x41018, + IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400, + IOCTL_BATTERY_QUERY_TAG = 0x294040, + IOCTL_BATTERY_QUERY_INFORMATION = 0x294044, + IOCTL_BATTERY_QUERY_STATUS = 0x29404C + } + + [Flags] + internal enum BatteryCapabilities : uint + { + BATTERY_CAPACITY_RELATIVE = 0x40000000, + BATTERY_IS_SHORT_TERM = 0x20000000, + BATTERY_SET_CHARGE_SUPPORTED = 0x00000001, + BATTERY_SET_DISCHARGE_SUPPORTED = 0x00000002, + BATTERY_SYSTEM_BATTERY = 0x80000000 + } + + internal enum BATTERY_QUERY_INFORMATION_LEVEL + { + BatteryInformation, + BatteryGranularityInformation, + BatteryTemperature, + BatteryEstimatedTime, + BatteryDeviceName, + BatteryManufactureDate, + BatteryManufactureName, + BatteryUniqueID, + BatterySerialNumber + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_QUERY_INFORMATION + { + public uint BatteryTag; + public BATTERY_QUERY_INFORMATION_LEVEL InformationLevel; + public uint AtRate; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_INFORMATION + { + public BatteryCapabilities Capabilities; + public byte Technology; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] Reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public char[] Chemistry; + + public uint DesignedCapacity; + public uint FullChargedCapacity; + public uint DefaultAlert1; + public uint DefaultAlert2; + public uint CriticalBias; + public uint CycleCount; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_WAIT_STATUS + { + public uint BatteryTag; + public uint Timeout; + public uint PowerState; + public uint LowCapacity; + public uint HighCapacity; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BATTERY_STATUS + { + public uint PowerState; + public uint Capacity; + public uint Voltage; + public int Rate; + } + + [Flags] + internal enum NVME_DIRECTION : uint + { + NVME_FROM_HOST_TO_DEV = 1, + NVME_FROM_DEV_TO_HOST = 2, + NVME_BI_DIRECTION = NVME_FROM_DEV_TO_HOST | NVME_FROM_HOST_TO_DEV + } + + internal enum NVME_LOG_PAGES + { + NVME_LOG_PAGE_ERROR_INFO = 0x01, + NVME_LOG_PAGE_HEALTH_INFO = 0x02, + NVME_LOG_PAGE_FIRMWARE_SLOT_INFO = 0x03, + NVME_LOG_PAGE_CHANGED_NAMESPACE_LIST = 0x04, + NVME_LOG_PAGE_COMMAND_EFFECTS = 0x05, + NVME_LOG_PAGE_DEVICE_SELF_TEST = 0x06, + NVME_LOG_PAGE_TELEMETRY_HOST_INITIATED = 0x07, + NVME_LOG_PAGE_TELEMETRY_CTLR_INITIATED = 0x08, + NVME_LOG_PAGE_RESERVATION_NOTIFICATION = 0x80, + NVME_LOG_PAGE_SANITIZE_STATUS = 0x81 + } + + internal enum ATA_COMMAND : byte + { + /// + /// SMART data requested. + /// + ATA_SMART = 0xB0, + + /// + /// Identify data is requested. + /// + ATA_IDENTIFY_DEVICE = 0xEC + } + + internal enum SCSI_IOCTL_DATA + { + SCSI_IOCTL_DATA_OUT = 0, + SCSI_IOCTL_DATA_IN = 1, + SCSI_IOCTL_DATA_UNSPECIFIED = 2 + } + + internal enum SMART_FEATURES : byte + { + /// + /// Read SMART data. + /// + SMART_READ_DATA = 0xD0, + + /// + /// Read SMART thresholds. + /// obsolete + /// + READ_THRESHOLDS = 0xD1, + + /// + /// Autosave SMART data. + /// + ENABLE_DISABLE_AUTOSAVE = 0xD2, + + /// + /// Save SMART attributes. + /// + SAVE_ATTRIBUTE_VALUES = 0xD3, + + /// + /// Set SMART to offline immediately. + /// + EXECUTE_OFFLINE_DIAGS = 0xD4, + + /// + /// Read SMART log. + /// + SMART_READ_LOG = 0xD5, + + /// + /// Write SMART log. + /// + SMART_WRITE_LOG = 0xD6, + + /// + /// Write SMART thresholds. + /// obsolete + /// + WRITE_THRESHOLDS = 0xD7, + + /// + /// Enable SMART. + /// + ENABLE_SMART = 0xD8, + + /// + /// Disable SMART. + /// + DISABLE_SMART = 0xD9, + + /// + /// Get SMART status. + /// + RETURN_SMART_STATUS = 0xDA, + + /// + /// Set SMART to offline automatically. + /// + ENABLE_DISABLE_AUTO_OFFLINE = 0xDB /* obsolete */ + } + + internal enum STORAGE_BUS_TYPE + { + BusTypeUnknown = 0x00, + BusTypeScsi, + BusTypeAtapi, + BusTypeAta, + BusType1394, + BusTypeSsa, + BusTypeFibre, + BusTypeUsb, + BusTypeRAID, + BusTypeiScsi, + BusTypeSas, + BusTypeSata, + BusTypeSd, + BusTypeMmc, + BusTypeVirtual, + BusTypeFileBackedVirtual, + BusTypeSpaces, + BusTypeNvme, + BusTypeSCM, + BusTypeMax, + BusTypeMaxReserved = 0x7F + } + + internal enum STORAGE_PROPERTY_ID + { + StorageDeviceProperty = 0, + StorageAdapterProperty, + StorageDeviceIdProperty, + StorageDeviceUniqueIdProperty, + StorageDeviceWriteCacheProperty, + StorageMiniportProperty, + StorageAccessAlignmentProperty, + StorageDeviceSeekPenaltyProperty, + StorageDeviceTrimProperty, + StorageDeviceWriteAggregationProperty, + StorageDeviceDeviceTelemetryProperty, + StorageDeviceLBProvisioningProperty, + StorageDevicePowerProperty, + StorageDeviceCopyOffloadProperty, + StorageDeviceResiliencyProperty, + StorageDeviceMediumProductType, + StorageAdapterRpmbProperty, + StorageDeviceIoCapabilityProperty = 48, + StorageAdapterProtocolSpecificProperty, + StorageDeviceProtocolSpecificProperty, + StorageAdapterTemperatureProperty, + StorageDeviceTemperatureProperty, + StorageAdapterPhysicalTopologyProperty, + StorageDevicePhysicalTopologyProperty, + StorageDeviceAttributesProperty, + StorageDeviceManagementStatus, + StorageAdapterSerialNumberProperty, + StorageDeviceLocationProperty + } + + internal enum STORAGE_PROTOCOL_NVME_DATA_TYPE + { + NVMeDataTypeUnknown = 0, + NVMeDataTypeIdentify, + NVMeDataTypeLogPage, + NVMeDataTypeFeature + } + + internal enum STORAGE_PROTOCOL_NVME_PROTOCOL_DATA_REQUEST_VALUE + { + NVMeIdentifyCnsSpecificNamespace = 0, + NVMeIdentifyCnsController = 1, + NVMeIdentifyCnsActiveNamespaces = 2 + } + + internal enum STORAGE_PROTOCOL_TYPE + { + ProtocolTypeUnknown = 0x00, + ProtocolTypeScsi, + ProtocolTypeAta, + ProtocolTypeNvme, + ProtocolTypeSd, + ProtocolTypeProprietary = 0x7E, + ProtocolTypeMaxReserved = 0x7F + } + + internal enum STORAGE_QUERY_TYPE + { + PropertyStandardQuery = 0, + PropertyExistsQuery, + PropertyMaskQuery, + PropertyQueryMaxDefined + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MEMORYSTATUSEX + { + public uint dwLength; + public uint dwMemoryLoad; + public ulong ullTotalPhys; + public ulong ullAvailPhys; + public ulong ullTotalPageFile; + public ulong ullAvailPageFile; + public ulong ullTotalVirtual; + public ulong ullAvailVirtual; + public ulong ullAvailExtendedVirtual; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SMART_ATTRIBUTE + { + public byte Id; + public short Flags; + public byte CurrentValue; + public byte WorstValue; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public byte[] RawValue; + + public byte Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SMART_THRESHOLD + { + public byte Id; + public byte Threshold; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public byte[] Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct SENDCMDINPARAMS + { + public uint cBufferSize; + public IDEREGS irDriveRegs; + public byte bDriveNumber; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] bReserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] dwReserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public byte[] bBuffer; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IDEREGS + { + public SMART_FEATURES bFeaturesReg; + public byte bSectorCountReg; + public byte bSectorNumberReg; + public byte bCylLowReg; + public byte bCylHighReg; + public byte bDriveHeadReg; + public ATA_COMMAND bCommandReg; + public byte bReserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct DRIVERSTATUS + { + public byte bDriverError; + public byte bIDEError; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public byte[] Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct SENDCMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public byte[] bBuffer; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct ATTRIBUTECMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + public byte Version; + public byte Reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DRIVE_ATTRIBUTES)] + public SMART_ATTRIBUTE[] Attributes; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct THRESHOLDCMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + public byte Version; + public byte Reserved; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DRIVE_ATTRIBUTES)] + public SMART_THRESHOLD[] Thresholds; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct STATUSCMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + public IDEREGS irDriveRegs; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IDENTIFY_DATA + { + public ushort GeneralConfiguration; + public ushort NumberOfCylinders; + public ushort Reserved1; + public ushort NumberOfHeads; + public ushort UnformattedBytesPerTrack; + public ushort UnformattedBytesPerSector; + public ushort SectorsPerTrack; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public ushort[] VendorUnique; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] SerialNumber; + + public ushort BufferType; + public ushort BufferSectorSize; + public ushort NumberOfEccBytes; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] FirmwareRevision; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] + public byte[] ModelNumber; + + public byte MaximumBlockTransfer; + public byte VendorUnique2; + public ushort DoubleWordIo; + public ushort Capabilities; + public ushort Reserved2; + public byte VendorUnique3; + public byte PioCycleTimingMode; + public byte VendorUnique4; + public byte DmaCycleTimingMode; + public ushort TranslationFieldsValid; + public ushort NumberOfCurrentCylinders; + public ushort NumberOfCurrentHeads; + public ushort CurrentSectorsPerTrack; + public uint CurrentSectorCapacity; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 197)] + public ushort[] Reserved3; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IDENTIFYCMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + public IDENTIFY_DATA Identify; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct STORAGE_PROPERTY_QUERY + { + public STORAGE_PROPERTY_ID PropertyId; + public STORAGE_QUERY_TYPE QueryType; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] + public byte[] AdditionalParameters; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct STORAGE_DEVICE_DESCRIPTOR_HEADER + { + public uint Version; + public uint Size; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct STORAGE_DEVICE_DESCRIPTOR + { + public uint Version; + public uint Size; + public byte DeviceType; + public byte DeviceTypeModifier; + + [MarshalAs(UnmanagedType.U1)] + public bool RemovableMedia; + + [MarshalAs(UnmanagedType.U1)] + public bool CommandQueueing; + + public uint VendorIdOffset; + public uint ProductIdOffset; + public uint ProductRevisionOffset; + public uint SerialNumberOffset; + public STORAGE_BUS_TYPE BusType; + public uint RawPropertiesLength; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SRB_IO_CONTROL + { + public uint HeaderLenght; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Signature; + + public uint Timeout; + public uint ControlCode; + public uint ReturnCode; + public uint Length; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NVME_PASS_THROUGH_IOCTL + { + public SRB_IO_CONTROL srb; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public uint[] VendorSpecific; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public uint[] NVMeCmd; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public uint[] CplEntry; + + public NVME_DIRECTION Direction; + public uint QueueId; + public uint DataBufferLen; + public uint MetaDataLen; + public uint ReturnBufferLen; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] + public byte[] DataBuffer; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SCSI_PASS_THROUGH + { + [MarshalAs(UnmanagedType.U2)] + public ushort Length; + + public byte ScsiStatus; + public byte PathId; + public byte TargetId; + public byte Lun; + public byte CdbLength; + public byte SenseInfoLength; + public byte DataIn; + public uint DataTransferLength; + public uint TimeOutValue; + public IntPtr DataBufferOffset; + public uint SenseInfoOffset; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] Cdb; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SCSI_PASS_THROUGH_WITH_BUFFERS + { + public SCSI_PASS_THROUGH Spt; + + public uint Filler; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public byte[] SenseBuf; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] + public byte[] DataBuf; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NVME_POWER_STATE_DESC + { + /// + /// bit 0:15 Maximum Power (MP) in centiwatts + /// + public ushort MP; + + /// + /// bit 16:23 + /// + public byte Reserved0; + + /// + /// bit 24 Max Power Scale (MPS), bit 25 Non-Operational State (NOPS) + /// + public byte MPS_NOPS; + + /// + /// bit 32:63 Entry Latency (ENLAT) in microseconds + /// + public uint ENLAT; + + /// + /// bit 64:95 Exit Latency (EXLAT) in microseconds + /// + public uint EXLAT; + + /// + /// bit 96:100 Relative Read Throughput (RRT) + /// + public byte RRT; + + /// + /// bit 104:108 Relative Read Latency (RRL) + /// + public byte RRL; + + /// + /// bit 112:116 Relative Write Throughput (RWT) + /// + public byte RWT; + + /// + /// bit 120:124 Relative Write Latency (RWL) + /// + public byte RWL; + + /// + /// bit 128:143 Idle Power (IDLP) + /// + public ushort IDLP; + + /// + /// bit 150:151 Idle Power Scale (IPS) + /// + public byte IPS; + + /// + /// bit 152:159 + /// + public byte Reserved7; + + /// + /// bit 160:175 Active Power (ACTP) + /// + public ushort ACTP; + + /// + /// bit 176:178 Active Power Workload (APW), bit 182:183 Active Power Scale (APS) + /// + public byte APW_APS; + + /// + /// bit 184:255. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] + public byte[] Reserved9; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NVME_IDENTIFY_CONTROLLER_DATA + { + /// + /// byte 0:1 M - PCI Vendor ID (VID) + /// + public ushort VID; + + /// + /// byte 2:3 M - PCI Subsystem Vendor ID (SSVID) + /// + public ushort SSVID; + + /// + /// byte 4: 23 M - Serial Number (SN) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + public byte[] SN; + + /// + /// byte 24:63 M - Model Number (MN) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)] + public byte[] MN; + + /// + /// byte 64:71 M - Firmware Revision (FR) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] FR; + + /// + /// byte 72 M - Recommended Arbitration Burst (RAB) + /// + public byte RAB; + + /// + /// byte 73:75 M - IEEE OUI Identifier (IEEE). Controller Vendor code. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + public byte[] IEEE; + + /// + /// byte 76 O - Controller Multi-Path I/O and Namespace Sharing Capabilities (CMIC) + /// + public byte CMIC; + + /// + /// byte 77 M - Maximum Data Transfer Size (MDTS) + /// + public byte MDTS; + + /// + /// byte 78:79 M - Controller ID (CNTLID) + /// + public ushort CNTLID; + + /// + /// byte 80:83 M - Version (VER) + /// + public uint VER; + + /// + /// byte 84:87 M - RTD3 Resume Latency (RTD3R) + /// + public uint RTD3R; + + /// + /// byte 88:91 M - RTD3 Entry Latency (RTD3E) + /// + public uint RTD3E; + + /// + /// byte 92:95 M - Optional Asynchronous Events Supported (OAES) + /// + public uint OAES; + + /// + /// byte 96:239. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 144)] + public byte[] Reserved0; + + /// + /// byte 240:255. Refer to the NVMe Management Interface Specification for definition. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] ReservedForManagement; + + /// + /// byte 256:257 M - Optional Admin Command Support (OACS) + /// + public ushort OACS; + + /// + /// byte 258 M - Abort Command Limit (ACL) + /// + public byte ACL; + + /// + /// byte 259 M - Asynchronous Event Request Limit (AERL) + /// + public byte AERL; + + /// + /// byte 260 M - Firmware Updates (FRMW) + /// + public byte FRMW; + + /// + /// byte 261 M - Log Page Attributes (LPA) + /// + public byte LPA; + + /// + /// byte 262 M - Error Log Page Entries (ELPE) + /// + public byte ELPE; + + /// + /// byte 263 M - Number of Power States Support (NPSS) + /// + public byte NPSS; + + /// + /// byte 264 M - Admin Vendor Specific Command Configuration (AVSCC) + /// + public byte AVSCC; + + /// + /// byte 265 O - Autonomous Power State Transition Attributes (APSTA) + /// + public byte APSTA; + + /// + /// byte 266:267 M - Warning Composite Temperature Threshold (WCTEMP) + /// + public ushort WCTEMP; + + /// + /// byte 268:269 M - Critical Composite Temperature Threshold (CCTEMP) + /// + public ushort CCTEMP; + + /// + /// byte 270:271 O - Maximum Time for Firmware Activation (MTFA) + /// + public ushort MTFA; + + /// + /// byte 272:275 O - Host Memory Buffer Preferred Size (HMPRE) + /// + public uint HMPRE; + + /// + /// byte 276:279 O - Host Memory Buffer Minimum Size (HMMIN) + /// + public uint HMMIN; + + /// + /// byte 280:295 O - Total NVM Capacity (TNVMCAP) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] TNVMCAP; + + /// + /// byte 296:311 O - Unallocated NVM Capacity (UNVMCAP) + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] UNVMCAP; + + /// + /// byte 312:315 O - Replay Protected Memory Block Support (RPMBS) + /// + public uint RPMBS; + + /// + /// byte 316:511 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 196)] + public byte[] Reserved1; + + /// + /// byte 512 M - Submission Queue Entry Size (SQES) + /// + public byte SQES; + + /// + /// byte 513 M - Completion Queue Entry Size (CQES) + /// + public byte CQES; + + /// + /// byte 514:515 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] Reserved2; + + /// + /// byte 516:519 M - Number of Namespaces (NN) + /// + public uint NN; + + /// + /// byte 520:521 M - Optional NVM Command Support (ONCS) + /// + public ushort ONCS; + + /// + /// byte 522:523 M - Fused Operation Support (FUSES) + /// + public ushort FUSES; + + /// + /// byte 524 M - Format NVM Attributes (FNA) + /// + public byte FNA; + + /// + /// byte 525 M - Volatile Write Cache (VWC) + /// + public byte VWC; + + /// + /// byte 526:527 M - Atomic Write Unit Normal (AWUN) + /// + public ushort AWUN; + + /// + /// byte 528:529 M - Atomic Write Unit Power Fail (AWUPF) + /// + public ushort AWUPF; + + /// + /// byte 530 M - NVM Vendor Specific Command Configuration (NVSCC) + /// + public byte NVSCC; + + /// + /// byte 531 + /// + public byte Reserved3; + + /// + /// byte 532:533 O - Atomic Compare and Write Unit (ACWU) + /// + public ushort ACWU; + + /// + /// byte 534:535 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] Reserved4; + + /// + /// byte 536:539 O - SGL Support (SGLS) + /// + public uint SGLS; + + /// + /// byte 540:703 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 164)] + public byte[] Reserved5; + + /// + /// byte 704:2047 + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1344)] + public byte[] Reserved6; + + /// + /// byte 2048:3071 Power State Descriptors + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public NVME_POWER_STATE_DESC[] PDS; + + /// + /// byte 3072:4095 Vendor Specific + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] + public byte[] VS; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NVME_HEALTH_INFO_LOG + { + /// + /// This field indicates critical warnings for the state of the controller. + /// Each bit corresponds to a critical warning type; multiple bits may be set. + /// + public byte CriticalWarning; + + /// + /// Composite Temperature: Contains the temperature of the overall device (controller and NVM included) in units of Kelvin. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public byte[] CompositeTemp; + + /// + /// Available Spare: Contains a normalized percentage (0 to 100%) of the remaining spare capacity available + /// + public byte AvailableSpare; + + /// + /// Available Spare Threshold: When the Available Spare falls below the threshold indicated in this field, + /// an asynchronous event completion may occur. The value is indicated as a normalized percentage (0 to 100%). + /// + public byte AvailableSpareThreshold; + + /// + /// Percentage Used: Contains a vendor specific estimate of the percentage of NVM subsystem life used based on + /// the actual usage and the manufacturer’s prediction of NVM life. A value of 100 indicates that the estimated endurance of + /// the NVM in the NVM subsystem has been consumed, but may not indicate an NVM subsystem failure. The value is allowed to exceed 100. + /// + public byte PercentageUsed; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)] + public byte[] Reserved1; + + /// + /// Data Units Read: Contains the number of 512 byte data units the host has read from the controller; + /// this value does not include metadata. This value is reported in thousands + /// (i.e., a value of 1 corresponds to 1000 units of 512 bytes read) and is rounded up. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] DataUnitRead; + + /// + /// Data Units Written: Contains the number of 512 byte data units the host has written to the controller; + /// this value does not include metadata. This value is reported in thousands + /// (i.e., a value of 1 corresponds to 1000 units of 512 bytes written) and is rounded up. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] DataUnitWritten; + + /// + /// Host Read Commands: Contains the number of read commands completed by the controller. + /// For the NVM command set, this is the number of Compare and Read commands. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] HostReadCommands; + + /// + /// Host Write Commands: Contains the number of write commands completed by the controller. + /// For the NVM command set, this is the number of Write commands. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] HostWriteCommands; + + /// + /// Controller Busy Time: Contains the amount of time the controller is busy with I/O commands. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] ControllerBusyTime; + + /// + /// Power Cycles: Contains the number of power cycles. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] PowerCycles; + + /// + /// Power On Hours: Contains the number of power-on hours. + /// This does not include time that the controller was powered and in a low power state condition. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] PowerOnHours; + + /// + /// Unsafe Shutdowns: Contains the number of unsafe shutdowns. + /// This count is incremented when a shutdown notification is not received prior to loss of power. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] UnsafeShutdowns; + + /// + /// Media Errors: Contains the number of occurrences where the controller detected an unrecoverable data integrity error. + /// Errors such as uncorrectable ECC, CRC checksum failure, or LBA tag mismatch are included in this field. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] MediaAndDataIntegrityErrors; + + /// + /// Number of Error Information Log Entries: Contains the number of Error Information log entries over the life of the controller + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public byte[] NumberErrorInformationLogEntries; + + /// + /// Warning Composite Temperature Time: Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater than or equal to the Warning Composite + /// Temperature Threshold. + /// + public uint WarningCompositeTemperatureTime; + + /// + /// Critical Composite Temperature Time: Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater than the Critical Composite Temperature + /// Threshold. + /// + public uint CriticalCompositeTemperatureTime; + + /// + /// Contains the current temperature reported by temperature sensor 1-8. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public ushort[] TemperatureSensor; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 296)] + internal byte[] Reserved2; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct STORAGE_PROTOCOL_SPECIFIC_DATA + { + public STORAGE_PROTOCOL_TYPE ProtocolType; + public uint DataType; + public uint ProtocolDataRequestValue; + public uint ProtocolDataRequestSubValue; + public uint ProtocolDataOffset; + public uint ProtocolDataLength; + public uint FixedProtocolReturnData; + public uint ProtocolDataRequestSubValue2; + public uint ProtocolDataRequestSubValue3; + public uint Reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct STORAGE_QUERY_BUFFER + { + public STORAGE_PROPERTY_ID PropertyId; + public STORAGE_QUERY_TYPE QueryType; + public STORAGE_PROTOCOL_SPECIFIC_DATA ProtocolSpecific; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4096)] + internal byte[] Buffer; + } + + [Flags] + internal enum MEM : uint + { + MEM_COMMIT = 0x1000, + MEM_RESERVE = 0x2000, + MEM_DECOMMIT = 0x4000, + MEM_RELEASE = 0x8000, + MEM_RESET = 0x80000, + MEM_LARGE_PAGES = 0x20000000, + MEM_PHYSICAL = 0x400000, + MEM_TOP_DOWN = 0x100000, + MEM_WRITE_WATCH = 0x200000 + } + + [Flags] + internal enum PAGE : uint + { + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80, + PAGE_NOACCESS = 0x01, + PAGE_READONLY = 0x02, + PAGE_READWRITE = 0x04, + PAGE_WRITECOPY = 0x08, + PAGE_GUARD = 0x100, + PAGE_NOCACHE = 0x200, + PAGE_WRITECOMBINE = 0x400 + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct IOControlCode + { + /// + /// Gets the resulting IO control code. + /// + public uint Code { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// Type of the device. + /// The function. + /// The access. + public IOControlCode(uint deviceType, uint function, Access access) : this(deviceType, function, Method.Buffered, access) + { } + + /// + /// Initializes a new instance of the struct. + /// + /// Type of the device. + /// The function. + /// The method. + /// The access. + public IOControlCode(uint deviceType, uint function, Method method, Access access) + { + Code = (deviceType << 16) | ((uint)access << 14) | (function << 2) | (uint)method; + } + + public enum Method : uint + { + Buffered = 0, + InDirect = 1, + OutDirect = 2, + Neither = 3 + } + + public enum Access : uint + { + Any = 0, + Read = 1, + Write = 2 + } + } + + internal enum Provider + { + ACPI = (byte)'A' << 24 | (byte)'C' << 16 | (byte)'P' << 8 | (byte)'I', + FIRM = (byte)'F' << 24 | (byte)'I' << 16 | (byte)'R' << 8 | (byte)'M', + RSMB = (byte)'R' << 24 | (byte)'S' << 16 | (byte)'M' << 8 | (byte)'B' + } } diff --git a/ColorControl/Services/NVIDIA/NvGpuInfo.cs b/ColorControl/Services/NVIDIA/NvGpuInfo.cs index 692dd51..6bd94c6 100644 --- a/ColorControl/Services/NVIDIA/NvGpuInfo.cs +++ b/ColorControl/Services/NVIDIA/NvGpuInfo.cs @@ -99,6 +99,28 @@ namespace ColorControl.Services.NVIDIA { Refresh(); + var coreDelta = GetDeltaClockMinMax(PublicClockDomain.Graphics); + var memoryDelta = GetDeltaClockMinMax(PublicClockDomain.Memory); + + var curveEntries = CurveV3.GPUCurveEntries ?? []; + + var graphicsDelta = GetDeltaClockMinMax(PublicClockDomain.Graphics); + + var minCurveFreq = curveEntries.Max(e => e.DefaultFrequencyInkHz) + graphicsDelta.Item1; + var freqValues = curveEntries + .Where(e => e.DefaultFrequencyInkHz == 0 || e.DefaultFrequencyInkHz >= minCurveFreq) + .Select(e => e.DefaultFrequencyInkHz / 1000).Distinct() + .OrderBy(x => x) + .ToList(); + var voltValues = curveEntries + .Where(e => (e.DefaultVoltageInMicroV == 0 || e.DefaultFrequencyInkHz >= minCurveFreq) && e.DefaultVoltageInMicroV <= 1100000) + .OrderBy(e => e.DefaultVoltageInMicroV) + .Select(e => e.DefaultVoltageInMicroV / 1000) + .Distinct() + .ToList(); + + var powerLimits = GetPowerLimits(); + return new NvGpuInfoDto { PCIIdentifier = PCIIdentifier, @@ -117,7 +139,16 @@ namespace ColorControl.Services.NVIDIA MaxPowerInMilliWatts = MaxPowerInMilliWatts, MinPowerInMilliWatts = MinPowerInMilliWatts, StockGraphicsClock = StockGraphicsClock, - StockMemoryClock = StockMemoryClock + StockMemoryClock = StockMemoryClock, + MinCoreDeltaInMHz = coreDelta.Item1 / 1000, + MaxCoreDeltaInMHz = coreDelta.Item2 / 1000, + MinMemoryDeltaInMHz = memoryDelta.Item1 / 1000, + MaxMemoryDeltaInMHz = memoryDelta.Item2 / 1000, + OverclockSettings = GetOverclockSettings(), + CurveFrequenciesInMHz = freqValues, + CurveVoltagesInMv = voltValues, + MinimumPowerInPCM = powerLimits.Item1, + MaximumPowerInPCM = powerLimits.Item2, }; } @@ -329,14 +360,14 @@ namespace ColorControl.Services.NVIDIA var sb = new StringBuilder(); - sb.Append($"GPU: {GpuFrequencyInKhz.ToUnitString()} @ {VoltageInMv} mV, {GpuTemperature}°"); + sb.Append($"GPU: {GpuFrequencyInKhz.ToKiloUnitString()} @ {VoltageInMv} mV, {GpuTemperature}°"); if (HotSpotTemperature.HasValue) { sb.Append($"/{HotSpotTemperature}°"); } - sb.Append($", Mem: {MemoryFrequencyInKhz.ToUnitString()}"); + sb.Append($", Mem: {MemoryFrequencyInKhz.ToKiloUnitString()}"); if (MemoryTemperature.HasValue) { @@ -752,7 +783,7 @@ namespace ColorControl.Services.NVIDIA //var field = new FieldDefinition //{ - // Label = $"Core offset in MHz ({graphicsDelta.Item1.ToUnitString()} to {graphicsDelta.Item2.ToUnitString()})", + // Label = $"Core offset in MHz ({graphicsDelta.Item1.ToKiloUnitString()} to {graphicsDelta.Item2.ToKiloUnitString()})", // FieldType = FieldType.Numeric, // MinValue = graphicsDelta.Item1 / 1000, // MaxValue = graphicsDelta.Item2 / 1000, @@ -761,7 +792,7 @@ namespace ColorControl.Services.NVIDIA var field = new FieldDefinition { - Label = $"Core offset in MHz ({graphicsDelta.Item1.ToUnitString()} to {graphicsDelta.Item2.ToUnitString()})", + Label = $"Core offset in MHz ({graphicsDelta.Item1.ToKiloUnitString()} to {graphicsDelta.Item2.ToKiloUnitString()})", FieldType = FieldType.TrackBar, MinValue = graphicsDelta.Item1 / 1000, MaxValue = graphicsDelta.Item2 / 1000, @@ -778,7 +809,7 @@ namespace ColorControl.Services.NVIDIA //var field = new FieldDefinition //{ - // Label = $"Memory offset in MHz ({memoryDelta.Item1.ToUnitString()} to {memoryDelta.Item2.ToUnitString()})", + // Label = $"Memory offset in MHz ({memoryDelta.Item1.ToKiloUnitString()} to {memoryDelta.Item2.ToKiloUnitString()})", // FieldType = FieldType.Numeric, // MinValue = memoryDelta.Item1 / 1000, // MaxValue = memoryDelta.Item2 / 1000, @@ -787,7 +818,7 @@ namespace ColorControl.Services.NVIDIA var field = new FieldDefinition { - Label = $"Memory offset in MHz ({memoryDelta.Item1.ToUnitString()} to {memoryDelta.Item2.ToUnitString()})", + Label = $"Memory offset in MHz ({memoryDelta.Item1.ToKiloUnitString()} to {memoryDelta.Item2.ToKiloUnitString()})", FieldType = FieldType.TrackBar, MinValue = memoryDelta.Item1 / 1000, MaxValue = memoryDelta.Item2 / 1000, diff --git a/ColorControl/Services/NVIDIA/NvPanel.cs b/ColorControl/Services/NVIDIA/NvPanel.cs index 286c14c..136145d 100644 --- a/ColorControl/Services/NVIDIA/NvPanel.cs +++ b/ColorControl/Services/NVIDIA/NvPanel.cs @@ -1,6 +1,7 @@ using ColorControl.Services.Common; using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; +using ColorControl.Shared.Contracts.DisplayInfo; using ColorControl.Shared.Contracts.NVIDIA; using ColorControl.Shared.EventDispatcher; using ColorControl.Shared.Forms; @@ -26,2062 +27,2040 @@ using System.Windows.Forms; namespace ColorControl.Services.NVIDIA { - partial class NvPanel : UserControl, IModulePanel - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private Config _config; - private NvService _nvService; - private readonly NotifyIconManager _notifyIconManager; - private readonly GlobalContext _globalContext; - private RpcClientService _rpcService; - private readonly WinApiAdminService _winApiAdminService; - private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; - - public NvPanel(NvService nvService, NotifyIconManager notifyIconManager, GlobalContext globalContext, RpcClientService rpcService, WinApiAdminService winApiAdminService, KeyboardShortcutDispatcher keyboardShortcutDispatcher) - { - _nvService = nvService; - _notifyIconManager = notifyIconManager; - _globalContext = globalContext; - _config = globalContext.Config; - _rpcService = rpcService; - _winApiAdminService = winApiAdminService; - _keyboardShortcutDispatcher = keyboardShortcutDispatcher; - _rpcService.Name = "NvService"; - - InitializeComponent(); - - FillNvPresets(); - - FormUtils.InitSortState(lvNvPresets, _config.NvPresetsSortState); - - _nvService.AfterApplyPreset += NvServiceAfterApplyPreset; - } - - private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e) - { - FormUtils.ListViewSort(sender, e); - } - - private void lvNvPresets_ItemCheck(object sender, ItemCheckEventArgs e) - { - var listView = sender as ListView; - - if (!listView.Focused) - { - return; - } - - - var point = listView.PointToClient(Cursor.Position); - - if (point.X >= 20) - { - return; - } - - var listItem = listView.Items[e.Index]; - - if (listItem.Tag == null) - { - MessageForms.WarningOk("Quick Access cannot be enabled for this item."); - e.NewValue = e.CurrentValue; - } - - if (e.NewValue == CheckState.Checked && string.IsNullOrEmpty(listItem.Text)) - { - MessageForms.WarningOk("A name must be entered before enabling Quick Access."); - e.NewValue = e.CurrentValue; - } - } - - private void NvServiceAfterApplyPreset(object sender, NvPreset preset) - { - UpdateDisplayInfoItems(); - } - - private void FillNvPresets() - { - FormUtils.InitListView(lvNvPresets, NvPreset.GetColumnNames()); - - lvNvPresets.BeginUpdate(); - try - { - UpdateDisplayInfoItems(); - - foreach (var preset in _nvService.GetPresets()) - { - AddOrUpdateItem(preset); - } - } - finally - { - lvNvPresets.EndUpdate(); - } - } - - private void UpdateDisplayInfoItems() - { - var displays = _nvService.GetDisplayInfos(); - if (displays == null || Program.IsRestarting) - { - return; - } - - var text = Program.TS_TASKNAME; - foreach (var displayInfo in displays) - { - var display = displayInfo.Display; - - ListViewItem item = null; - for (var i = 0; i < lvNvPresets.Items.Count; i++) - { - item = lvNvPresets.Items[i]; - - if (item.Tag == null && item.SubItems[1].Text == displayInfo.Name) - { - break; - } - item = null; - } - - if (item == null) - { - item = lvNvPresets.Items.Add(displayInfo.Name); - item.Font = new Font(item.Font, item.Font.Style | FontStyle.Bold); - } - item.BackColor = _globalContext.Config.UseDarkMode ? DarkModeUtils.ListViewDarkModeBackColor : Color.LightGray; - - var values = displayInfo.Values; - - item.Text = values[0]; - for (var i = 1; i < values.Count; i++) - { - if (item.SubItems.Count - 1 >= i) - { - item.SubItems[i].Text = values[i]; - } - else - { - item.SubItems.Add(values[i]); - } - } - - text += "\n" + displayInfo.InfoLine; - } - - UpdateNvGpuInfo(); - - _notifyIconManager.SetText(text); - } - - private void UpdateNvGpuInfo(List gpus = null) - { - gpus ??= _nvService.GetGPUs(); - - cbxNvGPU.Items.Clear(); - cbxNvGPU.Items.AddRange(gpus.ToArray()); - if (cbxNvGPU.Items.Count > 0 && cbxNvGPU.SelectedIndex == -1) - { - cbxNvGPU.SelectedIndex = 0; - } - - btnNvSetClocks.Visible = _nvService.Config.ShowOverclocking; - } - - private void AddOrUpdateItem(NvPreset preset = null) - { - ServiceFormUtils.AddOrUpdateListItem(lvNvPresets, _nvService.GetPresets(), _config, preset); - } - - private async void btnApply_Click(object sender, EventArgs e) - { - await ApplySelectedNvPreset(); - } - - private void lvNvPresets_ItemChecked(object sender, ItemCheckedEventArgs e) - { - ServiceFormUtils.ListViewItemChecked(lvNvPresets, e); - } - - private void lvNvPresets_SelectedIndexChanged(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - var enabled = preset != null; - - FormUtils.EnableControls(this, enabled, new List { lvNvPresets, btnAddModesNv, btnChange, edtNvGpuInfo, btnNvSetClocks, btnNvSettings, edtNvOverclock, btnNvSetClocks }); - - btnClone.Enabled = preset != null || GetSelectedNvPreset(true) != null; - - if (enabled) - { - edtNvPresetName.Text = preset.name; - edtShortcut.Text = preset.shortcut; - chkNvShowInQuickAccess.Checked = preset.ShowInQuickAccess; - } - else - { - edtNvPresetName.Text = string.Empty; - edtShortcut.Text = string.Empty; - chkNvShowInQuickAccess.Checked = false; - } - } - - private void btnNvPresetDelete_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - if (preset == null) - { - return; - } - - if (!string.IsNullOrEmpty(preset.shortcut)) - { - WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); - } - - _nvService.GetPresets().Remove(preset); - - var item = lvNvPresets.SelectedItems[0]; - lvNvPresets.Items.Remove(item); - } - - private void btnClone_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true, true); - - if (preset == null) - { - return; - } - - var newPreset = preset.Clone(); - newPreset.IsDisplayPreset = false; - AddOrUpdateItem(newPreset); - } - - private void miNvPresetColorSettings_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - preset.applyColorData = !preset.applyColorData; - - AddOrUpdateItem(); - } - - private void miNvPresetApplyDithering_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - preset.applyDithering = !preset.applyDithering; - - AddOrUpdateItem(); - } - - private void miNvPresetDitheringEnabled_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); - - var checkState = miNvPresetDitheringEnabled.CheckState; - var newCheckState = checkState == CheckState.Checked ? CheckState.Unchecked : checkState == CheckState.Unchecked ? CheckState.Indeterminate : CheckState.Checked; - - if (preset.IsDisplayPreset) - { - miNvPresetDitheringEnabled.CheckState = newCheckState; - - _nvService.SetDithering(newCheckState == CheckState.Indeterminate ? NvDitherState.Auto : newCheckState == CheckState.Checked ? NvDitherState.Enabled : NvDitherState.Disabled); - - UpdateDisplayInfoItems(); - return; - } - - preset.ditheringEnabled = checkState == CheckState.Indeterminate ? null : checkState == CheckState.Checked; - - AddOrUpdateItem(); - } - - private void btnChange_Click(object sender, EventArgs e) - { - mnuNvPresets.ShowCustom(btnChange); - } - - private void includedToolStripMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - preset.DisplayConfig.ApplyRefreshRate = !preset.DisplayConfig.ApplyRefreshRate; - - AddOrUpdateItem(); - } - - private Font _normalFont; - private Font _boldFont; - private Dictionary _menuItemFonts; - - private void mnuNvPresets_Opening(object sender, CancelEventArgs e) - { - var preset = GetSelectedNvPreset(true); - var isCurrentDisplay = preset?.IsDisplayPreset == true; - - var presetItemsVisible = preset != null && !isCurrentDisplay; - - miNvApply.Visible = presetItemsVisible; - miNvPresetApplyOnStartup.Visible = presetItemsVisible; - tssNvPresetMenu.Visible = presetItemsVisible; - mnuNvDisplay.Visible = presetItemsVisible; - miNvPresetColorSettings.Enabled = presetItemsVisible; - mnuNvPresetsColorSettings.Enabled = preset != null; - miNvPresetColorEnhancementsIncluded.Enabled = presetItemsVisible; - mnuNvPresetsColorEnhancements.Enabled = preset != null; - mnuRefreshRate.Enabled = preset != null; - mnuNvResolution.Enabled = preset != null; - mnuNvActiveResolution.Enabled = preset != null; - miNvPresetDithering.Enabled = preset != null; - miNvHDR.Enabled = preset != null; - miNvCopyId.Visible = presetItemsVisible; - miNvDriverSettingsIncluded.Visible = presetItemsVisible; - mnuNvDriverSettings.Enabled = preset != null; - mnuNvOverclocking.Visible = preset != null && _nvService.Config.ShowOverclocking; - mnuNvOtherSettings.Enabled = preset != null; - miNvOtherIncluded.Visible = presetItemsVisible; - mnuNvHdmiSettings.Visible = !isCurrentDisplay || preset?.Display?.DisplayDevice.ConnectionType == NvAPIWrapper.Native.GPU.MonitorConnectionType.HDMI; - mnuNvHdmiSettings.Enabled = preset != null; - miNvHdmiIncluded.Visible = presetItemsVisible; - miHDROuputMode.Visible = _nvService.OutputModeAvailable; - mnuNvTriggers.Visible = false; // TODO: implement later - - if (preset == null) - { - return; - } - - _normalFont ??= new Font(mnuNvDriverSettings.Font, FontStyle.Regular); - _boldFont ??= new Font(_normalFont, FontStyle.Bold); - _menuItemFonts ??= new Dictionary { { false, _normalFont }, { true, _boldFont } }; - - miNvPresetApplyOnStartup.Checked = preset.id > 0 && _config.NvPresetId_ApplyOnStartup == preset.id; - - miNvPresetApplyDithering.Visible = !isCurrentDisplay; - miNvPresetColorSettings.Visible = !isCurrentDisplay; - miNvPresetColorEnhancementsIncluded.Visible = !isCurrentDisplay; - miNvResolutionIncluded.Visible = !isCurrentDisplay; - miRefreshRateIncluded.Visible = true; // Workaround to make arrow visible - miHDRIncluded.Visible = !isCurrentDisplay; - miHDROutputModeUnchanged.Visible = !isCurrentDisplay; - miHDROutputModeHDR10.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10; - miHDROutputModeHDR10Plus.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; - - if (mnuNvDisplay.DropDownItems.Count == 1) - { - var displays = _nvService.GetDisplayInfos(); - foreach (var display in displays) - { - var item = mnuNvDisplay.DropDownItems.AddCustom(display.Name); - item.Tag = display; - item.Click += displayMenuItem_Click; - } - } - - FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Bit depth", typeof(ColorDataDepth), preset.colorData, "ColorDepth", nvPresetColorDataMenuItem_Click); - FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Format", typeof(ColorDataFormat), preset.colorData, "ColorFormat", nvPresetColorDataMenuItem_Click); - FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Dynamic range", typeof(ColorDataDynamicRange), preset.colorData, "DynamicRange", nvPresetColorDataMenuItem_Click); - FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Color space", typeof(ColorDataColorimetry), preset.colorData, "Colorimetry", nvPresetColorDataMenuItem_Click); - - FormUtils.BuildDropDownMenu(mnuNvResolution, "Scaling/Aspect Ratio", typeof(CCD.DisplayConfigScaling), preset.DisplayConfig, "Scaling", UpdateDisplayConfigScaling, unchanged: false, - skipValues: [CCD.DisplayConfigScaling.Zero, CCD.DisplayConfigScaling.Custom, CCD.DisplayConfigScaling.ForceUint32]); - FormUtils.BuildDropDownMenu(mnuNvResolution, "Rotation", typeof(CCD.DisplayConfigRotation), preset.DisplayConfig, "Rotation", UpdateDisplayConfigRotation, unchanged: false, - skipValues: [CCD.DisplayConfigRotation.Zero, CCD.DisplayConfigRotation.ForceUint32]); - - miNvPrimaryDisplay.Checked = preset.primaryDisplay; - foreach (var item in mnuNvDisplay.DropDownItems) - { - if (item is ToolStripMenuItem menuItem && menuItem.Tag != null) - { - menuItem.Checked = ((NvDisplayInfo)menuItem.Tag).DisplayId.Equals(preset.DisplayId); - } - } - - miNvPresetColorSettings.Checked = preset.applyColorData; - miNvPresetApplyDithering.Checked = preset.applyDithering; - miNvPresetDitheringEnabled.CheckState = preset.ditheringEnabled == null ? CheckState.Indeterminate : preset.ditheringEnabled == true ? CheckState.Checked : CheckState.Unchecked; - - foreach (var item in mnuNvDitheringBitDepth.DropDownItems.OfType()) - { - if (item.Tag != null) - { - item.Checked = uint.Parse(item.Tag.ToString()) == preset.ditheringBits; - } - } - foreach (var item in mnuNvDitheringMode.DropDownItems.OfType()) - { - if (item.Tag != null) - { - item.Checked = uint.Parse(item.Tag.ToString()) == preset.ditheringMode; - } - } - - miHDRIncluded.Checked = preset.applyHDR; - miHDREnabled.Checked = preset.HDREnabled; - miToggleHDR.Checked = preset.toggleHDR; - miNvOtherIncluded.Checked = preset.applyOther; - miNvHdmiIncluded.Checked = preset.applyHdmiSettings; - miNvDriverSettingsIncluded.Checked = preset.applyDriverSettings; - miNvPresetColorEnhancementsIncluded.Checked = preset.ApplyColorEnhancements; - - mnuNvPresetsColorSettings.Font = _menuItemFonts[preset.applyColorData]; - mnuNvPresetsColorEnhancements.Font = _menuItemFonts[preset.ApplyColorEnhancements]; - mnuRefreshRate.Font = _menuItemFonts[preset.DisplayConfig.ApplyRefreshRate]; - mnuNvResolution.Font = _menuItemFonts[preset.DisplayConfig.ApplyResolution]; - miNvPresetDithering.Font = _menuItemFonts[preset.applyDithering]; - miNvHDR.Font = _menuItemFonts[preset.applyHDR]; - mnuNvOverclocking.Font = _menuItemFonts[preset.applyOverclocking]; - mnuNvOtherSettings.Font = _menuItemFonts[preset.applyOther]; - mnuNvHdmiSettings.Font = _menuItemFonts[preset.applyHdmiSettings]; - - FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Digital Vibrance", - $"{preset.ColorEnhancementSettings.DigitalVibranceLevel}% (click to change)", nvPresetDvcMenuItem_Click); - FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Hue", - $"{preset.ColorEnhancementSettings.HueAngle}° (click to change)", nvPresetHueMenuItem_Click); - - FormUtils.BuildMenuItemAndSub(miNvHDR, "SDR brightness", - (preset.SDRBrightness.HasValue ? $"{preset.SDRBrightness.Value}%" : "Unset") + " (click to change)", nvPresetBrightnessMenuItem_Click); - - FormUtils.BuildDropDownMenu(mnuNvOtherSettings, "Scaling", typeof(Scaling), preset, "scaling", nvPresetScalingMenuItem_Click, unchanged: true, skipValues: new object[] { Scaling.Customized }) - .DropDownItems[0].Visible = !isCurrentDisplay; - - FormUtils.BuildMenuItemAndSub(mnuNvOtherSettings, "Color Profile", - (!preset.ColorProfileSettings.ProfileName.IsNullOrWhiteSpace() ? $"{preset.ColorProfileSettings.ProfileName}" : "Unset") + " (click to change)", nvPresetColorProfileMenuItem_Click); - - FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Content type", typeof(InfoFrameVideoContentType), preset.HdmiInfoFrameSettings, "ContentType", nvPresetHdmiContentTypeMenuItem_Click, unchanged: true) - .DropDownItems[0].Visible = !isCurrentDisplay; - FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Colorimetry", typeof(InfoFrameVideoColorimetry), preset.HdmiInfoFrameSettings, "Colorimetry", nvPresetHdmiColorimetryMenuItem_Click, unchanged: true) - .DropDownItems[0].Visible = !isCurrentDisplay; - FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Extended colorimetry", typeof(InfoFrameVideoExtendedColorimetry), preset.HdmiInfoFrameSettings, "ExtendedColorimetry", nvPresetHdmiExtendedColorimetryMenuItem_Click, unchanged: true) - .DropDownItems[0].Visible = !isCurrentDisplay; - - BuildNvOverclockingMenu(preset); - - UpdateNvPresetMenuFont(); - - BuildNvidiaDriverSettingsMenu(mnuNvDriverSettings, isCurrentDisplay ? null : preset.driverSettings); - - mnuNvDriverSettings.Font = _menuItemFonts[preset.applyDriverSettings]; - } - - private void BuildNvOverclockingMenu(NvPreset preset) - { - miNvOverclockingIncluded.Checked = preset.applyOverclocking; - miNvOverclockingIncluded.Visible = !preset.IsDisplayPreset; - - var gpus = _nvService.GetGPUs(); - - foreach (var gpu in gpus) - { - var gpuPciId = gpu.BusInformation.PCIIdentifiers.ToString(); - - var name = $"{mnuNvOverclocking.Name}_{gpu.GPUId}"; - - var gpuItem = FormUtils.BuildMenuItem(mnuNvOverclocking.DropDownItems, name, gpu.FullName); - - var subName = $"{name}_change"; - - var ocSettings = preset.ocSettings.FirstOrDefault(s => s.PCIIdentifier == gpuPciId) ?? new NvGpuOcSettings { PCIIdentifier = gpuPciId }; - - var subText = $"{ocSettings} (click to change)"; - - var subItem = FormUtils.BuildMenuItem(gpuItem.DropDownItems, subName, subText, ocSettings, nvChangeOverclockingMenuItem_Click); - } - } - - private void nvChangeOverclockingMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - if (preset == null) - { - btnNvSetClocks_Click(btnNvSetClocks, new EventArgs()); - - return; - } - - var menuItem = (ToolStripItem)sender; - - var currentSettings = (NvGpuOcSettings)menuItem.Tag; - - var gpuInfo = NvGpuInfo.GetGpuInfo(currentSettings.PCIIdentifier); - - var newSettings = AskNvOcSettings(gpuInfo, currentSettings); - - if (newSettings == null) - { - return; - } - - preset.UpdateOverclockingSetting(newSettings); - - AddOrUpdateItem(); - } - - private void UpdateNvPresetMenuFont(bool updateTopItems = false) - { - foreach (var menu in mnuNvPresets.Items.OfType().Where(i => i != mnuNvDriverSettings)) - { - if (updateTopItems) - { - menu.Font = _normalFont; - } + partial class NvPanel : UserControl, IModulePanel + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private Config _config; + private NvService _nvService; + private readonly NotifyIconManager _notifyIconManager; + private readonly GlobalContext _globalContext; + private RpcClientService _rpcService; + private readonly WinApiAdminService _winApiAdminService; + private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; + private readonly ColorProfileService _colorProfileService; + + public NvPanel(NvService nvService, NotifyIconManager notifyIconManager, GlobalContext globalContext, RpcClientService rpcService, WinApiAdminService winApiAdminService, KeyboardShortcutDispatcher keyboardShortcutDispatcher, ColorProfileService colorProfileService) + { + _nvService = nvService; + _notifyIconManager = notifyIconManager; + _globalContext = globalContext; + _config = globalContext.Config; + _rpcService = rpcService; + _winApiAdminService = winApiAdminService; + _keyboardShortcutDispatcher = keyboardShortcutDispatcher; + _colorProfileService = colorProfileService; + _rpcService.Name = "NvService"; + + InitializeComponent(); + + FillNvPresets(); + + FormUtils.InitSortState(lvNvPresets, _config.NvPresetsSortState); + + _nvService.AfterApplyPreset += NvServiceAfterApplyPreset; + } + + private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e) + { + FormUtils.ListViewSort(sender, e); + } + + private void lvNvPresets_ItemCheck(object sender, ItemCheckEventArgs e) + { + var listView = sender as ListView; + + if (!listView.Focused) + { + return; + } + + + var point = listView.PointToClient(Cursor.Position); + + if (point.X >= 20) + { + return; + } + + var listItem = listView.Items[e.Index]; + + if (listItem.Tag == null) + { + MessageForms.WarningOk("Quick Access cannot be enabled for this item."); + e.NewValue = e.CurrentValue; + } + + if (e.NewValue == CheckState.Checked && string.IsNullOrEmpty(listItem.Text)) + { + MessageForms.WarningOk("A name must be entered before enabling Quick Access."); + e.NewValue = e.CurrentValue; + } + } + + private void NvServiceAfterApplyPreset(object sender, NvPreset preset) + { + UpdateDisplayInfoItems(); + } + + private void FillNvPresets() + { + FormUtils.InitListView(lvNvPresets, NvPreset.GetColumnNames()); + + lvNvPresets.BeginUpdate(); + try + { + UpdateDisplayInfoItems(); + + foreach (var preset in _nvService.GetPresets()) + { + AddOrUpdateItem(preset); + } + } + finally + { + lvNvPresets.EndUpdate(); + } + } + + private void UpdateDisplayInfoItems() + { + var displays = _nvService.GetDisplayInfos(); + if (displays == null || Program.IsRestarting) + { + return; + } + + var text = Program.TS_TASKNAME; + foreach (var displayInfo in displays) + { + var display = displayInfo.Display; + + ListViewItem item = null; + for (var i = 0; i < lvNvPresets.Items.Count; i++) + { + item = lvNvPresets.Items[i]; + + if (item.Tag == null && item.SubItems[1].Text == displayInfo.Name) + { + break; + } + item = null; + } + + if (item == null) + { + item = lvNvPresets.Items.Add(displayInfo.Name); + item.Font = new Font(item.Font, item.Font.Style | FontStyle.Bold); + } + item.BackColor = _globalContext.Config.UseDarkMode ? DarkModeUtils.ListViewDarkModeBackColor : Color.LightGray; + + var values = displayInfo.Values; + + item.Text = values[0]; + for (var i = 1; i < values.Count; i++) + { + if (item.SubItems.Count - 1 >= i) + { + item.SubItems[i].Text = values[i]; + } + else + { + item.SubItems.Add(values[i]); + } + } + + text += "\n" + displayInfo.InfoLine; + } + + UpdateNvGpuInfo(); + + _notifyIconManager.SetText(text); + } + + private void UpdateNvGpuInfo(List gpus = null) + { + gpus ??= _nvService.GetGPUs(); + + cbxNvGPU.Items.Clear(); + cbxNvGPU.Items.AddRange(gpus.ToArray()); + if (cbxNvGPU.Items.Count > 0 && cbxNvGPU.SelectedIndex == -1) + { + cbxNvGPU.SelectedIndex = 0; + } + + btnNvSetClocks.Visible = _nvService.Config.ShowOverclocking; + } + + private void AddOrUpdateItem(NvPreset preset = null) + { + ServiceFormUtils.AddOrUpdateListItem(lvNvPresets, _nvService.GetPresets(), _config, preset); + } + + private async void btnApply_Click(object sender, EventArgs e) + { + await ApplySelectedNvPreset(); + } + + private void lvNvPresets_ItemChecked(object sender, ItemCheckedEventArgs e) + { + ServiceFormUtils.ListViewItemChecked(lvNvPresets, e); + } + + private void lvNvPresets_SelectedIndexChanged(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + var enabled = preset != null; + + FormUtils.EnableControls(this, enabled, new List { lvNvPresets, btnAddModesNv, btnChange, edtNvGpuInfo, btnNvSetClocks, btnNvSettings, edtNvOverclock, btnNvSetClocks }); + + btnClone.Enabled = preset != null || GetSelectedNvPreset(true) != null; + + if (enabled) + { + edtNvPresetName.Text = preset.name; + edtShortcut.Text = preset.shortcut; + chkNvShowInQuickAccess.Checked = preset.ShowInQuickAccess; + } + else + { + edtNvPresetName.Text = string.Empty; + edtShortcut.Text = string.Empty; + chkNvShowInQuickAccess.Checked = false; + } + } + + private void btnNvPresetDelete_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + if (preset == null) + { + return; + } + + if (!string.IsNullOrEmpty(preset.shortcut)) + { + WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); + } + + _nvService.GetPresets().Remove(preset); + + var item = lvNvPresets.SelectedItems[0]; + lvNvPresets.Items.Remove(item); + } + + private void btnClone_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true, true); + + if (preset == null) + { + return; + } + + var newPreset = preset.Clone(); + newPreset.IsDisplayPreset = false; + AddOrUpdateItem(newPreset); + } + + private void miNvPresetColorSettings_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + preset.applyColorData = !preset.applyColorData; + + AddOrUpdateItem(); + } + + private void miNvPresetApplyDithering_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + preset.applyDithering = !preset.applyDithering; + + AddOrUpdateItem(); + } + + private void miNvPresetDitheringEnabled_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + + var checkState = miNvPresetDitheringEnabled.CheckState; + var newCheckState = checkState == CheckState.Checked ? CheckState.Unchecked : checkState == CheckState.Unchecked ? CheckState.Indeterminate : CheckState.Checked; + + if (preset.IsDisplayPreset) + { + miNvPresetDitheringEnabled.CheckState = newCheckState; + + _nvService.SetDithering(newCheckState == CheckState.Indeterminate ? NvDitherState.Auto : newCheckState == CheckState.Checked ? NvDitherState.Enabled : NvDitherState.Disabled); + + UpdateDisplayInfoItems(); + return; + } + + preset.ditheringEnabled = checkState == CheckState.Indeterminate ? null : checkState == CheckState.Checked; + + AddOrUpdateItem(); + } + + private void btnChange_Click(object sender, EventArgs e) + { + mnuNvPresets.ShowCustom(btnChange); + } + + private void includedToolStripMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + preset.DisplayConfig.ApplyRefreshRate = !preset.DisplayConfig.ApplyRefreshRate; + + AddOrUpdateItem(); + } + + private Font _normalFont; + private Font _boldFont; + private Dictionary _menuItemFonts; + + private void mnuNvPresets_Opening(object sender, CancelEventArgs e) + { + var preset = GetSelectedNvPreset(true); + var isCurrentDisplay = preset?.IsDisplayPreset == true; + + var presetItemsVisible = preset != null && !isCurrentDisplay; + + miNvApply.Visible = presetItemsVisible; + miNvPresetApplyOnStartup.Visible = presetItemsVisible; + tssNvPresetMenu.Visible = presetItemsVisible; + mnuNvDisplay.Visible = presetItemsVisible; + miNvPresetColorSettings.Enabled = presetItemsVisible; + mnuNvPresetsColorSettings.Enabled = preset != null; + miNvPresetColorEnhancementsIncluded.Enabled = presetItemsVisible; + mnuNvPresetsColorEnhancements.Enabled = preset != null; + mnuRefreshRate.Enabled = preset != null; + mnuNvResolution.Enabled = preset != null; + mnuNvActiveResolution.Enabled = preset != null; + miNvPresetDithering.Enabled = preset != null; + miNvHDR.Enabled = preset != null; + miNvCopyId.Visible = presetItemsVisible; + miNvDriverSettingsIncluded.Visible = presetItemsVisible; + mnuNvDriverSettings.Enabled = preset != null; + mnuNvOverclocking.Visible = preset != null && _nvService.Config.ShowOverclocking; + mnuNvOtherSettings.Enabled = preset != null; + miNvOtherIncluded.Visible = presetItemsVisible; + mnuNvHdmiSettings.Visible = !isCurrentDisplay || preset?.Display?.DisplayDevice.ConnectionType == NvAPIWrapper.Native.GPU.MonitorConnectionType.HDMI; + mnuNvHdmiSettings.Enabled = preset != null; + miNvHdmiIncluded.Visible = presetItemsVisible; + miHDROuputMode.Visible = _nvService.OutputModeAvailable; + mnuNvTriggers.Visible = false; // TODO: implement later + + if (preset == null) + { + return; + } + + _normalFont ??= new Font(mnuNvDriverSettings.Font, FontStyle.Regular); + _boldFont ??= new Font(_normalFont, FontStyle.Bold); + _menuItemFonts ??= new Dictionary { { false, _normalFont }, { true, _boldFont } }; + + miNvPresetApplyOnStartup.Checked = preset.id > 0 && _config.NvPresetId_ApplyOnStartup == preset.id; + + miNvPresetApplyDithering.Visible = !isCurrentDisplay; + miNvPresetColorSettings.Visible = !isCurrentDisplay; + miNvPresetColorEnhancementsIncluded.Visible = !isCurrentDisplay; + miNvResolutionIncluded.Visible = !isCurrentDisplay; + miRefreshRateIncluded.Visible = true; // Workaround to make arrow visible + miHDRIncluded.Visible = !isCurrentDisplay; + miHDROutputModeUnchanged.Visible = !isCurrentDisplay; + miHDROutputModeHDR10.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10; + miHDROutputModeHDR10Plus.Checked = preset.HdrSettings.OutputMode == NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; + + if (mnuNvDisplay.DropDownItems.Count == 1) + { + var displays = _nvService.GetDisplayInfos(); + foreach (var display in displays) + { + var item = mnuNvDisplay.DropDownItems.AddCustom(display.Name); + item.Tag = display; + item.Click += displayMenuItem_Click; + } + } + + FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Bit depth", typeof(ColorDataDepth), preset.colorData, "ColorDepth", nvPresetColorDataMenuItem_Click); + FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Format", typeof(ColorDataFormat), preset.colorData, "ColorFormat", nvPresetColorDataMenuItem_Click); + FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Dynamic range", typeof(ColorDataDynamicRange), preset.colorData, "DynamicRange", nvPresetColorDataMenuItem_Click); + FormUtils.BuildDropDownMenu(mnuNvPresetsColorSettings, "Color space", typeof(ColorDataColorimetry), preset.colorData, "Colorimetry", nvPresetColorDataMenuItem_Click); + + FormUtils.BuildDropDownMenu(mnuNvResolution, "Scaling/Aspect Ratio", typeof(CCD.DisplayConfigScaling), preset.DisplayConfig, "Scaling", UpdateDisplayConfigScaling, unchanged: false, + skipValues: [CCD.DisplayConfigScaling.Zero, CCD.DisplayConfigScaling.Custom, CCD.DisplayConfigScaling.ForceUint32]); + FormUtils.BuildDropDownMenu(mnuNvResolution, "Rotation", typeof(CCD.DisplayConfigRotation), preset.DisplayConfig, "Rotation", UpdateDisplayConfigRotation, unchanged: false, + skipValues: [CCD.DisplayConfigRotation.Zero, CCD.DisplayConfigRotation.ForceUint32]); + + miNvPrimaryDisplay.Checked = preset.primaryDisplay; + foreach (var item in mnuNvDisplay.DropDownItems) + { + if (item is ToolStripMenuItem menuItem && menuItem.Tag != null) + { + menuItem.Checked = ((NvDisplayInfo)menuItem.Tag).DisplayId.Equals(preset.DisplayId); + } + } + + miNvPresetColorSettings.Checked = preset.applyColorData; + miNvPresetApplyDithering.Checked = preset.applyDithering; + miNvPresetDitheringEnabled.CheckState = preset.ditheringEnabled == null ? CheckState.Indeterminate : preset.ditheringEnabled == true ? CheckState.Checked : CheckState.Unchecked; + + foreach (var item in mnuNvDitheringBitDepth.DropDownItems.OfType()) + { + if (item.Tag != null) + { + item.Checked = uint.Parse(item.Tag.ToString()) == preset.ditheringBits; + } + } + foreach (var item in mnuNvDitheringMode.DropDownItems.OfType()) + { + if (item.Tag != null) + { + item.Checked = uint.Parse(item.Tag.ToString()) == preset.ditheringMode; + } + } + + miHDRIncluded.Checked = preset.applyHDR; + miHDREnabled.Checked = preset.HDREnabled; + miToggleHDR.Checked = preset.toggleHDR; + miNvOtherIncluded.Checked = preset.applyOther; + miNvHdmiIncluded.Checked = preset.applyHdmiSettings; + miNvDriverSettingsIncluded.Checked = preset.applyDriverSettings; + miNvPresetColorEnhancementsIncluded.Checked = preset.ApplyColorEnhancements; + + mnuNvPresetsColorSettings.Font = _menuItemFonts[preset.applyColorData]; + mnuNvPresetsColorEnhancements.Font = _menuItemFonts[preset.ApplyColorEnhancements]; + mnuRefreshRate.Font = _menuItemFonts[preset.DisplayConfig.ApplyRefreshRate]; + mnuNvResolution.Font = _menuItemFonts[preset.DisplayConfig.ApplyResolution]; + miNvPresetDithering.Font = _menuItemFonts[preset.applyDithering]; + miNvHDR.Font = _menuItemFonts[preset.applyHDR]; + mnuNvOverclocking.Font = _menuItemFonts[preset.applyOverclocking]; + mnuNvOtherSettings.Font = _menuItemFonts[preset.applyOther]; + mnuNvHdmiSettings.Font = _menuItemFonts[preset.applyHdmiSettings]; + + FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Digital Vibrance", + $"{preset.ColorEnhancementSettings.DigitalVibranceLevel}% (click to change)", nvPresetDvcMenuItem_Click); + FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Hue", + $"{preset.ColorEnhancementSettings.HueAngle}° (click to change)", nvPresetHueMenuItem_Click); + + FormUtils.BuildMenuItemAndSub(miNvHDR, "SDR brightness", + (preset.SDRBrightness.HasValue ? $"{preset.SDRBrightness.Value}%" : "Unset") + " (click to change)", nvPresetBrightnessMenuItem_Click); + + FormUtils.BuildDropDownMenu(mnuNvOtherSettings, "Scaling", typeof(Scaling), preset, "scaling", nvPresetScalingMenuItem_Click, unchanged: true, skipValues: new object[] { Scaling.Customized }) + .DropDownItems[0].Visible = !isCurrentDisplay; + + FormUtils.BuildMenuItemAndSub(mnuNvOtherSettings, "Color Profile", + (!preset.ColorProfileSettings.ProfileName.IsNullOrWhiteSpace() ? $"{preset.ColorProfileSettings.ProfileName}" : "Unset") + " (click to change)", nvPresetColorProfileMenuItem_Click); + + FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Content type", typeof(InfoFrameVideoContentType), preset.HdmiInfoFrameSettings, "ContentType", nvPresetHdmiContentTypeMenuItem_Click, unchanged: true) + .DropDownItems[0].Visible = !isCurrentDisplay; + FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Colorimetry", typeof(InfoFrameVideoColorimetry), preset.HdmiInfoFrameSettings, "Colorimetry", nvPresetHdmiColorimetryMenuItem_Click, unchanged: true) + .DropDownItems[0].Visible = !isCurrentDisplay; + FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Extended colorimetry", typeof(InfoFrameVideoExtendedColorimetry), preset.HdmiInfoFrameSettings, "ExtendedColorimetry", nvPresetHdmiExtendedColorimetryMenuItem_Click, unchanged: true) + .DropDownItems[0].Visible = !isCurrentDisplay; + + BuildNvOverclockingMenu(preset); + + UpdateNvPresetMenuFont(); + + BuildNvidiaDriverSettingsMenu(mnuNvDriverSettings, isCurrentDisplay ? null : preset.driverSettings); + + mnuNvDriverSettings.Font = _menuItemFonts[preset.applyDriverSettings]; + } + + private void BuildNvOverclockingMenu(NvPreset preset) + { + miNvOverclockingIncluded.Checked = preset.applyOverclocking; + miNvOverclockingIncluded.Visible = !preset.IsDisplayPreset; + + var gpus = _nvService.GetGPUs(); + + foreach (var gpu in gpus) + { + var gpuPciId = gpu.BusInformation.PCIIdentifiers.ToString(); + + var name = $"{mnuNvOverclocking.Name}_{gpu.GPUId}"; + + var gpuItem = FormUtils.BuildMenuItem(mnuNvOverclocking.DropDownItems, name, gpu.FullName); + + var subName = $"{name}_change"; + + var ocSettings = preset.ocSettings.FirstOrDefault(s => s.PCIIdentifier == gpuPciId) ?? new NvGpuOcSettings { PCIIdentifier = gpuPciId }; + + var subText = $"{ocSettings} (click to change)"; + + var subItem = FormUtils.BuildMenuItem(gpuItem.DropDownItems, subName, subText, ocSettings, nvChangeOverclockingMenuItem_Click); + } + } + + private void nvChangeOverclockingMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + if (preset == null) + { + btnNvSetClocks_Click(btnNvSetClocks, new EventArgs()); + + return; + } + + var menuItem = (ToolStripItem)sender; + + var currentSettings = (NvGpuOcSettings)menuItem.Tag; + + var gpuInfo = NvGpuInfo.GetGpuInfo(currentSettings.PCIIdentifier); + + var newSettings = AskNvOcSettings(gpuInfo, currentSettings); + + if (newSettings == null) + { + return; + } + + preset.UpdateOverclockingSetting(newSettings); + + AddOrUpdateItem(); + } + + private void UpdateNvPresetMenuFont(bool updateTopItems = false) + { + foreach (var menu in mnuNvPresets.Items.OfType().Where(i => i != mnuNvDriverSettings)) + { + if (updateTopItems) + { + menu.Font = _normalFont; + } - SetMenuFont(menu, _normalFont); - } - } + SetMenuFont(menu, _normalFont); + } + } - private void SetMenuFont(ToolStripMenuItem menu, Font font) - { - foreach (var item in menu.DropDownItems.OfType()) - { - item.Font = item.Checked ? _boldFont : font; + private void SetMenuFont(ToolStripMenuItem menu, Font font) + { + foreach (var item in menu.DropDownItems.OfType()) + { + item.Font = item.Checked ? _boldFont : font; - SetMenuFont(item, font); - } - } + SetMenuFont(item, font); + } + } - private void BuildNvidiaDriverSettingsMenu(ToolStripDropDownItem parent, Dictionary settings = null, ContextMenuStrip contextMenuStrip = null) - { - var driverSettings = _nvService.GetVisibleSettings().OrderBy(s => s.GroupName); - var handledGroupItems = new Dictionary(); - var showUnchanged = settings != null; - var defaultValue = SettingMeta.UnsetDwordValue; + private void BuildNvidiaDriverSettingsMenu(ToolStripDropDownItem parent, Dictionary settings = null, ContextMenuStrip contextMenuStrip = null) + { + var driverSettings = _nvService.GetVisibleSettings().OrderBy(s => s.GroupName); + var handledGroupItems = new Dictionary(); + var showUnchanged = settings != null; + var defaultValue = SettingMeta.UnsetDwordValue; - foreach (var driverSetting in driverSettings) - { - var groupName = $"mnuNvDriverSettingGroup_{driverSetting.GroupName}"; - var groupItem = FormUtils.BuildMenuItem(contextMenuStrip?.Items ?? parent.DropDownItems, groupName, driverSetting.GroupName); + foreach (var driverSetting in driverSettings) + { + var groupName = $"mnuNvDriverSettingGroup_{driverSetting.GroupName}"; + var groupItem = FormUtils.BuildMenuItem(contextMenuStrip?.Items ?? parent.DropDownItems, groupName, driverSetting.GroupName); - var itemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}"; - var item = FormUtils.BuildMenuItem(groupItem.DropDownItems, itemName, driverSetting.SettingText, driverSetting); - var settingMeta = _nvService.GetSettingMeta(driverSetting.SettingId); + var itemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}"; + var item = FormUtils.BuildMenuItem(groupItem.DropDownItems, itemName, driverSetting.SettingText, driverSetting); + var settingMeta = _nvService.GetSettingMeta(driverSetting.SettingId); - var presetSetting = defaultValue; - var isDefault = false; - if (settings != null) - { - presetSetting = settings.GetValueOrDefault(driverSetting.SettingId, defaultValue); - isDefault = presetSetting == defaultValue; - } - else - { - (presetSetting, isDefault, _) = settingMeta.ToIntValue(driverSetting.ValueText); - } + var presetSetting = defaultValue; + var isDefault = false; + if (settings != null) + { + presetSetting = settings.GetValueOrDefault(driverSetting.SettingId, defaultValue); + isDefault = presetSetting == defaultValue; + } + else + { + (presetSetting, isDefault, _) = settingMeta.ToIntValue(driverSetting.ValueText); + } - item.Font = _menuItemFonts[!isDefault]; + item.Font = _menuItemFonts[!isDefault]; - if (!handledGroupItems.ContainsKey(groupItem)) - { - groupItem.Tag = null; - handledGroupItems.Add(groupItem, !isDefault); - } - else if (!isDefault) - { - handledGroupItems[groupItem] = true; - } - - var subItemUnchangedName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_Unchanged"; - var subItemUnchanged = FormUtils.BuildMenuItem(item.DropDownItems, subItemUnchangedName, "Unchanged", onClick: driverSettingValueNvMenuItem_Click); - subItemUnchanged.Visible = showUnchanged; - - if (showUnchanged) - { - subItemUnchanged.Checked = presetSetting == defaultValue; - subItemUnchanged.Font = _menuItemFonts[subItemUnchanged.Checked]; - } - - if (NvSettingConstants.RangeDriverSettings.Contains(driverSetting.SettingId)) - { - var value = settingMeta.DwordValues.FirstOrDefault(v => v.ValueName == driverSetting.ValueText); - - var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem"; - var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, "", onClick: driverSettingFrameRateNvMenuItem_Click); - - var metaValue = settingMeta.DwordValues.FirstOrDefault(v => v.Value == presetSetting); - subItem.Text = $"{metaValue?.ValueName ?? (driverSetting.SettingId == NvSettingConstants.DRS_FRAME_RATE_LIMITER_V3 ? "??FPS" : "default")} (click to change)"; - - subItem.Tag = value?.Value ?? 0; - subItem.Checked = presetSetting != defaultValue; - - continue; - } - - if (settingMeta.SettingType == nspector.Native.NVAPI2.NVDRS_SETTING_TYPE.NVDRS_DWORD_TYPE) - { - var index = 0; - - foreach (var settingValue in settingMeta.DwordValues) - { - var text = settingValue.ValueName; - if (text.IndexOf(" (") == 10) - { - if (settingMeta.DwordValues.Take(index).Any(v => v.Value == settingValue.Value)) - { - continue; - } - - text = text.Substring(0, 10); - } - - var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem_{settingValue.Value}"; - var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, text, settingValue, driverSettingValueNvMenuItem_Click); - - subItem.Checked = presetSetting == settingValue.Value; - subItem.Font = _menuItemFonts[subItem.Checked]; - - index++; - } - } - - if (settingMeta.SettingType == nspector.Native.NVAPI2.NVDRS_SETTING_TYPE.NVDRS_BINARY_TYPE) - { - foreach (var settingValue in settingMeta.BinaryValues) - { - var text = settingValue.ValueName.Substring(0, 18); - var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem_{text}"; - - var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, text, settingValue, driverSettingValueNvMenuItem_Click); - - var intValue = BitConverter.ToUInt32(settingValue.Value); - - subItem.Checked = presetSetting == intValue; - subItem.Font = _menuItemFonts[subItem.Checked]; - } - } - - if (item.DropDownItems.Count == 0 || item.DropDownItems.Count == 1 && item.DropDownItems[0].Text == "Unchanged") - { - groupItem.DropDownItems.Remove(item); - } - } - - foreach (var groupItemKeyValue in handledGroupItems) - { - groupItemKeyValue.Key.Font = _menuItemFonts[groupItemKeyValue.Value]; - } - } - - private void nvPresetColorDataMenuItem_Click(object sender, EventArgs e) - { - var menuItem = (ToolStripMenuItem)sender; - var property = (PropertyInfo)menuItem.OwnerItem.Tag; + if (!handledGroupItems.ContainsKey(groupItem)) + { + groupItem.Tag = null; + handledGroupItems.Add(groupItem, !isDefault); + } + else if (!isDefault) + { + handledGroupItems[groupItem] = true; + } + + var subItemUnchangedName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_Unchanged"; + var subItemUnchanged = FormUtils.BuildMenuItem(item.DropDownItems, subItemUnchangedName, "Unchanged", onClick: driverSettingValueNvMenuItem_Click); + subItemUnchanged.Visible = showUnchanged; + + if (showUnchanged) + { + subItemUnchanged.Checked = presetSetting == defaultValue; + subItemUnchanged.Font = _menuItemFonts[subItemUnchanged.Checked]; + } + + if (NvSettingConstants.RangeDriverSettings.Contains(driverSetting.SettingId)) + { + var value = settingMeta.DwordValues.FirstOrDefault(v => v.ValueName == driverSetting.ValueText); + + var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem"; + var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, "", onClick: driverSettingFrameRateNvMenuItem_Click); + + var metaValue = settingMeta.DwordValues.FirstOrDefault(v => v.Value == presetSetting); + subItem.Text = $"{metaValue?.ValueName ?? (driverSetting.SettingId == NvSettingConstants.DRS_FRAME_RATE_LIMITER_V3 ? "??FPS" : "default")} (click to change)"; + + subItem.Tag = value?.Value ?? 0; + subItem.Checked = presetSetting != defaultValue; + + continue; + } + + if (settingMeta.SettingType == nspector.Native.NVAPI2.NVDRS_SETTING_TYPE.NVDRS_DWORD_TYPE) + { + var index = 0; + + foreach (var settingValue in settingMeta.DwordValues) + { + var text = settingValue.ValueName; + if (text.IndexOf(" (") == 10) + { + if (settingMeta.DwordValues.Take(index).Any(v => v.Value == settingValue.Value)) + { + continue; + } + + text = text.Substring(0, 10); + } + + var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem_{settingValue.Value}"; + var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, text, settingValue, driverSettingValueNvMenuItem_Click); + + subItem.Checked = presetSetting == settingValue.Value; + subItem.Font = _menuItemFonts[subItem.Checked]; + + index++; + } + } + + if (settingMeta.SettingType == nspector.Native.NVAPI2.NVDRS_SETTING_TYPE.NVDRS_BINARY_TYPE) + { + foreach (var settingValue in settingMeta.BinaryValues) + { + var text = settingValue.ValueName.Substring(0, 18); + var subItemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}_SubItem_{text}"; + + var subItem = FormUtils.BuildMenuItem(item.DropDownItems, subItemName, text, settingValue, driverSettingValueNvMenuItem_Click); + + var intValue = BitConverter.ToUInt32(settingValue.Value); + + subItem.Checked = presetSetting == intValue; + subItem.Font = _menuItemFonts[subItem.Checked]; + } + } + + if (item.DropDownItems.Count == 0 || item.DropDownItems.Count == 1 && item.DropDownItems[0].Text == "Unchanged") + { + groupItem.DropDownItems.Remove(item); + } + } + + foreach (var groupItemKeyValue in handledGroupItems) + { + groupItemKeyValue.Key.Font = _menuItemFonts[groupItemKeyValue.Value]; + } + } + + private void nvPresetColorDataMenuItem_Click(object sender, EventArgs e) + { + var menuItem = (ToolStripMenuItem)sender; + var property = (PropertyInfo)menuItem.OwnerItem.Tag; - var value = menuItem.Tag; + var value = menuItem.Tag; - var preset = GetSelectedNvPreset(true); - - var dictionary = new Dictionary - { - { "ColorFormat", preset.colorData.ColorFormat }, - { "ColorDepth", preset.colorData.ColorDepth }, - { "Colorimetry", preset.colorData.Colorimetry }, - { "DynamicRange", preset.colorData.DynamicRange }, - { "SelectionPolicy", preset.colorData.SelectionPolicy } - }; + var preset = GetSelectedNvPreset(true); + + var dictionary = new Dictionary + { + { "ColorFormat", preset.colorData.ColorFormat }, + { "ColorDepth", preset.colorData.ColorDepth }, + { "Colorimetry", preset.colorData.Colorimetry }, + { "DynamicRange", preset.colorData.DynamicRange }, + { "SelectionPolicy", preset.colorData.SelectionPolicy } + }; - dictionary[property.Name] = value; + dictionary[property.Name] = value; - var colorData = NvPreset.GenerateColorData(dictionary); + var colorData = NvPreset.GenerateColorData(dictionary); - if (preset.IsDisplayPreset) - { - _nvService.SetColorData(preset.Display, colorData); + if (preset.IsDisplayPreset) + { + _nvService.SetColorData(preset.Display, colorData); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.colorData = colorData; + preset.colorData = colorData; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetHdmiContentTypeMenuItem_Click(object sender, EventArgs e) - { - UpdateHdmiSettings(sender); - } + private void nvPresetHdmiContentTypeMenuItem_Click(object sender, EventArgs e) + { + UpdateHdmiSettings(sender); + } - private void nvPresetHdmiColorimetryMenuItem_Click(object sender, EventArgs e) - { - UpdateHdmiSettings(sender); - } + private void nvPresetHdmiColorimetryMenuItem_Click(object sender, EventArgs e) + { + UpdateHdmiSettings(sender); + } - private void nvPresetHdmiExtendedColorimetryMenuItem_Click(object sender, EventArgs e) - { - UpdateHdmiSettings(sender); - } + private void nvPresetHdmiExtendedColorimetryMenuItem_Click(object sender, EventArgs e) + { + UpdateHdmiSettings(sender); + } - private void UpdateHdmiSettings(object sender) where T : struct - { - var menuItem = (ToolStripMenuItem)sender; - var propName = ((PropertyInfo)menuItem.OwnerItem.Tag).Name; - var value = (T)menuItem.Tag; + private void UpdateHdmiSettings(object sender) where T : struct + { + var menuItem = (ToolStripMenuItem)sender; + var propName = ((PropertyInfo)menuItem.OwnerItem.Tag).Name; + var value = (T)menuItem.Tag; - var preset = GetSelectedNvPreset(true); + var preset = GetSelectedNvPreset(true); - var settings = preset.IsDisplayPreset ? new NvHdmiInfoFrameSettings() : preset.HdmiInfoFrameSettings; - var propInfo = settings.GetType().GetDeclaredProperty(propName); - propInfo.SetValue(settings, value); + var settings = preset.IsDisplayPreset ? new NvHdmiInfoFrameSettings() : preset.HdmiInfoFrameSettings; + var propInfo = settings.GetType().GetDeclaredProperty(propName); + propInfo.SetValue(settings, value); - if (preset.IsDisplayPreset) - { - _nvService.SetHdmiSettings(preset.Display, settings); + if (preset.IsDisplayPreset) + { + _nvService.SetHdmiSettings(preset.Display, settings); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void UpdateDisplayConfigScaling(object sender, EventArgs e) - { - var menuItem = (ToolStripMenuItem)sender; - var value = (CCD.DisplayConfigScaling)menuItem.Tag; + private void UpdateDisplayConfigScaling(object sender, EventArgs e) + { + var menuItem = (ToolStripMenuItem)sender; + var value = (CCD.DisplayConfigScaling)menuItem.Tag; - var preset = GetSelectedNvPreset(true); - var settings = preset.DisplayConfig; + var preset = GetSelectedNvPreset(true); + var settings = preset.DisplayConfig; - if (preset.IsDisplayPreset) - { - var updatedSettings = new DisplayConfig(settings); - updatedSettings.Scaling = value; + if (preset.IsDisplayPreset) + { + var updatedSettings = new DisplayConfig(settings); + updatedSettings.Scaling = value; - _nvService.SetMode(preset.Display.Name, updatedSettings); + _nvService.SetMode(preset.Display.Name, updatedSettings); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.DisplayConfig.Scaling = value; + preset.DisplayConfig.Scaling = value; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void UpdateDisplayConfigRotation(object sender, EventArgs e) - { - var menuItem = (ToolStripMenuItem)sender; - var value = (CCD.DisplayConfigRotation)menuItem.Tag; + private void UpdateDisplayConfigRotation(object sender, EventArgs e) + { + var menuItem = (ToolStripMenuItem)sender; + var value = (CCD.DisplayConfigRotation)menuItem.Tag; - var preset = GetSelectedNvPreset(true); - var settings = preset.DisplayConfig; + var preset = GetSelectedNvPreset(true); + var settings = preset.DisplayConfig; - if (preset.IsDisplayPreset) - { - var updatedSettings = new DisplayConfig(settings); - updatedSettings.Rotation = value; + if (preset.IsDisplayPreset) + { + var updatedSettings = new DisplayConfig(settings); + updatedSettings.Rotation = value; - _nvService.SetMode(preset.Display.Name, updatedSettings); + _nvService.SetMode(preset.Display.Name, updatedSettings); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.DisplayConfig.Rotation = value; + preset.DisplayConfig.Rotation = value; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetBrightnessMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + private void nvPresetBrightnessMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - var label = "SDR brightness in %"; - if (!preset.IsDisplayPreset) - { - label += " (clear to set to unchanged)"; - } + var label = "SDR brightness in %"; + if (!preset.IsDisplayPreset) + { + label += " (clear to set to unchanged)"; + } - var value = MessageForms.SelectIntValue(0, 100, preset.SDRBrightness, label); + var value = MessageForms.SelectIntValue(0, 100, preset.SDRBrightness, label); - if (value == -1) - { - return; - } + if (value == -1) + { + return; + } - if (preset.IsDisplayPreset) - { - if (!value.HasValue) - { - return; - } + if (preset.IsDisplayPreset) + { + if (!value.HasValue) + { + return; + } - _nvService.SetSDRBrightness(preset.Display.Name, value.Value); + _nvService.SetSDRBrightness(preset.Display.Name, value.Value); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.SDRBrightness = value; + preset.SDRBrightness = value; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetDvcMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + private void nvPresetDvcMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - var label = "Digital vibrance in % (clear to set to default)"; + var label = "Digital vibrance in % (clear to set to default)"; - var display = _nvService.ResolveDisplay(preset); - var dvControl = display.DigitalVibranceControl; + var display = _nvService.ResolveDisplay(preset); + var dvControl = display.DigitalVibranceControl; - var value = MessageForms.SelectIntValue(dvControl.MinimumLevel, dvControl.MaximumLevel, preset.ColorEnhancementSettings.DigitalVibranceLevel, label); + var value = MessageForms.SelectIntValue(dvControl.MinimumLevel, dvControl.MaximumLevel, preset.ColorEnhancementSettings.DigitalVibranceLevel, label); - if (value == -1) - { - return; - } + if (value == -1) + { + return; + } - var intValue = value ?? dvControl.DefaultLevel; + var intValue = value ?? dvControl.DefaultLevel; - if (preset.IsDisplayPreset) - { - _nvService.SetDigitalVibranceLevel(preset.Display, intValue); + if (preset.IsDisplayPreset) + { + _nvService.SetDigitalVibranceLevel(preset.Display, intValue); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.ColorEnhancementSettings.DigitalVibranceLevel = intValue; + preset.ColorEnhancementSettings.DigitalVibranceLevel = intValue; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetHueMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + private void nvPresetHueMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - var label = "Hue angle in ° (clear to set to default)"; + var label = "Hue angle in ° (clear to set to default)"; - var display = _nvService.ResolveDisplay(preset); - var hueControl = display.HUEControl; + var display = _nvService.ResolveDisplay(preset); + var hueControl = display.HUEControl; - var value = MessageForms.SelectIntValue(hueControl.DefaultAngle, 359, preset.ColorEnhancementSettings.HueAngle, label); + var value = MessageForms.SelectIntValue(hueControl.DefaultAngle, 359, preset.ColorEnhancementSettings.HueAngle, label); - if (value == -1) - { - return; - } + if (value == -1) + { + return; + } - var intValue = value ?? hueControl.DefaultAngle; + var intValue = value ?? hueControl.DefaultAngle; - if (preset.IsDisplayPreset) - { - _nvService.SetHueAngle(preset.Display, intValue); + if (preset.IsDisplayPreset) + { + _nvService.SetHueAngle(preset.Display, intValue); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - preset.ColorEnhancementSettings.HueAngle = intValue; + preset.ColorEnhancementSettings.HueAngle = intValue; - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetScalingMenuItem_Click(object sender, EventArgs e) - { - var menuItem = (ToolStripMenuItem)sender; - var propName = ((PropertyInfo)menuItem.OwnerItem.Tag).Name; - var value = (Scaling?)menuItem.Tag; + private void nvPresetScalingMenuItem_Click(object sender, EventArgs e) + { + var menuItem = (ToolStripMenuItem)sender; + var propName = ((PropertyInfo)menuItem.OwnerItem.Tag).Name; + var value = (Scaling?)menuItem.Tag; - var preset = GetSelectedNvPreset(true); + var preset = GetSelectedNvPreset(true); - var propInfo = preset.GetType().GetDeclaredProperty(propName); - propInfo.SetValue(preset, value); + var propInfo = preset.GetType().GetDeclaredProperty(propName); + propInfo.SetValue(preset, value); - if (preset.IsDisplayPreset) - { - _nvService.SetScaling(preset.Display, value ?? Scaling.Default); + if (preset.IsDisplayPreset) + { + _nvService.SetScaling(preset.Display, value ?? Scaling.Default); - UpdateDisplayInfoItems(); - return; - } + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + AddOrUpdateItem(); + } - private void nvPresetColorProfileMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + private void nvPresetColorProfileMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - _nvService.ResolveDisplay(preset); + _nvService.ResolveDisplay(preset); - if (preset.Display == null) - { - MessageForms.WarningOk($"Display {preset.displayName} not found"); - return; - } + if (preset.Display == null) + { + MessageForms.WarningOk($"Display {preset.displayName} not found"); + return; + } - var display = preset.Display.Name; - if (!CCD.GetUsePerUserDisplayProfiles(display)) - { - if (MessageForms.QuestionYesNo("To be able to select a color profile, user specific settings have to be enabled. Do you want to enable this?") != DialogResult.Yes) - { - return; - } + var display = preset.Display.Name; + if (!CCD.GetUsePerUserDisplayProfiles(display)) + { + if (MessageForms.QuestionYesNo("To be able to select a color profile, user specific settings have to be enabled. Do you want to enable this?") != DialogResult.Yes) + { + return; + } - CCD.SetUsePerUserDisplayProfiles(display, true); - } + CCD.SetUsePerUserDisplayProfiles(display, true); + } - var profiles = CCD.GetDisplayColorProfiles(display); + //var profiles = CCD.GetDisplayColorProfiles(display); + var profiles = _colorProfileService.GetAllDisplayColorProfiles(display); - if (!profiles.Any()) - { - if (MessageForms.QuestionYesNo("There are no profiles associated with this display. You have to manually add these through the Color Management app. Do you want to open this?") != DialogResult.Yes) - { - return; - } + if (!profiles.Any()) + { + if (MessageForms.QuestionYesNo("There are no profiles associated with this display. You have to manually add these through the Color Management app. Do you want to open this?") != DialogResult.Yes) + { + return; + } - _winApiAdminService.StartProcess("colorcpl.exe"); + _winApiAdminService.StartProcess("colorcpl.exe"); - return; - } + return; + } - profiles.Insert(0, "None"); + var none = new ColorProfileDto { Name = "None" }; - var value = SelectStringValue(preset.ColorProfileSettings.ProfileName, "Color Profile", profiles); + profiles.Insert(0, none); - if (value == null) - { - return; - } + var value = MessageForms.SelectObjectValue(profiles.FirstOrDefault(p => p.Name == preset.ColorProfileSettings.ProfileName)?.ToString() ?? "None", "Color Profile", profiles); - if (value == "None") - { - value = null; - } + if (value == null) + { + return; + } - if (preset.IsDisplayPreset) - { - _nvService.SetColorProfile(preset.Display, value); + if (value == none) + { + value = null; + } - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetColorProfile(preset.Display, value?.Name); - preset.ColorProfileSettings.ProfileName = value; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.ColorProfileSettings.ProfileName = value?.Name; - private void refreshRateMenuItem_Click(object sender, EventArgs e) - { - var refreshRate = (Rational)((ToolStripItem)sender).Tag; + AddOrUpdateItem(); + } - var preset = GetSelectedNvPreset(true); + private void refreshRateMenuItem_Click(object sender, EventArgs e) + { + var refreshRate = (Rational)((ToolStripItem)sender).Tag; - if (preset.IsDisplayPreset) - { - _nvService.SetMode(refreshRate: refreshRate, preset: preset); + var preset = GetSelectedNvPreset(true); - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetMode(refreshRate: refreshRate, preset: preset); - preset.DisplayConfig.RefreshRate = refreshRate; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.DisplayConfig.RefreshRate = refreshRate; - private void resolutionNvMenuItem_Click(object sender, EventArgs e) - { - var mode = (VirtualResolution)((ToolStripItem)sender).Tag; + AddOrUpdateItem(); + } - var preset = GetSelectedNvPreset(true); + private void resolutionNvMenuItem_Click(object sender, EventArgs e) + { + var mode = (VirtualResolution)((ToolStripItem)sender).Tag; - if (preset.IsDisplayPreset) - { - _nvService.SetMode(mode, preset: preset); + var preset = GetSelectedNvPreset(true); - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetMode(mode, preset: preset); - preset.DisplayConfig.Resolution.ActiveWidth = mode.ActiveWidth; - preset.DisplayConfig.Resolution.ActiveHeight = mode.ActiveHeight; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.DisplayConfig.Resolution.ActiveWidth = mode.ActiveWidth; + preset.DisplayConfig.Resolution.ActiveHeight = mode.ActiveHeight; - private void virtualResolutionNvMenuItem_Click(object sender, EventArgs e) - { - var mode = (VirtualResolution)((ToolStripItem)sender).Tag; + AddOrUpdateItem(); + } - var preset = GetSelectedNvPreset(true); + private void virtualResolutionNvMenuItem_Click(object sender, EventArgs e) + { + var mode = (VirtualResolution)((ToolStripItem)sender).Tag; - var virtualMode = new VirtualResolution(mode); - virtualMode.ActiveWidth = preset.DisplayConfig.Resolution.ActiveWidth; - virtualMode.ActiveHeight = preset.DisplayConfig.Resolution.ActiveHeight; + var preset = GetSelectedNvPreset(true); - if (preset.IsDisplayPreset) - { - _nvService.SetMode(virtualMode, preset: preset); + var virtualMode = new VirtualResolution(mode); + virtualMode.ActiveWidth = preset.DisplayConfig.Resolution.ActiveWidth; + virtualMode.ActiveHeight = preset.DisplayConfig.Resolution.ActiveHeight; - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetMode(virtualMode, preset: preset); - preset.DisplayConfig.Resolution.VirtualWidth = virtualMode.VirtualWidth; - preset.DisplayConfig.Resolution.VirtualHeight = virtualMode.VirtualHeight; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.DisplayConfig.Resolution.VirtualWidth = virtualMode.VirtualWidth; + preset.DisplayConfig.Resolution.VirtualHeight = virtualMode.VirtualHeight; - private void displayMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + AddOrUpdateItem(); + } - var menuItem = (ToolStripItem)sender; - if (menuItem.Tag != null) - { - var displayInfo = (NvDisplayInfo)menuItem.Tag; + private void displayMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - preset.displayName = displayInfo.Name; - preset.DisplayId = displayInfo.DisplayId; - preset.primaryDisplay = false; - } - else - { - preset.primaryDisplay = true; - preset.displayName = null; - preset.DisplayId = null; - } + var menuItem = (ToolStripItem)sender; + if (menuItem.Tag != null) + { + var displayInfo = (NvDisplayInfo)menuItem.Tag; - AddOrUpdateItem(); - } + preset.displayName = displayInfo.Name; + preset.DisplayId = displayInfo.DisplayId; + preset.primaryDisplay = false; + } + else + { + preset.primaryDisplay = true; + preset.displayName = null; + preset.DisplayId = null; + } - private void btnAddModesNv_Click(object sender, EventArgs e) - { - var nameField = new FieldDefinition - { - Label = "Preset name", - Value = _nvService.CreateNewPresetName() - }; + AddOrUpdateItem(); + } + private void btnAddModesNv_Click(object sender, EventArgs e) + { + var nameField = new FieldDefinition + { + Label = "Preset name", + Value = _nvService.CreateNewPresetName() + }; - var displays = _nvService.GetSimpleDisplayInfos(); - //var primaryDisplay = _nvService.GetPrimaryDisplay(); - displays.Insert(0, new NvDisplayInfo(null, null, null, "Primary display")); - var displayField = FieldDefinition.CreateDropDownField("Display", displays.Select(d => d.Name).ToList()); + var displays = _nvService.GetSimpleDisplayInfos(); + //var primaryDisplay = _nvService.GetPrimaryDisplay(); + displays.Insert(0, new NvDisplayInfo(null, null, null, "Primary display")); - var sectionLabel = new FieldDefinition - { - Label = "Check the sections below you want to configure", - FieldType = FieldType.Label - }; + var displayField = FieldDefinition.CreateDropDownField("Display", displays.Select(d => d.Name).ToList()); - var colorSelectField = FieldDefinition.CreateCheckField("Color settings"); - var resolutionSelectField = FieldDefinition.CreateCheckField("Resolution"); - var refreshRateSelectField = FieldDefinition.CreateCheckField("Refresh rate"); + var sectionLabel = new FieldDefinition + { + Label = "Check the sections below you want to configure", + FieldType = FieldType.Label + }; - var result = MessageForms.ShowDialog("Create new NVIDIA preset", new[] { nameField, displayField, sectionLabel, colorSelectField, resolutionSelectField, refreshRateSelectField }); + var colorSelectField = FieldDefinition.CreateCheckField("Color settings"); + var resolutionSelectField = FieldDefinition.CreateCheckField("Resolution"); + var refreshRateSelectField = FieldDefinition.CreateCheckField("Refresh rate"); - if (!result.Any()) - { - return; - } + var result = MessageForms.ShowDialog("Create new NVIDIA preset", new[] { nameField, displayField, sectionLabel, colorSelectField, resolutionSelectField, refreshRateSelectField }); - var display = displays.FirstOrDefault(d => d.ToString() == displayField.Value.ToString()); + if (!result.Any()) + { + return; + } - var preset = new NvPreset(); - preset.name = nameField.Value.ToString(); - preset.primaryDisplay = displayField.Value.ToString() == "Primary display"; - preset.DisplayId = preset.primaryDisplay ? null : display?.DisplayId; - preset.displayName = preset.primaryDisplay ? null : display?.Name; + var display = displays.FirstOrDefault(d => d.ToString() == displayField.Value.ToString()); - AskColorData(preset, colorSelectField.ValueAsBool); - AskResolution(preset, resolutionSelectField.ValueAsBool); - AskRefreshRate(preset, refreshRateSelectField.ValueAsBool); + var preset = new NvPreset(); + preset.name = nameField.Value.ToString(); + preset.primaryDisplay = displayField.Value.ToString() == "Primary display"; + preset.DisplayId = preset.primaryDisplay ? null : display?.DisplayId; + preset.displayName = preset.primaryDisplay ? null : display?.Name; - AddOrUpdateItem(preset); - } + AskColorData(preset, colorSelectField.ValueAsBool); + AskResolution(preset, resolutionSelectField.ValueAsBool); + AskRefreshRate(preset, refreshRateSelectField.ValueAsBool); - private void AskColorData(NvPreset preset, bool ask = true) - { - preset.colorData = NvPreset.DefaultColorData; + AddOrUpdateItem(preset); + } - if (!ask) - { - preset.applyColorData = false; - return; - } + private void AskColorData(NvPreset preset, bool ask = true) + { + preset.colorData = NvPreset.DefaultColorData; - var bitDepthField = FieldDefinition.CreateEnumField("Bit depth", ColorDataDepth.Default); - var colorFormatField = FieldDefinition.CreateEnumField("Color format", ColorDataFormat.Auto); - var dynamicRangeField = FieldDefinition.CreateEnumField("Dynamic range", ColorDataDynamicRange.Auto); - var colorimetryField = FieldDefinition.CreateEnumField("Color space", ColorDataColorimetry.Auto); + if (!ask) + { + preset.applyColorData = false; + return; + } - var result = MessageForms.ShowDialog("Color settings", new[] { bitDepthField, colorFormatField, dynamicRangeField, colorimetryField }); + var bitDepthField = FieldDefinition.CreateEnumField("Bit depth", ColorDataDepth.Default); + var colorFormatField = FieldDefinition.CreateEnumField("Color format", ColorDataFormat.Auto); + var dynamicRangeField = FieldDefinition.CreateEnumField("Dynamic range", ColorDataDynamicRange.Auto); + var colorimetryField = FieldDefinition.CreateEnumField("Color space", ColorDataColorimetry.Auto); - if (!result.Any()) - { - return; - } + var result = MessageForms.ShowDialog("Color settings", new[] { bitDepthField, colorFormatField, dynamicRangeField, colorimetryField }); - preset.colorData = new ColorData( - colorFormatField.ValueAsEnum(), - colorimetryField.ValueAsEnum(), - dynamicRange: dynamicRangeField.ValueAsEnum(), - colorDepth: bitDepthField.ValueAsEnum()); + if (!result.Any()) + { + return; + } - preset.applyColorData = true; - } + preset.colorData = new ColorData( + colorFormatField.ValueAsEnum(), + colorimetryField.ValueAsEnum(), + dynamicRange: dynamicRangeField.ValueAsEnum(), + colorDepth: bitDepthField.ValueAsEnum()); - private void AskResolution(NvPreset preset, bool ask = true) - { - if (!ask) - { - return; - } + preset.applyColorData = true; + } - var resolutions = _nvService.GetAvailableResolutionsV2(preset); + private void AskResolution(NvPreset preset, bool ask = true) + { + if (!ask) + { + return; + } - var modes = resolutions.Select(r => r.ToString()).ToList(); + var resolutions = _nvService.GetAvailableResolutionsV2(preset); - var modesField = FieldDefinition.CreateDropDownField("Resolution", modes); + var modes = resolutions.Select(r => r.ToString()).ToList(); - var result = MessageForms.ShowDialog("Resolution", new[] { modesField }); + var modesField = FieldDefinition.CreateDropDownField("Resolution", modes); - if (!result.Any()) - { - return; - } + var result = MessageForms.ShowDialog("Resolution", new[] { modesField }); - var value = modesField.Value.ToString(); - var resolution = resolutions.First(r => r.ToString() == value); + if (!result.Any()) + { + return; + } - preset.DisplayConfig.Resolution = resolution; + var value = modesField.Value.ToString(); + var resolution = resolutions.First(r => r.ToString() == value); - preset.DisplayConfig.ApplyResolution = true; - } + preset.DisplayConfig.Resolution = resolution; - private void AskRefreshRate(NvPreset preset, bool ask = true) - { - if (!ask) - { - return; - } + preset.DisplayConfig.ApplyResolution = true; + } - var modes = _nvService.GetAvailableRefreshRatesV2(preset).ToList(); + private void AskRefreshRate(NvPreset preset, bool ask = true) + { + if (!ask) + { + return; + } - var stringModes = modes.Select(m => m.ToString()).ToList(); + var modes = _nvService.GetAvailableRefreshRatesV2(preset).ToList(); - var modesField = FieldDefinition.CreateDropDownField("Refresh rate", stringModes); + var stringModes = modes.Select(m => m.ToString()).ToList(); - var result = MessageForms.ShowDialog("Refresh rate", new[] { modesField }); + var modesField = FieldDefinition.CreateDropDownField("Refresh rate", stringModes); - if (!result.Any()) - { - return; - } + var result = MessageForms.ShowDialog("Refresh rate", new[] { modesField }); - var value = modesField.Value.ToString(); - var refreshRate = modes.FirstOrDefault(m => m.ToString() == value); + if (!result.Any()) + { + return; + } - if (refreshRate != null) - { - preset.DisplayConfig.RefreshRate = refreshRate; - preset.DisplayConfig.ApplyRefreshRate = true; - } - } + var value = modesField.Value.ToString(); + var refreshRate = modes.FirstOrDefault(m => m.ToString() == value); - private void miHDRIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + if (refreshRate != null) + { + preset.DisplayConfig.RefreshRate = refreshRate; + preset.DisplayConfig.ApplyRefreshRate = true; + } + } - preset.applyHDR = !preset.applyHDR; + private void miHDRIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - AddOrUpdateItem(); - } + preset.applyHDR = !preset.applyHDR; - private void miToggleHDR_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + AddOrUpdateItem(); + } - if (preset.IsDisplayPreset) - { - _nvService.SetHDRState(preset.Display, !_nvService.IsHDREnabled()); + private void miToggleHDR_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetHDRState(preset.Display, !_nvService.IsHDREnabled()); - preset.toggleHDR = !preset.toggleHDR; + UpdateDisplayInfoItems(); + return; + } - if (preset.toggleHDR) - { - preset.HDREnabled = false; - } + preset.toggleHDR = !preset.toggleHDR; - AddOrUpdateItem(); - } + if (preset.toggleHDR) + { + preset.HDREnabled = false; + } - private void miHDREnabled_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + AddOrUpdateItem(); + } - if (preset.IsDisplayPreset) - { - miHDREnabled.Checked = !miHDREnabled.Checked; - _nvService.SetHDRState(preset.Display, miHDREnabled.Checked); + private void miHDREnabled_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + miHDREnabled.Checked = !miHDREnabled.Checked; + _nvService.SetHDRState(preset.Display, miHDREnabled.Checked); - preset.HDREnabled = !preset.HDREnabled; - preset.toggleHDR = false; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.HDREnabled = !preset.HDREnabled; + preset.toggleHDR = false; - private async Task ApplySelectedNvPreset() - { - var preset = GetSelectedNvPreset(); - await _nvService.ApplyPresetUi(preset); - } + AddOrUpdateItem(); + } - private NvPreset GetSelectedNvPreset(bool currentDisplay = false, bool driverSettings = false) - { - if (lvNvPresets.SelectedItems.Count > 0) - { - var item = lvNvPresets.SelectedItems[0]; - var result = (NvPreset)item.Tag; + private async Task ApplySelectedNvPreset() + { + var preset = GetSelectedNvPreset(); + await _nvService.ApplyPresetUi(preset); + } - if (result == null && currentDisplay) - { - result = _nvService.GetPresetForDisplay(item.SubItems[1].Text, driverSettings); - } + private NvPreset GetSelectedNvPreset(bool currentDisplay = false, bool driverSettings = false) + { + if (lvNvPresets.SelectedItems.Count > 0) + { + var item = lvNvPresets.SelectedItems[0]; + var result = (NvPreset)item.Tag; - return result; - } - else - { - return null; - } - } + if (result == null && currentDisplay) + { + result = _nvService.GetPresetForDisplay(item.SubItems[1].Text, driverSettings); + } - private void edtShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } + return result; + } + else + { + return null; + } + } - private void btnSetShortcut_Click(object sender, EventArgs e) - { - var shortcut = edtShortcut.Text.Trim(); + private void edtShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); + } - if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) - { - return; - } + private void btnSetShortcut_Click(object sender, EventArgs e) + { + var shortcut = edtShortcut.Text.Trim(); - var preset = GetSelectedNvPreset(); + if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) + { + return; + } - var name = edtNvPresetName.Text.Trim(); + var preset = GetSelectedNvPreset(); - if (!string.IsNullOrEmpty(name) && _nvService.GetPresets().Any(x => x.id != preset.id && x.name != null && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - MessageForms.WarningOk("The name must be unique."); - return; - } + var name = edtNvPresetName.Text.Trim(); - if (string.IsNullOrEmpty(name) && chkNvShowInQuickAccess.Checked) - { - MessageForms.WarningOk("A name must be entered before enabling Quick Access."); - return; - } + if (!string.IsNullOrEmpty(name) && _nvService.GetPresets().Any(x => x.id != preset.id && x.name != null && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + MessageForms.WarningOk("The name must be unique."); + return; + } - preset.shortcut = shortcut; - preset.name = name; - preset.ShowInQuickAccess = chkNvShowInQuickAccess.Checked; + if (string.IsNullOrEmpty(name) && chkNvShowInQuickAccess.Checked) + { + MessageForms.WarningOk("A name must be entered before enabling Quick Access."); + return; + } - AddOrUpdateItem(); + preset.shortcut = shortcut; + preset.name = name; + preset.ShowInQuickAccess = chkNvShowInQuickAccess.Checked; - _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); - } + AddOrUpdateItem(); - private void edtShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } + _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); + } - private void edtShortcut_TextChanged(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + private void edtShortcut_KeyUp(object sender, KeyEventArgs e) + { + KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); + } - ServiceFormUtils.UpdateShortcutTextBox(edtShortcut, preset); - } + private void edtShortcut_TextChanged(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - private void miNvResolutionIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + ServiceFormUtils.UpdateShortcutTextBox(edtShortcut, preset); + } - preset.DisplayConfig.ApplyResolution = !preset.DisplayConfig.ApplyResolution; + private void miNvResolutionIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - AddOrUpdateItem(); - } + preset.DisplayConfig.ApplyResolution = !preset.DisplayConfig.ApplyResolution; - private void miNvDriverSettingsIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + AddOrUpdateItem(); + } - preset.applyDriverSettings = !preset.applyDriverSettings; + private void miNvDriverSettingsIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - AddOrUpdateItem(); - } + preset.applyDriverSettings = !preset.applyDriverSettings; - private void driverSettingValueNvMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + AddOrUpdateItem(); + } - var menuItem = (ToolStripItem)sender; - var parentMenuItem = menuItem.OwnerItem as ToolStripMenuItem; + private void driverSettingValueNvMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - var settingItem = (SettingItem)parentMenuItem.Tag; + var menuItem = (ToolStripItem)sender; + var parentMenuItem = menuItem.OwnerItem as ToolStripMenuItem; - if (menuItem.Tag == null) - { - if (!preset.IsDisplayPreset) - { - preset.ResetDriverSetting(settingItem.SettingId); - AddOrUpdateItem(); - } - return; - } + var settingItem = (SettingItem)parentMenuItem.Tag; - var intValue = menuItem.Tag is SettingValue intSettingValue ? intSettingValue.Value : - menuItem.Tag is SettingValue byteSettingValue ? BitConverter.ToUInt32(byteSettingValue.Value) : 0; + if (menuItem.Tag == null) + { + if (!preset.IsDisplayPreset) + { + preset.ResetDriverSetting(settingItem.SettingId); + AddOrUpdateItem(); + } + return; + } - if (preset.IsDisplayPreset) - { - _nvService.SetDriverSetting(settingItem.SettingId, intValue); + var intValue = menuItem.Tag is SettingValue intSettingValue ? intSettingValue.Value : + menuItem.Tag is SettingValue byteSettingValue ? BitConverter.ToUInt32(byteSettingValue.Value) : 0; - UpdateDisplayInfoItems(); + if (preset.IsDisplayPreset) + { + _nvService.SetDriverSetting(settingItem.SettingId, intValue); - return; - } + UpdateDisplayInfoItems(); - preset.UpdateDriverSetting(settingItem.SettingId, intValue); + return; + } - AddOrUpdateItem(); - } + preset.UpdateDriverSetting(settingItem.SettingId, intValue); - private void driverSettingFrameRateNvMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); + AddOrUpdateItem(); + } - var menuItem = (ToolStripItem)sender; - var parentMenuItem = menuItem.OwnerItem as ToolStripMenuItem; + private void driverSettingFrameRateNvMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); - var settingItem = (SettingItem)parentMenuItem.Tag; + var menuItem = (ToolStripItem)sender; + var parentMenuItem = menuItem.OwnerItem as ToolStripMenuItem; - var settingMeta = _nvService.GetSettingMeta(settingItem.SettingId); + var settingItem = (SettingItem)parentMenuItem.Tag; - uint presetValue; - if (preset.IsDisplayPreset) - { - (presetValue, _, _) = settingMeta.ToIntValue(settingItem.ValueText); - } - else - { - presetValue = preset.driverSettings.GetValueOrDefault(settingItem.SettingId, settingMeta.DefaultDwordValue); - } + var settingMeta = _nvService.GetSettingMeta(settingItem.SettingId); - var settingValue = SelectNvDriverValue(settingMeta, presetValue); + uint presetValue; + if (preset.IsDisplayPreset) + { + (presetValue, _, _) = settingMeta.ToIntValue(settingItem.ValueText); + } + else + { + presetValue = preset.driverSettings.GetValueOrDefault(settingItem.SettingId, settingMeta.DefaultDwordValue); + } - if (settingValue == SettingMeta.UnsetDwordValue) - { - return; - } + var settingValue = SelectNvDriverValue(settingMeta, presetValue); + if (settingValue == SettingMeta.UnsetDwordValue) + { + return; + } - if (preset.IsDisplayPreset) - { - _nvService.SetDriverSetting(settingItem.SettingId, settingValue); - UpdateDisplayInfoItems(); - - return; - } - - preset.UpdateDriverSetting(settingItem.SettingId, settingValue); - - AddOrUpdateItem(); - } - - private uint SelectNvDriverValue(SettingMeta settingMeta, uint currentValue) - { - var field = new FieldDefinition - { - MinValue = settingMeta.DwordValues.First().Value, - MaxValue = settingMeta.DwordValues.Last().Value, - Label = settingMeta.SettingName, - FieldType = FieldType.Numeric, - Value = currentValue - }; + if (preset.IsDisplayPreset) + { + _nvService.SetDriverSetting(settingItem.SettingId, settingValue); - var resultFields = MessageForms.ShowDialog(field.Label, new[] { field }); - - if (!resultFields.Any()) - { - return SettingMeta.UnsetDwordValue; - } - - var settingValue = (uint)Utils.ParseInt((string)resultFields.First().Value); - - return settingValue; - } - - private string SelectStringValue(string currentValue, string label, IEnumerable values) - { - var field = new FieldDefinition - { - Label = label, - FieldType = FieldType.DropDown, - Values = values, - Value = currentValue - }; - - var resultFields = MessageForms.ShowDialog(field.Label, new[] { field }); - - if (!resultFields.Any()) - { - return null; - } + UpdateDisplayInfoItems(); - var strValue = (string)resultFields.First().Value; + return; + } - if (string.IsNullOrWhiteSpace(strValue)) - { - return null; - } + preset.UpdateDriverSetting(settingItem.SettingId, settingValue); - return strValue; - } - - private void miNvProfileInspector_Click(object sender, EventArgs e) - { - StartNvProfileInspector(); - } - - public void StartNvProfileInspector() - { - var form = new frmDrvSettings(); - form.Show(); - } - - private void miNvSettings_Click(object sender, EventArgs e) - { - EditNvSettings(); - } - - private void EditNvSettings() - { - var quickAccessField = new FieldDefinition - { - Label = "Quick Access shortcut", - FieldType = FieldType.Shortcut, - Value = _nvService.Config.QuickAccessShortcut - }; - - var enableOcField = new FieldDefinition - { - Label = "Enable overclocking", - FieldType = FieldType.CheckBox, - Value = _nvService.Config.ShowOverclocking - }; - - var enableNovideoField = new FieldDefinition - { - Label = "Apply Novideo sRGB settings on startup", - FieldType = FieldType.CheckBox, - Value = _nvService.Config.ApplyNovideoOnStartup - }; - - var values = MessageForms.ShowDialog("NVIDIA controller settings", new[] { - quickAccessField, - enableOcField, - enableNovideoField - }); - if (!values.Any()) - { - return; - } - - var shortcut = (string)quickAccessField.Value; - - _nvService.Config.QuickAccessShortcut = shortcut; - - _keyboardShortcutDispatcher.RegisterShortcut(NvService.SHORTCUTID_NVQA, _nvService.Config.QuickAccessShortcut); - - _nvService.Config.ShowOverclocking = enableOcField.ValueAsBool; - _nvService.Config.ApplyNovideoOnStartup = enableNovideoField.ValueAsBool; - - UpdateInfo(); - } - - private void btnNvSettings_Click(object sender, EventArgs e) - { - mnuNvSettings.ShowCustom(btnNvSettings); - } - - private ListViewItem _lastItem; - private int _lastSubItemIndex; - - private void lvNvPresets_MouseMove(object sender, MouseEventArgs e) - { - var item = lvNvPresets.GetItemAt(e.X, e.Y); - var subItem = item?.GetSubItemAt(e.X, e.Y); - - if (item == null || subItem == null || subItem?.Text?.Length < 20) - { - lvNvPresetsToolTip.Active = false; - _lastItem = null; - - return; - } - - var index = item.SubItems.IndexOf(subItem); - - lvNvPresets.ShowItemToolTips = false; - - if (item == _lastItem && index == _lastSubItemIndex) - { - return; - } - - _lastItem = item; - _lastSubItemIndex = index; - - var text = subItem.Text; - - if (index == 8) - { - text = text.Replace(", ", "\r\n"); - } - - var point = lvNvPresets.PointToClient(Cursor.Position); - point.X += 10; - point.Y += 0; - - lvNvPresetsToolTip.Active = false; - if (index >= 0) - { - lvNvPresetsToolTip.Active = true; - lvNvPresetsToolTip.Show("", lvNvPresets, point); - lvNvPresetsToolTip.Show(text, lvNvPresets, point); - } - } - - private void lvNvPresets_MouseLeave(object sender, EventArgs e) - { - lvNvPresetsToolTip.Active = false; - } - - private void btnNvSetClocks_Click(object sender, EventArgs e) - { - var gpu = cbxNvGPU.SelectedItem as PhysicalGPU; - - if (gpu == null) - { - return; - } - - var gpuInfo = new NvGpuInfo(gpu); - var currentSettings = gpuInfo.GetOverclockSettings(); - - var newSettings = AskNvOcSettings(gpuInfo, currentSettings); - - if (newSettings == null) - { - return; - } - - _nvService.ApplyOverclocking(new List { newSettings }); - - UpdateNvGpuInfo(); - } - - public NvGpuOcSettings AskNvOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) - { - var overclockTypes = gpuInfo.SupportedOcTypes.Select(t => Utils.GetDescription(t)); - - var values = MessageForms.ShowDialog($"Overclock settings - {gpuInfo.GPU.FullName}", new[] { - new FieldDefinition - { - Label = "Overclock method", - FieldType = FieldType.DropDown, - Values = overclockTypes, - Value = currentSettings.Type - } }, okButtonText: "Next >"); - if (!values.Any()) - { - return null; - } - - var overclockType = Enum.Parse(values[0].Value.ToString()); - var newSettings = default(NvGpuOcSettings); - - if (overclockType == NvGpuOcType.Curve) - { - newSettings = AskNvCurveOcSettings(gpuInfo, currentSettings); - } - else if (overclockType == NvGpuOcType.Offset) - { - newSettings = AskNvOffsetOcSettings(gpuInfo, currentSettings); - } - else if (overclockType == NvGpuOcType.BoostLock) - { - newSettings = AskNvBoostLockSettings(gpuInfo, currentSettings); - } - else - { - newSettings = new NvGpuOcSettings - { - Type = overclockType, - PCIIdentifier = gpuInfo.PCIIdentifier - }; - } - - return newSettings; - } - - public NvGpuOcSettings AskNvCurveOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) - { - var nvOverclockSettings = new NvGpuOcSettings - { - Type = NvGpuOcType.Curve, - PCIIdentifier = gpuInfo.PCIIdentifier - }; - - var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); - var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); - - var (voltField, freqField) = gpuInfo.GetCurveVoltFreqFields(currentSettings); - - var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); - var powerField = gpuInfo.GetPowerField(currentSettings); - - var values = MessageForms.ShowDialog($"Curve overclock settings - {gpuInfo.GPU.FullName}", new[] - { - coreOffsetField, voltField, freqField, memoryOffsetField, voltageBoostField, powerField, - }); - if (!values.Any()) - { - return null; - } - - nvOverclockSettings.GraphicsOffsetKHz = coreOffsetField.ValueAsInt * 1000; - nvOverclockSettings.MaximumVoltageUv = voltField.ValueAsUInt * 1000; - nvOverclockSettings.MaximumFrequencyKHz = freqField.ValueAsUInt * 1000; - nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; - nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; - nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); - nvOverclockSettings.FrequencyPreferred = nvOverclockSettings.MaximumFrequencyKHz > 0 && nvOverclockSettings.MaximumVoltageUv == 0; - - return nvOverclockSettings; - } - - public NvGpuOcSettings AskNvOffsetOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) - { - var nvOverclockSettings = new NvGpuOcSettings - { - Type = NvGpuOcType.Offset, - PCIIdentifier = gpuInfo.PCIIdentifier - }; - - var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); - var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); - - var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); - var powerField = gpuInfo.GetPowerField(currentSettings); - - var values = MessageForms.ShowDialog($"Offset overclock settings - {gpuInfo.GPU.FullName}", new[] { - coreOffsetField, - memoryOffsetField, - voltageBoostField, - powerField, - }); - if (!values.Any()) - { - return null; - } - - nvOverclockSettings.GraphicsOffsetKHz = coreOffsetField.ValueAsInt * 1000; - nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; - nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; - nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); - nvOverclockSettings.FrequencyPreferred = nvOverclockSettings.MaximumFrequencyKHz > 0 && nvOverclockSettings.MaximumVoltageUv == 0; - - return nvOverclockSettings; - } - - public NvGpuOcSettings AskNvBoostLockSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) - { - var nvOverclockSettings = new NvGpuOcSettings - { - Type = NvGpuOcType.BoostLock, - PCIIdentifier = gpuInfo.PCIIdentifier - }; - - var curveEntries = gpuInfo.CurveV3.GPUCurveEntries; - - var currentGraphicsOffset = gpuInfo.GetOffset(NvAPIWrapper.Native.GPU.PublicClockDomain.Graphics); - - var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); - var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); - - var (voltField, freqField) = gpuInfo.GetCurveVoltFreqFields(currentSettings); - voltField.Label = "Locked voltage in mV"; - freqField.Label = "Locked boost frequency in MHz"; - - var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); - var powerField = gpuInfo.GetPowerField(currentSettings); - - var values = MessageForms.ShowDialog($"Boost lock overclock settings - {gpuInfo.GPU.FullName}", new[] { - voltField, - freqField, - memoryOffsetField, - voltageBoostField, - powerField - }); - if (!values.Any()) - { - return null; - } - - var newVolt = voltField.ValueAsUInt * 1000; - var newFreq = freqField.ValueAsUInt * 1000; - - var newCurveEntryFreq = curveEntries.FirstOrDefault(e => e.DefaultFrequencyInkHz == newFreq); - var newCurveEntryVolt = curveEntries.FirstOrDefault(e => e.DefaultVoltageInMicroV == newVolt); - - var coreOffset = (int)(newCurveEntryFreq.DefaultFrequencyInkHz - newCurveEntryVolt.DefaultFrequencyInkHz); - - nvOverclockSettings.GraphicsOffsetKHz = coreOffset; - nvOverclockSettings.MaximumFrequencyKHz = newFreq; - nvOverclockSettings.MaximumVoltageUv = newVolt; - nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; - nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; - nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); - - return nvOverclockSettings; - } - - private void miNvOverclockingIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - preset.applyOverclocking = !preset.applyOverclocking; - - AddOrUpdateItem(); - } - - private void cbxNvGPU_SelectedIndexChanged(object sender, EventArgs e) - { - RefreshNvGpuInfo(); - } - - private void RefreshNvGpuInfo() - { - var gpu = (PhysicalGPU)cbxNvGPU.SelectedItem; - - if (gpu == null) - { - return; - } - - var gpuInfo = new NvGpuInfo(gpu); - var ocSettings = gpuInfo.GetOverclockSettings(); - - edtNvGpuInfo.Text = gpuInfo.ToString(); - edtNvOverclock.Text = ocSettings.ToString(); - } - - private void miNvCopyId_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - - if (preset != null) - { - Clipboard.SetText(preset.id.ToString()); - } - } + AddOrUpdateItem(); + } - private void miNvPresetApplyOnStartup_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); - _config.NvPresetId_ApplyOnStartup = miNvPresetApplyOnStartup.Checked ? preset.id : 0; + private uint SelectNvDriverValue(SettingMeta settingMeta, uint currentValue) + { + var field = new FieldDefinition + { + MinValue = settingMeta.DwordValues.First().Value, + MaxValue = settingMeta.DwordValues.Last().Value, + Label = settingMeta.SettingName, + FieldType = FieldType.Numeric, + Value = currentValue + }; - AddOrUpdateItem(); - } + var resultFields = MessageForms.ShowDialog(field.Label, new[] { field }); - private async Task ApplyNvPresetOnStartup() - { - var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; + if (!resultFields.Any()) + { + return SettingMeta.UnsetDwordValue; + } - var presetIdOrName = !string.IsNullOrEmpty(startUpParams.NvidiaPresetIdOrName) ? startUpParams.NvidiaPresetIdOrName : _config.NvPresetId_ApplyOnStartup.ToString(); + var settingValue = (uint)Utils.ParseInt((string)resultFields.First().Value); - if (string.IsNullOrEmpty(presetIdOrName)) - { - return; - } + return settingValue; + } - var preset = _nvService?.GetPresetByIdOrName(presetIdOrName); - if (preset == null) - { - if (string.IsNullOrEmpty(startUpParams.NvidiaPresetIdOrName)) - { - _config.NvPresetId_ApplyOnStartup = 0; - } - return; - } + private void miNvProfileInspector_Click(object sender, EventArgs e) + { + StartNvProfileInspector(); + } - preset.IsStartupPreset = true; - await _nvService.ApplyPresetUi(preset); - } + public void StartNvProfileInspector() + { + var form = new frmDrvSettings(); + form.Show(); + } + + private void miNvSettings_Click(object sender, EventArgs e) + { + EditNvSettings(); + } - private void miNvDithering6bit_Click(object sender, EventArgs e) - { - var item = sender as ToolStripMenuItem; + private void EditNvSettings() + { + var quickAccessField = new FieldDefinition + { + Label = "Quick Access shortcut", + FieldType = FieldType.Shortcut, + Value = _nvService.Config.QuickAccessShortcut + }; + + var enableOcField = new FieldDefinition + { + Label = "Enable overclocking", + FieldType = FieldType.CheckBox, + Value = _nvService.Config.ShowOverclocking + }; + + var enableNovideoField = new FieldDefinition + { + Label = "Apply Novideo sRGB settings on startup", + FieldType = FieldType.CheckBox, + Value = _nvService.Config.ApplyNovideoOnStartup + }; + + var values = MessageForms.ShowDialog("NVIDIA controller settings", new[] { + quickAccessField, + enableOcField, + enableNovideoField + }); + if (!values.Any()) + { + return; + } + + var shortcut = (string)quickAccessField.Value; + + _nvService.Config.QuickAccessShortcut = shortcut; + + _keyboardShortcutDispatcher.RegisterShortcut(NvService.SHORTCUTID_NVQA, _nvService.Config.QuickAccessShortcut); + + _nvService.Config.ShowOverclocking = enableOcField.ValueAsBool; + _nvService.Config.ApplyNovideoOnStartup = enableNovideoField.ValueAsBool; + + UpdateInfo(); + } + + private void btnNvSettings_Click(object sender, EventArgs e) + { + mnuNvSettings.ShowCustom(btnNvSettings); + } + + private ListViewItem _lastItem; + private int _lastSubItemIndex; + + private void lvNvPresets_MouseMove(object sender, MouseEventArgs e) + { + var item = lvNvPresets.GetItemAt(e.X, e.Y); + var subItem = item?.GetSubItemAt(e.X, e.Y); + + if (item == null || subItem == null || subItem?.Text?.Length < 20) + { + lvNvPresetsToolTip.Active = false; + _lastItem = null; + + return; + } + + var index = item.SubItems.IndexOf(subItem); + + lvNvPresets.ShowItemToolTips = false; + + if (item == _lastItem && index == _lastSubItemIndex) + { + return; + } + + _lastItem = item; + _lastSubItemIndex = index; + + var text = subItem.Text; + + if (index == 8) + { + text = text.Replace(", ", "\r\n"); + } + + var point = lvNvPresets.PointToClient(Cursor.Position); + point.X += 10; + point.Y += 0; + + lvNvPresetsToolTip.Active = false; + if (index >= 0) + { + lvNvPresetsToolTip.Active = true; + lvNvPresetsToolTip.Show("", lvNvPresets, point); + lvNvPresetsToolTip.Show(text, lvNvPresets, point); + } + } + + private void lvNvPresets_MouseLeave(object sender, EventArgs e) + { + lvNvPresetsToolTip.Active = false; + } + + private void btnNvSetClocks_Click(object sender, EventArgs e) + { + var gpu = cbxNvGPU.SelectedItem as PhysicalGPU; + + if (gpu == null) + { + return; + } + + var gpuInfo = new NvGpuInfo(gpu); + var currentSettings = gpuInfo.GetOverclockSettings(); + + var newSettings = AskNvOcSettings(gpuInfo, currentSettings); + + if (newSettings == null) + { + return; + } + + _nvService.ApplyOverclocking(new List { newSettings }); + + UpdateNvGpuInfo(); + } + + public NvGpuOcSettings AskNvOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) + { + var overclockTypes = gpuInfo.SupportedOcTypes.Select(t => Utils.GetDescription(t)); + + var values = MessageForms.ShowDialog($"Overclock settings - {gpuInfo.GPU.FullName}", new[] { + new FieldDefinition + { + Label = "Overclock method", + FieldType = FieldType.DropDown, + Values = overclockTypes, + Value = currentSettings.Type + } }, okButtonText: "Next >"); + if (!values.Any()) + { + return null; + } + + var overclockType = Enum.Parse(values[0].Value.ToString()); + var newSettings = default(NvGpuOcSettings); + + if (overclockType == NvGpuOcType.Curve) + { + newSettings = AskNvCurveOcSettings(gpuInfo, currentSettings); + } + else if (overclockType == NvGpuOcType.Offset) + { + newSettings = AskNvOffsetOcSettings(gpuInfo, currentSettings); + } + else if (overclockType == NvGpuOcType.BoostLock) + { + newSettings = AskNvBoostLockSettings(gpuInfo, currentSettings); + } + else + { + newSettings = new NvGpuOcSettings + { + Type = overclockType, + PCIIdentifier = gpuInfo.PCIIdentifier + }; + } + + return newSettings; + } + + public NvGpuOcSettings AskNvCurveOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) + { + var nvOverclockSettings = new NvGpuOcSettings + { + Type = NvGpuOcType.Curve, + PCIIdentifier = gpuInfo.PCIIdentifier + }; + + var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); + var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); + + var (voltField, freqField) = gpuInfo.GetCurveVoltFreqFields(currentSettings); + + var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); + var powerField = gpuInfo.GetPowerField(currentSettings); + + var values = MessageForms.ShowDialog($"Curve overclock settings - {gpuInfo.GPU.FullName}", new[] + { + coreOffsetField, voltField, freqField, memoryOffsetField, voltageBoostField, powerField, + }); + if (!values.Any()) + { + return null; + } + + nvOverclockSettings.GraphicsOffsetKHz = coreOffsetField.ValueAsInt * 1000; + nvOverclockSettings.MaximumVoltageUv = voltField.ValueAsUInt * 1000; + nvOverclockSettings.MaximumFrequencyKHz = freqField.ValueAsUInt * 1000; + nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; + nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; + nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); + nvOverclockSettings.FrequencyPreferred = nvOverclockSettings.MaximumFrequencyKHz > 0 && nvOverclockSettings.MaximumVoltageUv == 0; + + return nvOverclockSettings; + } + + public NvGpuOcSettings AskNvOffsetOcSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) + { + var nvOverclockSettings = new NvGpuOcSettings + { + Type = NvGpuOcType.Offset, + PCIIdentifier = gpuInfo.PCIIdentifier + }; + + var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); + var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); + + var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); + var powerField = gpuInfo.GetPowerField(currentSettings); + + var values = MessageForms.ShowDialog($"Offset overclock settings - {gpuInfo.GPU.FullName}", new[] { + coreOffsetField, + memoryOffsetField, + voltageBoostField, + powerField, + }); + if (!values.Any()) + { + return null; + } + + nvOverclockSettings.GraphicsOffsetKHz = coreOffsetField.ValueAsInt * 1000; + nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; + nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; + nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); + nvOverclockSettings.FrequencyPreferred = nvOverclockSettings.MaximumFrequencyKHz > 0 && nvOverclockSettings.MaximumVoltageUv == 0; + + return nvOverclockSettings; + } + + public NvGpuOcSettings AskNvBoostLockSettings(NvGpuInfo gpuInfo, NvGpuOcSettings currentSettings) + { + var nvOverclockSettings = new NvGpuOcSettings + { + Type = NvGpuOcType.BoostLock, + PCIIdentifier = gpuInfo.PCIIdentifier + }; + + var curveEntries = gpuInfo.CurveV3.GPUCurveEntries; + + var currentGraphicsOffset = gpuInfo.GetOffset(NvAPIWrapper.Native.GPU.PublicClockDomain.Graphics); + + var coreOffsetField = gpuInfo.GetCoreOffsetField(currentSettings); + var memoryOffsetField = gpuInfo.GetMemoryOffsetField(currentSettings); + + var (voltField, freqField) = gpuInfo.GetCurveVoltFreqFields(currentSettings); + voltField.Label = "Locked voltage in mV"; + freqField.Label = "Locked boost frequency in MHz"; + + var voltageBoostField = gpuInfo.GetVoltageBoostField(currentSettings); + var powerField = gpuInfo.GetPowerField(currentSettings); + + var values = MessageForms.ShowDialog($"Boost lock overclock settings - {gpuInfo.GPU.FullName}", new[] { + voltField, + freqField, + memoryOffsetField, + voltageBoostField, + powerField + }); + if (!values.Any()) + { + return null; + } + + var newVolt = voltField.ValueAsUInt * 1000; + var newFreq = freqField.ValueAsUInt * 1000; + + var newCurveEntryFreq = curveEntries.FirstOrDefault(e => e.DefaultFrequencyInkHz == newFreq); + var newCurveEntryVolt = curveEntries.FirstOrDefault(e => e.DefaultVoltageInMicroV == newVolt); + + var coreOffset = (int)(newCurveEntryFreq.DefaultFrequencyInkHz - newCurveEntryVolt.DefaultFrequencyInkHz); + + nvOverclockSettings.GraphicsOffsetKHz = coreOffset; + nvOverclockSettings.MaximumFrequencyKHz = newFreq; + nvOverclockSettings.MaximumVoltageUv = newVolt; + nvOverclockSettings.MemoryOffsetKHz = memoryOffsetField.ValueAsInt * 1000; + nvOverclockSettings.VoltageBoostPercent = voltageBoostField.ValueAsUInt; + nvOverclockSettings.PowerPCM = gpuInfo.PowerToPCM(powerField.ValueAsUInt, powerField.FieldType == FieldType.TrackBar); + + return nvOverclockSettings; + } + + private void miNvOverclockingIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + preset.applyOverclocking = !preset.applyOverclocking; + + AddOrUpdateItem(); + } + + private void cbxNvGPU_SelectedIndexChanged(object sender, EventArgs e) + { + RefreshNvGpuInfo(); + } + + private void RefreshNvGpuInfo() + { + var gpu = (PhysicalGPU)cbxNvGPU.SelectedItem; + + if (gpu == null) + { + return; + } + + var gpuInfo = new NvGpuInfo(gpu); + var ocSettings = gpuInfo.GetOverclockSettings(); + + edtNvGpuInfo.Text = gpuInfo.ToString(); + edtNvOverclock.Text = ocSettings.ToString(); + } + + private void miNvCopyId_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + + if (preset != null) + { + Clipboard.SetText(preset.id.ToString()); + } + } - var preset = GetSelectedNvPreset(true); + private void miNvPresetApplyOnStartup_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); + _config.NvPresetId_ApplyOnStartup = miNvPresetApplyOnStartup.Checked ? preset.id : 0; - var ditheringBits = uint.Parse(item.Tag.ToString()); + AddOrUpdateItem(); + } - if (preset.IsDisplayPreset) - { - _nvService.SetDithering(preset.DitherState, ditheringBits, preset.ditheringMode); + private async Task ApplyNvPresetOnStartup() + { + var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; - UpdateDisplayInfoItems(); - return; - } + var presetIdOrName = !string.IsNullOrEmpty(startUpParams.NvidiaPresetIdOrName) ? startUpParams.NvidiaPresetIdOrName : _config.NvPresetId_ApplyOnStartup.ToString(); - preset.ditheringBits = ditheringBits; + if (string.IsNullOrEmpty(presetIdOrName)) + { + return; + } - AddOrUpdateItem(); - } + var preset = _nvService?.GetPresetByIdOrName(presetIdOrName); + if (preset == null) + { + if (string.IsNullOrEmpty(startUpParams.NvidiaPresetIdOrName)) + { + _config.NvPresetId_ApplyOnStartup = 0; + } + return; + } - private void spatial1ToolStripMenuItem_Click(object sender, EventArgs e) - { - var item = sender as ToolStripMenuItem; + preset.IsStartupPreset = true; + await _nvService.ApplyPresetUi(preset); + } - var preset = GetSelectedNvPreset(true); + private void miNvDithering6bit_Click(object sender, EventArgs e) + { + var item = sender as ToolStripMenuItem; - var ditheringMode = uint.Parse(item.Tag.ToString()); + var preset = GetSelectedNvPreset(true); - if (preset.IsDisplayPreset) - { - _nvService.SetDithering(preset.DitherState, preset.ditheringBits, ditheringMode); + var ditheringBits = uint.Parse(item.Tag.ToString()); - UpdateDisplayInfoItems(); - return; - } + if (preset.IsDisplayPreset) + { + _nvService.SetDithering(preset.DitherState, ditheringBits, preset.ditheringMode); - preset.ditheringMode = ditheringMode; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.ditheringBits = ditheringBits; - public async Task AfterInitialized() - { - await ApplyNvPresetOnStartup(); - } + AddOrUpdateItem(); + } - internal void Save() - { - FormUtils.SaveSortState(lvNvPresets.ListViewItemSorter, _config.NvPresetsSortState); - } + private void spatial1ToolStripMenuItem_Click(object sender, EventArgs e) + { + var item = sender as ToolStripMenuItem; - public void UpdateInfo() - { - UpdateDisplayInfoItems(); - } + var preset = GetSelectedNvPreset(true); - private void miNvNovideo_Click(object sender, EventArgs e) - { - MainWindow.CreateAndShow(); - } + var ditheringMode = uint.Parse(item.Tag.ToString()); - private void miNvOtherIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + if (preset.IsDisplayPreset) + { + _nvService.SetDithering(preset.DitherState, preset.ditheringBits, ditheringMode); - preset.applyOther = !preset.applyOther; + UpdateDisplayInfoItems(); + return; + } - AddOrUpdateItem(); - } + preset.ditheringMode = ditheringMode; - private void miNvHdmiIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + AddOrUpdateItem(); + } - preset.applyHdmiSettings = !preset.applyHdmiSettings; + public async Task AfterInitialized() + { + await ApplyNvPresetOnStartup(); + } - AddOrUpdateItem(); - } + internal void Save() + { + FormUtils.SaveSortState(lvNvPresets.ListViewItemSorter, _config.NvPresetsSortState); + } - private void miNvTestDithering_Click(object sender, EventArgs e) - { - var panel = new NvDitherPanel(_nvService); + public void UpdateInfo() + { + UpdateDisplayInfoItems(); + } - MessageForms.ShowControl(panel, "Test dithering"); - } + private void miNvNovideo_Click(object sender, EventArgs e) + { + MainWindow.CreateAndShow(); + } - private void miNVIDIAInfo_Click(object sender, EventArgs e) - { - var panel = new NvInfoPanel(_nvService); + private void miNvOtherIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - MessageForms.ShowControl(panel, "NVIDIA Info", height: 800); - } + preset.applyOther = !preset.applyOther; - private void miNvPresetColorEnhancementsIncluded_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(); + AddOrUpdateItem(); + } - preset.ApplyColorEnhancements = !preset.ApplyColorEnhancements; + private void miNvHdmiIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - AddOrUpdateItem(); - } + preset.applyHdmiSettings = !preset.applyHdmiSettings; - private void miHDROutputModeUnchanged_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); - var menuItem = (ToolStripMenuItem)sender; + AddOrUpdateItem(); + } - var outputMode = menuItem.Tag == null ? default : (string)menuItem.Tag == "1" ? NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10 : NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; + private void miNvTestDithering_Click(object sender, EventArgs e) + { + var panel = new NvDitherPanel(_nvService); - if (preset.IsDisplayPreset) - { - if (outputMode == default) - { - return; - } + MessageForms.ShowControl(panel, "Test dithering"); + } - _nvService.SetOutputMode(outputMode, preset.Display); + private void miNVIDIAInfo_Click(object sender, EventArgs e) + { + var panel = new NvInfoPanel(_nvService); - UpdateDisplayInfoItems(); - return; - } + MessageForms.ShowControl(panel, "NVIDIA Info", height: 800); + } - preset.HdrSettings.OutputMode = outputMode == default ? null : outputMode; + private void miNvPresetColorEnhancementsIncluded_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(); - AddOrUpdateItem(); + preset.ApplyColorEnhancements = !preset.ApplyColorEnhancements; - } + AddOrUpdateItem(); + } - private void miNvTrigger1ToolStripMenuItem_Click(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); - var menuItem = (ToolStripMenuItem)sender; + private void miHDROutputModeUnchanged_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + var menuItem = (ToolStripMenuItem)sender; - var trigger = preset.Triggers.FirstOrDefault() ?? new PresetTrigger(); - var triggerTypes = Utils.EnumToDictionary(new[] { PresetTriggerType.ProcessSwitch, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop, PresetTriggerType.Reserved5 }).Select(d => d.Value); - var conditions = Utils.GetDescriptions(fromValue: 1); + var outputMode = menuItem.Tag == null ? default : (string)menuItem.Tag == "1" ? NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10 : NvHdrSettings.NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_HDR10PLUS_GAMING; - var eventField = new FieldDefinition - { - Label = "Event", - FieldType = FieldType.DropDown, - Values = triggerTypes, - Value = trigger.Trigger.GetDescription() - }; + if (preset.IsDisplayPreset) + { + if (outputMode == default) + { + return; + } - var conditionsField = new FieldDefinition - { - Label = "Conditions", - FieldType = FieldType.Flags, - Values = conditions, - Value = trigger.Conditions - }; + _nvService.SetOutputMode(outputMode, preset.Display); - var values = MessageForms.ShowDialog("Edit trigger 1", new[] { - eventField, - conditionsField - }); - if (!values.Any()) - { - return; - } + UpdateDisplayInfoItems(); + return; + } - trigger.Trigger = eventField.ValueAsEnum(); - trigger.Conditions = conditionsField.ValueAsEnum(); + preset.HdrSettings.OutputMode = outputMode == default ? null : outputMode; - preset.UpdateTrigger(trigger.Trigger, trigger.Conditions); + AddOrUpdateItem(); - AddOrUpdateItem(); + } - } - - public void Init() - { - } - - private void mnuRefreshRate_DropDownOpening(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); - - if (preset == null) - { - return; - } - - miRefreshRateIncluded.Visible = !preset.IsDisplayPreset; - miRefreshRateIncluded.Checked = preset.DisplayConfig.ApplyRefreshRate; - - var refreshRates = _nvService.GetAvailableRefreshRatesV2(preset); - - while (mnuRefreshRate.DropDownItems.Count > 1) - { - mnuRefreshRate.DropDownItems.RemoveAt(mnuRefreshRate.DropDownItems.Count - 1); - } - - foreach (var refreshRate in refreshRates) - { - var item = mnuRefreshRate.DropDownItems.AddCustom(refreshRate.ToString() + "Hz") as ToolStripMenuItem; - item.Tag = refreshRate; - item.Click += refreshRateMenuItem_Click; - item.Checked = refreshRate.Equals(preset.DisplayConfig.RefreshRate); - } - - UpdateNvPresetMenuFont(); - } - - private void mnuNvResolution_DropDownOpening(object sender, EventArgs e) - { - var preset = GetSelectedNvPreset(true); - - miNvResolutionIncluded.Checked = preset.DisplayConfig.ApplyResolution; - - mnuNvActiveResolution.DropDownItems.Clear(); - mnuNvVirtualResolution.DropDownItems.Clear(); - - var modes = _nvService.GetAvailableResolutionsV2(preset); - foreach (var mode in modes) - { - var item = mnuNvActiveResolution.DropDownItems.AddCustom(mode.ToString()) as ToolStripMenuItem; - item.Tag = mode; - item.Click += resolutionNvMenuItem_Click; - item.Checked = mode.ActiveWidth == preset.DisplayConfig.Resolution.ActiveWidth && mode.ActiveHeight == preset.DisplayConfig.Resolution.ActiveHeight; - } - - foreach (var mode in modes) - { - var item = mnuNvVirtualResolution.DropDownItems.AddCustom(mode.ToString()) as ToolStripMenuItem; - item.Tag = mode; - item.Click += virtualResolutionNvMenuItem_Click; - item.Checked = mode.VirtualWidth == preset.DisplayConfig.Resolution.VirtualWidth && mode.VirtualHeight == preset.DisplayConfig.Resolution.VirtualHeight; - } - - UpdateNvPresetMenuFont(); - } - } + private void miNvTrigger1ToolStripMenuItem_Click(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + var menuItem = (ToolStripMenuItem)sender; + + var trigger = preset.Triggers.FirstOrDefault() ?? new PresetTrigger(); + var triggerTypes = Utils.EnumToDictionary(new[] { PresetTriggerType.ProcessSwitch, PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop, PresetTriggerType.Reserved5 }).Select(d => d.Value); + var conditions = Utils.GetDescriptions(fromValue: 1); + + var eventField = new FieldDefinition + { + Label = "Event", + FieldType = FieldType.DropDown, + Values = triggerTypes, + Value = trigger.Trigger.GetDescription() + }; + + var conditionsField = new FieldDefinition + { + Label = "Conditions", + FieldType = FieldType.Flags, + Values = conditions, + Value = trigger.Conditions + }; + + var values = MessageForms.ShowDialog("Edit trigger 1", new[] { + eventField, + conditionsField + }); + if (!values.Any()) + { + return; + } + + trigger.Trigger = eventField.ValueAsEnum(); + trigger.Conditions = conditionsField.ValueAsEnum(); + + preset.UpdateTrigger(trigger.Trigger, trigger.Conditions); + + AddOrUpdateItem(); + + } + + public void Init() + { + } + + private void mnuRefreshRate_DropDownOpening(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + + if (preset == null) + { + return; + } + + miRefreshRateIncluded.Visible = !preset.IsDisplayPreset; + miRefreshRateIncluded.Checked = preset.DisplayConfig.ApplyRefreshRate; + + var refreshRates = _nvService.GetAvailableRefreshRatesV2(preset); + + while (mnuRefreshRate.DropDownItems.Count > 1) + { + mnuRefreshRate.DropDownItems.RemoveAt(mnuRefreshRate.DropDownItems.Count - 1); + } + + foreach (var refreshRate in refreshRates) + { + var item = mnuRefreshRate.DropDownItems.AddCustom(refreshRate.ToString() + "Hz") as ToolStripMenuItem; + item.Tag = refreshRate; + item.Click += refreshRateMenuItem_Click; + item.Checked = refreshRate.Equals(preset.DisplayConfig.RefreshRate); + } + + UpdateNvPresetMenuFont(); + } + + private void mnuNvResolution_DropDownOpening(object sender, EventArgs e) + { + var preset = GetSelectedNvPreset(true); + + miNvResolutionIncluded.Checked = preset.DisplayConfig.ApplyResolution; + + mnuNvActiveResolution.DropDownItems.Clear(); + mnuNvVirtualResolution.DropDownItems.Clear(); + + var modes = _nvService.GetAvailableResolutionsV2(preset); + foreach (var mode in modes) + { + var item = mnuNvActiveResolution.DropDownItems.AddCustom(mode.ToString()) as ToolStripMenuItem; + item.Tag = mode; + item.Click += resolutionNvMenuItem_Click; + item.Checked = mode.ActiveWidth == preset.DisplayConfig.Resolution.ActiveWidth && mode.ActiveHeight == preset.DisplayConfig.Resolution.ActiveHeight; + } + + foreach (var mode in modes) + { + var item = mnuNvVirtualResolution.DropDownItems.AddCustom(mode.ToString()) as ToolStripMenuItem; + item.Tag = mode; + item.Click += virtualResolutionNvMenuItem_Click; + item.Checked = mode.VirtualWidth == preset.DisplayConfig.Resolution.VirtualWidth && mode.VirtualHeight == preset.DisplayConfig.Resolution.VirtualHeight; + } + + UpdateNvPresetMenuFont(); + } + } } diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs index ecadb80..abe87e3 100644 --- a/ColorControl/Services/NVIDIA/NvService.cs +++ b/ColorControl/Services/NVIDIA/NvService.cs @@ -139,7 +139,6 @@ namespace ColorControl.Services.NVIDIA private readonly RpcClientService _rpcClientService; private readonly PowerEventDispatcher _powerEventDispatcher; private readonly ServiceManager _serviceManager; - private static NvService ServiceInstance; public static readonly int SHORTCUTID_NVQA = -200; @@ -422,9 +421,9 @@ namespace ColorControl.Services.NVIDIA if (preset.applyOther) { - if (preset.ColorProfileSettings.ProfileName != null) + if (!string.IsNullOrWhiteSpace(preset.ColorProfileSettings.ProfileName)) { - CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName, _globalContext.Config.SetMinTmlAndMaxTml, newHdrEnabled); + CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName, _globalContext.Config.SetMinTmlAndMaxTml, newHdrEnabled || !applyHdr && hdrEnabled); } if (preset.scaling.HasValue) @@ -462,7 +461,7 @@ namespace ColorControl.Services.NVIDIA SetNovideoSettings(preset.DisplayId, preset.NovideoSettings); } - if (preset.applyOverclocking) + if (Config.ShowOverclocking && preset.applyOverclocking) { result = ApplyOverclocking(preset.ocSettings); } @@ -1203,7 +1202,7 @@ namespace ColorControl.Services.NVIDIA DisplayApi.SetDisplayConfig(newPathInfos, DisplayConfigFlags.SaveToPersistence); } - private IPathTargetInfo? GetTargetInfoForDisplay(Display display) + private IPathTargetInfo GetTargetInfoForDisplay(Display display) { var pathInfos = DisplayApi.GetDisplayConfig(); @@ -1219,7 +1218,7 @@ namespace ColorControl.Services.NVIDIA return pathTargetInfo; } - private IPathInfo? GetPathInfoForDisplay(Display display) + private IPathInfo GetPathInfoForDisplay(Display display) { var pathInfos = DisplayApi.GetDisplayConfig(); diff --git a/ColorControl/Services/Samsung/SamsungDevice.cs b/ColorControl/Services/Samsung/SamsungDevice.cs index 8b65738..b45f1d4 100644 --- a/ColorControl/Services/Samsung/SamsungDevice.cs +++ b/ColorControl/Services/Samsung/SamsungDevice.cs @@ -737,7 +737,8 @@ namespace ColorControl.Services.Samsung return serviceMenuTypeField.ValueAsEnum() switch { ServiceMenuType.FactoryMenu => await OpenFactoryMenu(), - ServiceMenuType.HospitalityMenu => await OpenHospitalityMenu() + ServiceMenuType.HospitalityMenu => await OpenHospitalityMenu(), + _ => throw new NotImplementedException() }; } @@ -823,7 +824,8 @@ namespace ColorControl.Services.Samsung var task = exitServiceMenuTypeField.ValueAsEnum() switch { ExitServiceMenuType.Exit => ExecutePresetWithProgressAsync(SamsungFactoryMenuPresets.Exit, "Final Step: exiting service menu..."), - ExitServiceMenuType.Reboot => ExecutePresetWithProgressAsync(SamsungGenericPresets.Reboot, "Final Step: rebooting TV...") + ExitServiceMenuType.Reboot => ExecutePresetWithProgressAsync(SamsungGenericPresets.Reboot, "Final Step: rebooting TV..."), + _ => throw new NotImplementedException() }; await task; diff --git a/ColorControl/Services/Samsung/SamsungPanel.Designer.cs b/ColorControl/Services/Samsung/SamsungPanel.Designer.cs index 227010e..5fd12ae 100644 --- a/ColorControl/Services/Samsung/SamsungPanel.Designer.cs +++ b/ColorControl/Services/Samsung/SamsungPanel.Designer.cs @@ -778,7 +778,7 @@ clbLgPower.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; clbLgPower.ColumnWidth = 240; clbLgPower.FormattingEnabled = true; - clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Use secure connection" }); + clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Power off when locking the pc", "Power on when unlocking the pc", "Use secure connection" }); clbLgPower.Location = new System.Drawing.Point(6, 70); clbLgPower.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); clbLgPower.MultiColumn = true; diff --git a/ColorControl/Services/Samsung/SamsungPanel.cs b/ColorControl/Services/Samsung/SamsungPanel.cs index 7fcf07b..fd85d52 100644 --- a/ColorControl/Services/Samsung/SamsungPanel.cs +++ b/ColorControl/Services/Samsung/SamsungPanel.cs @@ -18,639 +18,641 @@ using System.Windows.Forms; namespace ColorControl.Services.Samsung { - partial class SamsungPanel : UserControl, IModulePanel - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private Config _config; - private SamsungService _samsungService; - private readonly GlobalContext _globalContext; - private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; - private readonly ServiceManager _serviceManager; - private string _tabMessage; - private bool _disableEvents = false; - - public SamsungPanel(SamsungService samsungService, GlobalContext globalContext, KeyboardShortcutDispatcher keyboardShortcutDispatcher, ServiceManager serviceManager) - { - _samsungService = samsungService; - _globalContext = globalContext; - _keyboardShortcutDispatcher = keyboardShortcutDispatcher; - _serviceManager = serviceManager; - _config = Shared.Common.GlobalContext.CurrentContext.Config; - - InitializeComponent(); - - scSamsungController.Panel2Collapsed = true; - - FillPresets(); - - FormUtils.InitSortState(lvSamsungPresets, _config.SamsungPresetsSortState); - - //_samsungService.AfterApplyPreset += SamsungServiceAfterApplyPreset; - //_samsungService.SelectedDeviceChangedEvent += _samsungService_SelectedDeviceChangedEvent; - - edtSamsungDeviceFilter.Text = _samsungService.Config.DeviceSearchKey; - - var values = Enum.GetValues(typeof(ButtonType)); - foreach (var button in values) - { - var text = button.ToString(); - if (text[0] == '_') - { - text = text.Substring(1); - } - - var item = mnuLgRcButtons.DropDownItems.AddCustom(text); - item.Click += miLgAddButton_Click; - } - } - - public void Init() - { - _ = Handle; - - if (_samsungService.Devices == null) - { - RefreshSamsungDevices(); - return; - } - - _ = AfterSamsungServiceRefreshDevices(); - } - - private void _samsungService_SelectedDeviceChangedEvent(object sender, EventArgs e) - { - FormUtils.BeginInvokeCheck(this, () => SetSamsungDevicesSelectedIndex(sender)); - } - - private void SetSamsungDevicesSelectedIndex(object sender) - { - if (sender == null) - { - cbxSamsungDevices.SelectedIndex = -1; - return; - } - - cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(sender); - } - - private async Task AfterSamsungServiceRefreshDevices() - { - FillSamsungDevices(); - - var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; - - if (startUpParams.ExecuteSamsungPreset) - { - await _samsungService.ApplyPreset(startUpParams.SamsungPresetName); - return; - } - } - - internal void Save() - { - FormUtils.SaveSortState(lvSamsungPresets.ListViewItemSorter, _config.SamsungPresetsSortState); - } - - private void edtShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } - - private void edtShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } - - private void FillPresets() - { - FormUtils.InitListView(lvSamsungPresets, SamsungPreset.GetColumnNames()); - - foreach (var preset in _samsungService.GetPresets()) - { - AddOrUpdateItem(preset); - } - } - - private void AddOrUpdateItem(SamsungPreset preset = null, ListViewItem specItem = null) - { - ServiceFormUtils.AddOrUpdateListItem(lvSamsungPresets, _samsungService.GetPresets(), _config, preset, specItem); - } - - private void btnCloneLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedPreset(); - - var newPreset = preset.Clone(); - AddOrUpdateItem(newPreset); - } - - private SamsungPreset GetSelectedPreset() - { - if (lvSamsungPresets.SelectedItems.Count > 0) - { - var item = lvSamsungPresets.SelectedItems[0]; - return (SamsungPreset)item.Tag; - } - else - { - return null; - } - } - - private void lvSamsungPresets_SelectedIndexChanged(object sender, EventArgs e) - { - var enabled = lvSamsungPresets.SelectedItems.Count > 0; - btnApplyLg.Enabled = enabled; - btnCloneLg.Enabled = enabled; - edtNameLg.Enabled = enabled; - chkSamsungQuickAccess.Enabled = enabled; - edtSamsungPresetDescription.Enabled = enabled; - cbxSamsungPresetDevice.Enabled = enabled; - edtShortcutLg.Enabled = enabled; - btnSetShortcutLg.Enabled = enabled; - edtStepsLg.Enabled = enabled; - btnSamsungAddButton.Enabled = enabled; - cbxSamsungApps.Enabled = enabled; - btnSamsungRefreshApps.Enabled = enabled; - btnDeleteLg.Enabled = enabled; - cbxSamsungPresetTrigger.Enabled = enabled; - edtSamsungPresetTriggerConditions.Enabled = enabled; - btnSamsungPresetEditTriggerConditions.Enabled = enabled; - edtSamsungPresetIncludedProcesses.Enabled = enabled; - edtSamsungPresetExcludedProcesses.Enabled = enabled; - edtSamsungPresetConnectedDisplaysRegex.Enabled = enabled; - - var preset = GetSelectedPreset(); - - if (preset != null) - { - edtNameLg.Text = preset.name; - chkSamsungQuickAccess.Checked = preset.ShowInQuickAccess; - edtSamsungPresetDescription.Text = preset.Description; - var lgApps = _samsungService?.GetApps(); - cbxSamsungApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.AppId.Equals(preset.AppId)); - edtShortcutLg.Text = preset.shortcut; - edtStepsLg.Text = preset.Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); - - var index = -1; - - for (var i = 0; i < cbxSamsungPresetDevice.Items.Count; i++) - { - var pnpDev = (SamsungDevice)cbxSamsungPresetDevice.Items[i]; - if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase)) - { - index = i; - break; - } - } - - cbxSamsungPresetDevice.SelectedIndex = index; - - var trigger = preset.Triggers.FirstOrDefault(); - - FormUtils.SetComboBoxEnumIndex(cbxSamsungPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); - - edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); - edtSamsungPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; - - edtSamsungPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; - edtSamsungPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; - edtSamsungPresetConnectedDisplaysRegex.Text = trigger?.ConnectedDisplaysRegex ?? string.Empty; - } - else - { - edtNameLg.Text = string.Empty; - chkSamsungQuickAccess.Checked = false; - edtSamsungPresetDescription.Text = string.Empty; - cbxSamsungApps.SelectedIndex = -1; - edtShortcutLg.Text = string.Empty; - edtStepsLg.Text = string.Empty; - cbxSamsungPresetDevice.SelectedIndex = -1; - cbxSamsungPresetTrigger.SelectedIndex = -1; - edtSamsungPresetTriggerConditions.Text = string.Empty; - edtSamsungPresetTriggerConditions.Tag = 0; - edtSamsungPresetIncludedProcesses.Text = string.Empty; - edtSamsungPresetExcludedProcesses.Text = string.Empty; - edtSamsungPresetConnectedDisplaysRegex.Text = string.Empty; - } - } - - private async void btnApplyLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedPreset(); - - await _samsungService.ApplyPresetUi(preset); - } - - private void btnSetShortcutLg_Click(object sender, EventArgs e) - { - var shortcut = edtShortcutLg.Text.Trim(); - if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) - { - return; - } - - var preset = GetSelectedPreset(); - - var name = edtNameLg.Text.Trim(); - - if (name.Length == 0 || _samsungService.GetPresets().Any(x => x.id != preset.id && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - MessageForms.WarningOk("The name can not be empty and must be unique."); - return; - } - - var clear = !string.IsNullOrEmpty(preset.shortcut); - - preset.name = name; - preset.ShowInQuickAccess = chkSamsungQuickAccess.Checked; - preset.Description = edtSamsungPresetDescription.Text.Trim(); - - if (cbxSamsungPresetDevice.SelectedIndex == -1) - { - preset.DeviceMacAddress = string.Empty; - } - else - { - var device = (SamsungDevice)cbxSamsungPresetDevice.SelectedItem; - preset.DeviceMacAddress = device.MacAddress; - } - - if (cbxSamsungApps.SelectedIndex == -1) - { - preset.AppId = string.Empty; - } - else - { - var app = (SamsungApp)cbxSamsungApps.SelectedItem; - preset.AppId = app.AppId; - } - - var triggerType = FormUtils.GetComboBoxEnumItem(cbxSamsungPresetTrigger); - preset.UpdateTrigger(triggerType, - (PresetConditionType)edtSamsungPresetTriggerConditions.Tag, - edtSamsungPresetIncludedProcesses.Text, - edtSamsungPresetExcludedProcesses.Text, - edtSamsungPresetConnectedDisplaysRegex.Text); - - preset.shortcut = shortcut; - - var text = edtStepsLg.Text; - - Utils.ParseWords(preset.Steps, text); - - AddOrUpdateItem(); - - _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); - } - - public void UpdateInfo() - { - if (_samsungService == null) - { - return; - } - - //if (scSamsungController.Panel2.Controls.Count == 0) - //{ - // var rcPanel = new RemoteControlPanel(_samsungService, _samsungService.GetRemoteControlButtons()); - // rcPanel.Parent = scSamsungController.Panel2; - // rcPanel.Dock = DockStyle.Fill; - - // if (DarkModeUtils.UseDarkMode) - // { - // DarkModeUtils.SetControlTheme(rcPanel); - // } - //} - //chkSamsungRemoteControlShow.Checked = _samsungService.Config.ShowRemoteControl; - //scSamsungController.Panel2Collapsed = !_samsungService.Config.ShowRemoteControl; - - FormUtils.BuildComboBox(cbxSamsungPresetTrigger, PresetTriggerType.Reserved5); - - if (!string.IsNullOrEmpty(_tabMessage)) - { - MessageForms.WarningOk(_tabMessage); - _tabMessage = null; - } - } - - private void FillSamsungDevices() - { - var devices = _samsungService.Devices; - - if (devices == null) - { - return; - } - - cbxSamsungDevices.Items.Clear(); - cbxSamsungDevices.Items.AddRange(devices.ToArray()); - - cbxSamsungPresetDevice.Items.Clear(); - cbxSamsungPresetDevice.Items.AddRange(devices.ToArray()); - var globalDevice = new SamsungDevice("Globally selected device", string.Empty, string.Empty, true, true); - cbxSamsungPresetDevice.Items.Insert(0, globalDevice); - - var device = _samsungService.SelectedDevice; - - if (device != null) - { - cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(device); - } - - if (!devices.Any()) - { - var message = "It seems there's no Samsung TV available! Please make sure it's connected to the same network as this PC."; - - if (Visible) - { - MessageForms.WarningOk(message); - } - else - { - _tabMessage = message; - } - } - - //if (cbxSamsungApps.Items.Count == 0 && device != null) - //{ - // _samsungService.RefreshAppsAsync().ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillApps(false))); - //} - - btnSamsungDeviceConvertToCustom.Enabled = devices.Any(); - - cbxSamsungDevices_SelectedIndexChanged(cbxSamsungDevices, null); - - SetSamsungDevicePowerOptions(); - } - - private void SetSamsungDevicePowerOptions() - { - var device = _samsungService.SelectedDevice; - clbLgPower.Enabled = device != null; - - _disableEvents = true; - try - { - clbLgPower.SetItemChecked(0, device?.Options.PowerOnAfterStartup ?? false); - clbLgPower.SetItemChecked(1, device?.Options.PowerOnAfterResume ?? false); - clbLgPower.SetItemChecked(2, device?.Options.PowerOffOnShutdown ?? false); - clbLgPower.SetItemChecked(3, device?.Options.PowerOffOnStandby ?? false); - clbLgPower.SetItemChecked(4, device?.Options.PowerOffOnScreenSaver ?? false); - clbLgPower.SetItemChecked(5, device?.Options.PowerOnAfterScreenSaver ?? false); - clbLgPower.SetItemChecked(6, device?.Options.PowerOnAfterManualPowerOff ?? false); - clbLgPower.SetItemChecked(7, device?.Options.TriggersEnabled ?? true); - clbLgPower.SetItemChecked(8, device?.Options.PowerOffByWindows ?? false); - clbLgPower.SetItemChecked(9, device?.Options.PowerOnByWindows ?? false); - clbLgPower.SetItemChecked(10, device?.Options.UseSecureConnection ?? false); - } - finally - { - _disableEvents = false; - } - } - - private void FillApps(bool forced) - { - var apps = _samsungService?.GetApps(); - if (forced && (apps == null || !apps.Any())) - { - MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); - return; - } - InitApps(); - } - - private void InitApps() - { - var apps = _samsungService?.GetApps(); - - cbxSamsungApps.Items.Clear(); - if (apps != null) - { - cbxSamsungApps.Items.AddRange(apps.ToArray()); - } - - for (var i = 0; i < lvSamsungPresets.Items.Count; i++) - { - var item = lvSamsungPresets.Items[i]; - AddOrUpdateItem((SamsungPreset)item.Tag, item); - } - - var preset = GetSelectedPreset(); - if (preset != null) - { - cbxSamsungApps.SelectedIndex = apps == null ? -1 : apps.FindIndex(x => x.AppId.Equals(preset.AppId)); - } - } - - private void btnDeleteLg_Click(object sender, EventArgs e) - { - var preset = GetSelectedPreset(); - - if (preset == null) - { - return; - } - - if (!string.IsNullOrEmpty(preset.shortcut)) - { - WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); - } - - _samsungService.GetPresets().Remove(preset); - - var item = lvSamsungPresets.SelectedItems[0]; - lvSamsungPresets.Items.Remove(item); - } - - private void btnAddLg_Click(object sender, EventArgs e) - { - AddOrUpdateItem(_samsungService.CreateNewPreset()); - } - - private void btnSamsungRefreshApps_Click(object sender, EventArgs e) - { - //_samsungService.RefreshApps(true).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillLgApps(true))); - } - - private void cbxSamsungDevices_SelectedIndexChanged(object sender, EventArgs e) - { - cbxSamsungPcHdmiPort.Enabled = cbxSamsungDevices.SelectedIndex > -1; - if (cbxSamsungDevices.SelectedIndex == -1) - { - _samsungService.SelectedDevice = null; - cbxSamsungPcHdmiPort.SelectedIndex = 0; - } - else - { - _samsungService.SelectedDevice = (SamsungDevice)cbxSamsungDevices.SelectedItem; - //cbxSamsungPcHdmiPort.SelectedIndex = _samsungService.SelectedDevice.HDMIPortNumber; - } - chkSamsungRemoteControlShow.Enabled = _samsungService.SelectedDevice != null; - btnSamsungRemoveDevice.Enabled = _samsungService.SelectedDevice != null && _samsungService.SelectedDevice.IsCustom; - btnSamsungDeviceConvertToCustom.Enabled = _samsungService.SelectedDevice != null && !_samsungService.SelectedDevice.IsCustom; - - SetSamsungDevicePowerOptions(); - } - - private void lvSamsungPresets_DoubleClick(object sender, EventArgs e) - { - ApplySelectedPreset(); - } - - private async void ApplySelectedPreset() - { - var preset = GetSelectedPreset(); - await _samsungService.ApplyPresetUi(preset); - } - - private void btnSamsungAddButton_Click(object sender, EventArgs e) - { - mnuLgButtons.ShowCustom(btnSamsungAddButton); - } - - private void miLgAddButton_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - - FormUtils.AddStepToTextBox(edtStepsLg, item.Text); - } - - private void miLgAddAction_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var action = item.Tag as SamsungDevice.InvokableAction; - - var value = ShowActionForm(action); - - var text = action.Name; - - if (!string.IsNullOrEmpty(value)) - { - text += $"({value})"; - } - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private string ShowActionForm(SamsungDevice.InvokableAction action) - { - var text = action.Name; - var title = action.Title ?? action.Name; - - var value = string.Empty; - - if (action.EnumType == null) - { - if (action.MinValue != action.MaxValue) - { - List fields = new(); - - if (action.NumberOfValues == 1) - { - fields.Add(new FieldDefinition - { - Label = "Enter desired " + title, - FieldType = FieldType.Numeric, - MinValue = action.MinValue, - MaxValue = action.MaxValue, - }); - } - else - { - var array = Enumerable.Range(0, action.NumberOfValues); - - fields.AddRange(array.Select(i => new FieldDefinition - { - Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()), - FieldType = FieldType.Numeric, - MinValue = action.MinValue, - MaxValue = action.MaxValue, - })); - } - - var values = MessageForms.ShowDialog($"Enter value for {title}", fields); - - if (!values.Any()) - { - return null; - } - - if (values.Count > 1) - { - value = string.Join("; ", values.Select(v => v.Value)); - } - else - { - value = values.First().Value.ToString(); - } - } - } - else - { - var values = MessageForms.ShowDialog("Choose value", new[] { - new FieldDefinition - { - Label = "Choose desired " + title, - FieldType = FieldType.DropDown, - Values = Utils.GetDescriptions(action.EnumType, replaceUnderscore: true), - } - }); - - if (!values.Any()) - { - return null; - } - - value = values.First().Value.ToString(); - var enumName = Utils.GetEnumNameByDescription(action.EnumType, value); - if (enumName != null) - { - value = enumName; - } - } - - return value; - } - - private void miLgAddNvPreset_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var preset = item.Tag as NvPreset; - - var text = $"NvPreset({preset.name})"; - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private void miLgAddAmdPreset_Click(object sender, EventArgs e) - { - var item = sender as ToolStripItem; - var preset = item.Tag as AmdPreset; - - var text = $"AmdPreset({preset.name})"; - - FormUtils.AddStepToTextBox(edtStepsLg, text); - } - - private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) - { - if (_disableEvents) - { - return; - } - - var device = _samsungService?.SelectedDevice; - if (device == null) - { - return; - } - - if (e.Index is 0 or 1 or 5 or 8 && !(device.Options.PowerOnAfterResume || device.Options.PowerOnAfterStartup || device.Options.PowerOnAfterScreenSaver || device.Options.PowerOnByWindows)) - { - MessageForms.InfoOk( + partial class SamsungPanel : UserControl, IModulePanel + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private Config _config; + private SamsungService _samsungService; + private readonly GlobalContext _globalContext; + private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher; + private readonly ServiceManager _serviceManager; + private string _tabMessage; + private bool _disableEvents = false; + + public SamsungPanel(SamsungService samsungService, GlobalContext globalContext, KeyboardShortcutDispatcher keyboardShortcutDispatcher, ServiceManager serviceManager) + { + _samsungService = samsungService; + _globalContext = globalContext; + _keyboardShortcutDispatcher = keyboardShortcutDispatcher; + _serviceManager = serviceManager; + _config = Shared.Common.GlobalContext.CurrentContext.Config; + + InitializeComponent(); + + scSamsungController.Panel2Collapsed = true; + + FillPresets(); + + FormUtils.InitSortState(lvSamsungPresets, _config.SamsungPresetsSortState); + + //_samsungService.AfterApplyPreset += SamsungServiceAfterApplyPreset; + //_samsungService.SelectedDeviceChangedEvent += _samsungService_SelectedDeviceChangedEvent; + + edtSamsungDeviceFilter.Text = _samsungService.Config.DeviceSearchKey; + + var values = Enum.GetValues(typeof(ButtonType)); + foreach (var button in values) + { + var text = button.ToString(); + if (text[0] == '_') + { + text = text.Substring(1); + } + + var item = mnuLgRcButtons.DropDownItems.AddCustom(text); + item.Click += miLgAddButton_Click; + } + } + + public void Init() + { + _ = Handle; + + if (_samsungService.Devices == null) + { + RefreshSamsungDevices(); + return; + } + + _ = AfterSamsungServiceRefreshDevices(); + } + + private void _samsungService_SelectedDeviceChangedEvent(object sender, EventArgs e) + { + FormUtils.BeginInvokeCheck(this, () => SetSamsungDevicesSelectedIndex(sender)); + } + + private void SetSamsungDevicesSelectedIndex(object sender) + { + if (sender == null) + { + cbxSamsungDevices.SelectedIndex = -1; + return; + } + + cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(sender); + } + + private async Task AfterSamsungServiceRefreshDevices() + { + FillSamsungDevices(); + + var startUpParams = Shared.Common.GlobalContext.CurrentContext.StartUpParams; + + if (startUpParams.ExecuteSamsungPreset) + { + await _samsungService.ApplyPreset(startUpParams.SamsungPresetName); + return; + } + } + + internal void Save() + { + FormUtils.SaveSortState(lvSamsungPresets.ListViewItemSorter, _config.SamsungPresetsSortState); + } + + private void edtShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); + } + + private void edtShortcut_KeyUp(object sender, KeyEventArgs e) + { + KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); + } + + private void FillPresets() + { + FormUtils.InitListView(lvSamsungPresets, SamsungPreset.GetColumnNames()); + + foreach (var preset in _samsungService.GetPresets()) + { + AddOrUpdateItem(preset); + } + } + + private void AddOrUpdateItem(SamsungPreset preset = null, ListViewItem specItem = null) + { + ServiceFormUtils.AddOrUpdateListItem(lvSamsungPresets, _samsungService.GetPresets(), _config, preset, specItem); + } + + private void btnCloneLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedPreset(); + + var newPreset = preset.Clone(); + AddOrUpdateItem(newPreset); + } + + private SamsungPreset GetSelectedPreset() + { + if (lvSamsungPresets.SelectedItems.Count > 0) + { + var item = lvSamsungPresets.SelectedItems[0]; + return (SamsungPreset)item.Tag; + } + else + { + return null; + } + } + + private void lvSamsungPresets_SelectedIndexChanged(object sender, EventArgs e) + { + var enabled = lvSamsungPresets.SelectedItems.Count > 0; + btnApplyLg.Enabled = enabled; + btnCloneLg.Enabled = enabled; + edtNameLg.Enabled = enabled; + chkSamsungQuickAccess.Enabled = enabled; + edtSamsungPresetDescription.Enabled = enabled; + cbxSamsungPresetDevice.Enabled = enabled; + edtShortcutLg.Enabled = enabled; + btnSetShortcutLg.Enabled = enabled; + edtStepsLg.Enabled = enabled; + btnSamsungAddButton.Enabled = enabled; + cbxSamsungApps.Enabled = enabled; + btnSamsungRefreshApps.Enabled = enabled; + btnDeleteLg.Enabled = enabled; + cbxSamsungPresetTrigger.Enabled = enabled; + edtSamsungPresetTriggerConditions.Enabled = enabled; + btnSamsungPresetEditTriggerConditions.Enabled = enabled; + edtSamsungPresetIncludedProcesses.Enabled = enabled; + edtSamsungPresetExcludedProcesses.Enabled = enabled; + edtSamsungPresetConnectedDisplaysRegex.Enabled = enabled; + + var preset = GetSelectedPreset(); + + if (preset != null) + { + edtNameLg.Text = preset.name; + chkSamsungQuickAccess.Checked = preset.ShowInQuickAccess; + edtSamsungPresetDescription.Text = preset.Description; + var lgApps = _samsungService?.GetApps(); + cbxSamsungApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.AppId.Equals(preset.AppId)); + edtShortcutLg.Text = preset.shortcut; + edtStepsLg.Text = preset.Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b); + + var index = -1; + + for (var i = 0; i < cbxSamsungPresetDevice.Items.Count; i++) + { + var pnpDev = (SamsungDevice)cbxSamsungPresetDevice.Items[i]; + if ((string.IsNullOrEmpty(preset.DeviceMacAddress) && string.IsNullOrEmpty(pnpDev.MacAddress)) || pnpDev.MacAddress.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase)) + { + index = i; + break; + } + } + + cbxSamsungPresetDevice.SelectedIndex = index; + + var trigger = preset.Triggers.FirstOrDefault(); + + FormUtils.SetComboBoxEnumIndex(cbxSamsungPresetTrigger, (int)(trigger?.Trigger ?? PresetTriggerType.None)); + + edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions(trigger != null ? (int)trigger.Conditions : 0).Join(", "); + edtSamsungPresetTriggerConditions.Tag = trigger != null ? (int)trigger.Conditions : 0; + + edtSamsungPresetIncludedProcesses.Text = trigger?.IncludedProcesses?.Join(", ") ?? string.Empty; + edtSamsungPresetExcludedProcesses.Text = trigger?.ExcludedProcesses?.Join(", ") ?? string.Empty; + edtSamsungPresetConnectedDisplaysRegex.Text = trigger?.ConnectedDisplaysRegex ?? string.Empty; + } + else + { + edtNameLg.Text = string.Empty; + chkSamsungQuickAccess.Checked = false; + edtSamsungPresetDescription.Text = string.Empty; + cbxSamsungApps.SelectedIndex = -1; + edtShortcutLg.Text = string.Empty; + edtStepsLg.Text = string.Empty; + cbxSamsungPresetDevice.SelectedIndex = -1; + cbxSamsungPresetTrigger.SelectedIndex = -1; + edtSamsungPresetTriggerConditions.Text = string.Empty; + edtSamsungPresetTriggerConditions.Tag = 0; + edtSamsungPresetIncludedProcesses.Text = string.Empty; + edtSamsungPresetExcludedProcesses.Text = string.Empty; + edtSamsungPresetConnectedDisplaysRegex.Text = string.Empty; + } + } + + private async void btnApplyLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedPreset(); + + await _samsungService.ApplyPresetUi(preset); + } + + private void btnSetShortcutLg_Click(object sender, EventArgs e) + { + var shortcut = edtShortcutLg.Text.Trim(); + if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut)) + { + return; + } + + var preset = GetSelectedPreset(); + + var name = edtNameLg.Text.Trim(); + + if (name.Length == 0 || _samsungService.GetPresets().Any(x => x.id != preset.id && x.name.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + MessageForms.WarningOk("The name can not be empty and must be unique."); + return; + } + + var clear = !string.IsNullOrEmpty(preset.shortcut); + + preset.name = name; + preset.ShowInQuickAccess = chkSamsungQuickAccess.Checked; + preset.Description = edtSamsungPresetDescription.Text.Trim(); + + if (cbxSamsungPresetDevice.SelectedIndex == -1) + { + preset.DeviceMacAddress = string.Empty; + } + else + { + var device = (SamsungDevice)cbxSamsungPresetDevice.SelectedItem; + preset.DeviceMacAddress = device.MacAddress; + } + + if (cbxSamsungApps.SelectedIndex == -1) + { + preset.AppId = string.Empty; + } + else + { + var app = (SamsungApp)cbxSamsungApps.SelectedItem; + preset.AppId = app.AppId; + } + + var triggerType = FormUtils.GetComboBoxEnumItem(cbxSamsungPresetTrigger); + preset.UpdateTrigger(triggerType, + (PresetConditionType)edtSamsungPresetTriggerConditions.Tag, + edtSamsungPresetIncludedProcesses.Text, + edtSamsungPresetExcludedProcesses.Text, + edtSamsungPresetConnectedDisplaysRegex.Text); + + preset.shortcut = shortcut; + + var text = edtStepsLg.Text; + + Utils.ParseWords(preset.Steps, text); + + AddOrUpdateItem(); + + _keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut); + } + + public void UpdateInfo() + { + if (_samsungService == null) + { + return; + } + + //if (scSamsungController.Panel2.Controls.Count == 0) + //{ + // var rcPanel = new RemoteControlPanel(_samsungService, _samsungService.GetRemoteControlButtons()); + // rcPanel.Parent = scSamsungController.Panel2; + // rcPanel.Dock = DockStyle.Fill; + + // if (DarkModeUtils.UseDarkMode) + // { + // DarkModeUtils.SetControlTheme(rcPanel); + // } + //} + //chkSamsungRemoteControlShow.Checked = _samsungService.Config.ShowRemoteControl; + //scSamsungController.Panel2Collapsed = !_samsungService.Config.ShowRemoteControl; + + FormUtils.BuildComboBox(cbxSamsungPresetTrigger, PresetTriggerType.Reserved5); + + if (!string.IsNullOrEmpty(_tabMessage)) + { + MessageForms.WarningOk(_tabMessage); + _tabMessage = null; + } + } + + private void FillSamsungDevices() + { + var devices = _samsungService.Devices; + + if (devices == null) + { + return; + } + + cbxSamsungDevices.Items.Clear(); + cbxSamsungDevices.Items.AddRange(devices.ToArray()); + + cbxSamsungPresetDevice.Items.Clear(); + cbxSamsungPresetDevice.Items.AddRange(devices.ToArray()); + var globalDevice = new SamsungDevice("Globally selected device", string.Empty, string.Empty, true, true); + cbxSamsungPresetDevice.Items.Insert(0, globalDevice); + + var device = _samsungService.SelectedDevice; + + if (device != null) + { + cbxSamsungDevices.SelectedIndex = cbxSamsungDevices.Items.IndexOf(device); + } + + if (!devices.Any()) + { + var message = "It seems there's no Samsung TV available! Please make sure it's connected to the same network as this PC."; + + if (Visible) + { + MessageForms.WarningOk(message); + } + else + { + _tabMessage = message; + } + } + + //if (cbxSamsungApps.Items.Count == 0 && device != null) + //{ + // _samsungService.RefreshAppsAsync().ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillApps(false))); + //} + + btnSamsungDeviceConvertToCustom.Enabled = devices.Any(); + + cbxSamsungDevices_SelectedIndexChanged(cbxSamsungDevices, null); + + SetSamsungDevicePowerOptions(); + } + + private void SetSamsungDevicePowerOptions() + { + var device = _samsungService.SelectedDevice; + clbLgPower.Enabled = device != null; + + _disableEvents = true; + try + { + clbLgPower.SetItemChecked(0, device?.Options.PowerOnAfterStartup ?? false); + clbLgPower.SetItemChecked(1, device?.Options.PowerOnAfterResume ?? false); + clbLgPower.SetItemChecked(2, device?.Options.PowerOffOnShutdown ?? false); + clbLgPower.SetItemChecked(3, device?.Options.PowerOffOnStandby ?? false); + clbLgPower.SetItemChecked(4, device?.Options.PowerOffOnScreenSaver ?? false); + clbLgPower.SetItemChecked(5, device?.Options.PowerOnAfterScreenSaver ?? false); + clbLgPower.SetItemChecked(6, device?.Options.PowerOnAfterManualPowerOff ?? false); + clbLgPower.SetItemChecked(7, device?.Options.TriggersEnabled ?? true); + clbLgPower.SetItemChecked(8, device?.Options.PowerOffByWindows ?? false); + clbLgPower.SetItemChecked(9, device?.Options.PowerOnByWindows ?? false); + clbLgPower.SetItemChecked(10, device?.Options.PowerOffAfterSessionLock ?? false); + clbLgPower.SetItemChecked(11, device?.Options.PowerOnAfterSessionUnlock ?? false); + clbLgPower.SetItemChecked(12, device?.Options.UseSecureConnection ?? false); + } + finally + { + _disableEvents = false; + } + } + + private void FillApps(bool forced) + { + var apps = _samsungService?.GetApps(); + if (forced && (apps == null || !apps.Any())) + { + MessageForms.WarningOk("Could not refresh the apps. Check the log for details."); + return; + } + InitApps(); + } + + private void InitApps() + { + var apps = _samsungService?.GetApps(); + + cbxSamsungApps.Items.Clear(); + if (apps != null) + { + cbxSamsungApps.Items.AddRange(apps.ToArray()); + } + + for (var i = 0; i < lvSamsungPresets.Items.Count; i++) + { + var item = lvSamsungPresets.Items[i]; + AddOrUpdateItem((SamsungPreset)item.Tag, item); + } + + var preset = GetSelectedPreset(); + if (preset != null) + { + cbxSamsungApps.SelectedIndex = apps == null ? -1 : apps.FindIndex(x => x.AppId.Equals(preset.AppId)); + } + } + + private void btnDeleteLg_Click(object sender, EventArgs e) + { + var preset = GetSelectedPreset(); + + if (preset == null) + { + return; + } + + if (!string.IsNullOrEmpty(preset.shortcut)) + { + WinApi.UnregisterHotKey(_globalContext.MainHandle, preset.id); + } + + _samsungService.GetPresets().Remove(preset); + + var item = lvSamsungPresets.SelectedItems[0]; + lvSamsungPresets.Items.Remove(item); + } + + private void btnAddLg_Click(object sender, EventArgs e) + { + AddOrUpdateItem(_samsungService.CreateNewPreset()); + } + + private void btnSamsungRefreshApps_Click(object sender, EventArgs e) + { + //_samsungService.RefreshApps(true).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillLgApps(true))); + } + + private void cbxSamsungDevices_SelectedIndexChanged(object sender, EventArgs e) + { + cbxSamsungPcHdmiPort.Enabled = cbxSamsungDevices.SelectedIndex > -1; + if (cbxSamsungDevices.SelectedIndex == -1) + { + _samsungService.SelectedDevice = null; + cbxSamsungPcHdmiPort.SelectedIndex = 0; + } + else + { + _samsungService.SelectedDevice = (SamsungDevice)cbxSamsungDevices.SelectedItem; + //cbxSamsungPcHdmiPort.SelectedIndex = _samsungService.SelectedDevice.HDMIPortNumber; + } + chkSamsungRemoteControlShow.Enabled = _samsungService.SelectedDevice != null; + btnSamsungRemoveDevice.Enabled = _samsungService.SelectedDevice != null && _samsungService.SelectedDevice.IsCustom; + btnSamsungDeviceConvertToCustom.Enabled = _samsungService.SelectedDevice != null && !_samsungService.SelectedDevice.IsCustom; + + SetSamsungDevicePowerOptions(); + } + + private void lvSamsungPresets_DoubleClick(object sender, EventArgs e) + { + ApplySelectedPreset(); + } + + private async void ApplySelectedPreset() + { + var preset = GetSelectedPreset(); + await _samsungService.ApplyPresetUi(preset); + } + + private void btnSamsungAddButton_Click(object sender, EventArgs e) + { + mnuLgButtons.ShowCustom(btnSamsungAddButton); + } + + private void miLgAddButton_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + + FormUtils.AddStepToTextBox(edtStepsLg, item.Text); + } + + private void miLgAddAction_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var action = item.Tag as SamsungDevice.InvokableAction; + + var value = ShowActionForm(action); + + var text = action.Name; + + if (!string.IsNullOrEmpty(value)) + { + text += $"({value})"; + } + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private string ShowActionForm(SamsungDevice.InvokableAction action) + { + var text = action.Name; + var title = action.Title ?? action.Name; + + var value = string.Empty; + + if (action.EnumType == null) + { + if (action.MinValue != action.MaxValue) + { + List fields = new(); + + if (action.NumberOfValues == 1) + { + fields.Add(new FieldDefinition + { + Label = "Enter desired " + title, + FieldType = FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + }); + } + else + { + var array = Enumerable.Range(0, action.NumberOfValues); + + fields.AddRange(array.Select(i => new FieldDefinition + { + Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()), + FieldType = FieldType.Numeric, + MinValue = action.MinValue, + MaxValue = action.MaxValue, + })); + } + + var values = MessageForms.ShowDialog($"Enter value for {title}", fields); + + if (!values.Any()) + { + return null; + } + + if (values.Count > 1) + { + value = string.Join("; ", values.Select(v => v.Value)); + } + else + { + value = values.First().Value.ToString(); + } + } + } + else + { + var values = MessageForms.ShowDialog("Choose value", new[] { + new FieldDefinition + { + Label = "Choose desired " + title, + FieldType = FieldType.DropDown, + Values = Utils.GetDescriptions(action.EnumType, replaceUnderscore: true), + } + }); + + if (!values.Any()) + { + return null; + } + + value = values.First().Value.ToString(); + var enumName = Utils.GetEnumNameByDescription(action.EnumType, value); + if (enumName != null) + { + value = enumName; + } + } + + return value; + } + + private void miLgAddNvPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as NvPreset; + + var text = $"NvPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void miLgAddAmdPreset_Click(object sender, EventArgs e) + { + var item = sender as ToolStripItem; + var preset = item.Tag as AmdPreset; + + var text = $"AmdPreset({preset.name})"; + + FormUtils.AddStepToTextBox(edtStepsLg, text); + } + + private void clbLgPower_ItemCheck(object sender, ItemCheckEventArgs e) + { + if (_disableEvents) + { + return; + } + + var device = _samsungService?.SelectedDevice; + if (device == null) + { + return; + } + + if (e.Index is 0 or 1 or 5 or 8 && !(device.Options.PowerOnAfterResume || device.Options.PowerOnAfterStartup || device.Options.PowerOnAfterScreenSaver || device.Options.PowerOnByWindows || device.Options.PowerOnAfterSessionUnlock)) + { + MessageForms.InfoOk( @"Be sure to activate the following setting on the TV, or the app will not be able to wake the TV: Connection > Network > Expert Settings: @@ -658,280 +660,282 @@ Connection > Network > Expert Settings: 2) IP Remote Use 'Settings > Test power off/on' to test this functionality." - ); - } + ); + } - FormUtils.BeginInvokeCheck(this, () => - { - device.Options.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); - device.Options.PowerOnAfterResume = clbLgPower.GetItemChecked(1); - device.Options.PowerOffOnShutdown = clbLgPower.GetItemChecked(2); - device.Options.PowerOffOnStandby = clbLgPower.GetItemChecked(3); - device.Options.PowerOffOnScreenSaver = clbLgPower.GetItemChecked(4); - device.Options.PowerOnAfterScreenSaver = clbLgPower.GetItemChecked(5); - device.Options.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(6); - device.Options.TriggersEnabled = clbLgPower.GetItemChecked(7); - device.Options.PowerOffByWindows = clbLgPower.GetItemChecked(8); - device.Options.PowerOnByWindows = clbLgPower.GetItemChecked(9); - device.Options.UseSecureConnection = clbLgPower.GetItemChecked(10); + FormUtils.BeginInvokeCheck(this, () => + { + device.Options.PowerOnAfterStartup = clbLgPower.GetItemChecked(0); + device.Options.PowerOnAfterResume = clbLgPower.GetItemChecked(1); + device.Options.PowerOffOnShutdown = clbLgPower.GetItemChecked(2); + device.Options.PowerOffOnStandby = clbLgPower.GetItemChecked(3); + device.Options.PowerOffOnScreenSaver = clbLgPower.GetItemChecked(4); + device.Options.PowerOnAfterScreenSaver = clbLgPower.GetItemChecked(5); + device.Options.PowerOnAfterManualPowerOff = clbLgPower.GetItemChecked(6); + device.Options.TriggersEnabled = clbLgPower.GetItemChecked(7); + device.Options.PowerOffByWindows = clbLgPower.GetItemChecked(8); + device.Options.PowerOnByWindows = clbLgPower.GetItemChecked(9); + device.Options.PowerOffAfterSessionLock = clbLgPower.GetItemChecked(10); + device.Options.PowerOnAfterSessionUnlock = clbLgPower.GetItemChecked(11); + device.Options.UseSecureConnection = clbLgPower.GetItemChecked(12); - if (e.Index == 10) - { - RefreshSamsungDevices(); - } - }); - } + if (e.Index == 12) + { + RefreshSamsungDevices(); + } + }); + } - private void edtSamsungDeviceFilter_TextChanged(object sender, EventArgs e) - { - if (_samsungService != null) - { - _samsungService.Config.DeviceSearchKey = edtSamsungDeviceFilter.Text; - } - } + private void edtSamsungDeviceFilter_TextChanged(object sender, EventArgs e) + { + if (_samsungService != null) + { + _samsungService.Config.DeviceSearchKey = edtSamsungDeviceFilter.Text; + } + } - private void btnSamsungDeviceFilterRefresh_Click(object sender, EventArgs e) - { - RefreshSamsungDevices(); - } + private void btnSamsungDeviceFilterRefresh_Click(object sender, EventArgs e) + { + RefreshSamsungDevices(); + } - private void RefreshSamsungDevices() - { - _samsungService.RefreshDevices(false).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillSamsungDevices())); - } + private void RefreshSamsungDevices() + { + _samsungService.RefreshDevices(false).ContinueWith((task) => FormUtils.BeginInvokeCheck(this, () => FillSamsungDevices())); + } - private void edtShortcutLg_TextChanged(object sender, EventArgs e) - { - var text = edtShortcutLg.Text; + private void edtShortcutLg_TextChanged(object sender, EventArgs e) + { + var text = edtShortcutLg.Text; - var preset = GetSelectedPreset(); + var preset = GetSelectedPreset(); - ServiceFormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); - } + ServiceFormUtils.UpdateShortcutTextBox(edtShortcutLg, preset); + } - private async void btnSamsungAddDevice_Click(object sender, EventArgs e) - { - var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); - if (values.Any()) - { - var device = new SamsungDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); + private async void btnSamsungAddDevice_Click(object sender, EventArgs e) + { + var values = MessageForms.ShowDialog("Add tv", new[] { "Name", "Ip-address", "MAC-address" }, ValidateAddDevice); + if (values.Any()) + { + var device = new SamsungDevice(values[0].Value.ToString(), values[1].Value.ToString(), values[2].Value.ToString()); - var form = MessageForms.ShowProgress("Connecting to device..."); + var form = MessageForms.ShowProgress("Connecting to device..."); - var result = false; - try - { - result = await device.TestConnectionAsync(); - } - finally - { - form.Close(); - } + var result = false; + try + { + result = await device.TestConnectionAsync(); + } + finally + { + form.Close(); + } - if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes) - { - return; - } + if (!result && MessageForms.QuestionYesNo("Unable to connect to the device. Are you sure you want to add it?") != DialogResult.Yes) + { + return; + } - _samsungService.AddCustomDevice(device); + _samsungService.AddCustomDevice(device); - FillSamsungDevices(); - } - } + FillSamsungDevices(); + } + } - private string ValidateAddDevice(IEnumerable values) - { - if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) - { - return "Please fill in all the fields"; - } + private string ValidateAddDevice(IEnumerable values) + { + if (values.Any(v => string.IsNullOrEmpty(v.Value?.ToString()))) + { + return "Please fill in all the fields"; + } - return null; - } + return null; + } - private void btnSamsungRemoveDevice_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes) - { - return; - } + private void btnSamsungRemoveDevice_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo("Are you sure you want to remove this device?") != DialogResult.Yes) + { + return; + } - var device = _samsungService.SelectedDevice; + var device = _samsungService.SelectedDevice; - if (device != null) - { - _samsungService.RemoveCustomDevice(device); + if (device != null) + { + _samsungService.RemoveCustomDevice(device); - FillSamsungDevices(); - } - } + FillSamsungDevices(); + } + } - private void mnuLgButtons_Opening(object sender, CancelEventArgs e) - { - mnuLgActions.DropDownItems.Clear(); + private void mnuLgButtons_Opening(object sender, CancelEventArgs e) + { + mnuLgActions.DropDownItems.Clear(); - var preset = GetSelectedPreset(); + var preset = GetSelectedPreset(); - if (preset == null) - { - return; - } + if (preset == null) + { + return; + } - var device = _samsungService.GetPresetDevice(preset); + var device = _samsungService.GetPresetDevice(preset); - BuildActionMenu(device, mnuLgActions.DropDownItems, mnuLgActions.Name, miLgAddAction_Click, _samsungService.Config.ShowAdvancedActions); - ServiceFormUtils.BuildServicePresetsMenu(mnuLgNvPresets, _serviceManager.NvService, "NVIDIA", miLgAddNvPreset_Click); - ServiceFormUtils.BuildServicePresetsMenu(mnuLgAmdPresets, _serviceManager.AmdService, "AMD", miLgAddAmdPreset_Click); - } + BuildActionMenu(device, mnuLgActions.DropDownItems, mnuLgActions.Name, miLgAddAction_Click, _samsungService.Config.ShowAdvancedActions); + ServiceFormUtils.BuildServicePresetsMenu(mnuLgNvPresets, _serviceManager.NvService, "NVIDIA", miLgAddNvPreset_Click); + ServiceFormUtils.BuildServicePresetsMenu(mnuLgAmdPresets, _serviceManager.AmdService, "AMD", miLgAddAmdPreset_Click); + } - private void btnSamsungDeviceConvertToCustom_Click(object sender, EventArgs e) - { - if (MessageForms.QuestionYesNo( + private void btnSamsungDeviceConvertToCustom_Click(object sender, EventArgs e) + { + if (MessageForms.QuestionYesNo( @"This will convert the automatically detected device to a custom variant. This means that the device will remain here even if it is not detected anymore. Do you want to continue?" - ) != DialogResult.Yes) - { - return; - } + ) != DialogResult.Yes) + { + return; + } - _samsungService.SelectedDevice.ConvertToCustom(); + _samsungService.SelectedDevice.ConvertToCustom(); - FillSamsungDevices(); - } + FillSamsungDevices(); + } - private void chkSamsungRemoteControlShow_CheckedChanged(object sender, EventArgs e) - { - //scSamsungController.Panel2Collapsed = !chkSamsungRemoteControlShow.Checked; - //_samsungService.Config.ShowRemoteControl = chkSamsungRemoteControlShow.Checked; - } + private void chkSamsungRemoteControlShow_CheckedChanged(object sender, EventArgs e) + { + //scSamsungController.Panel2Collapsed = !chkSamsungRemoteControlShow.Checked; + //_samsungService.Config.ShowRemoteControl = chkSamsungRemoteControlShow.Checked; + } - private void BuildActionMenu(SamsungDevice device, ToolStripItemCollection parent, string parentName, EventHandler clickEvent, bool showAdvanced = false) - { - if (device == null) - { - return; - } + private void BuildActionMenu(SamsungDevice device, ToolStripItemCollection parent, string parentName, EventHandler clickEvent, bool showAdvanced = false) + { + if (device == null) + { + return; + } - var actions = device.GetInvokableActions(showAdvanced); + var actions = device.GetInvokableActions(showAdvanced); - foreach (var action in actions) - { - var text = action.Title ?? action.Name; + foreach (var action in actions) + { + var text = action.Title ?? action.Name; - var item = parent.AddCustom(text); - item.Tag = action; - item.Click += clickEvent; + var item = parent.AddCustom(text); + item.Tag = action; + item.Click += clickEvent; - //var menu = FormUtils.BuildDropDownMenuEx(catMenuItem.DropDownItems, catMenuItem.Name, action.Title, action.EnumType, clickEvent, action, (int)action.MinValue, (int)action.MaxValue, action.NumberOfValues > 1); - } - } + //var menu = FormUtils.BuildDropDownMenuEx(catMenuItem.DropDownItems, catMenuItem.Name, action.Title, action.EnumType, clickEvent, action, (int)action.MinValue, (int)action.MaxValue, action.NumberOfValues > 1); + } + } - private void cbxSamsungApps_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == ((char)Keys.Back)) - { - cbxSamsungApps.SelectedIndex = -1; - } - } + private void cbxSamsungApps_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == ((char)Keys.Back)) + { + cbxSamsungApps.SelectedIndex = -1; + } + } - private void btnSamsungPresetEditTriggerConditions_Click(object sender, EventArgs e) - { - var preset = GetSelectedPreset(); + private void btnSamsungPresetEditTriggerConditions_Click(object sender, EventArgs e) + { + var preset = GetSelectedPreset(); - if (preset == null) - { - return; - } + if (preset == null) + { + return; + } - var dropDownValues = Utils.GetDescriptions(fromValue: 1); + var dropDownValues = Utils.GetDescriptions(fromValue: 1); - var values = MessageForms.ShowDialog("Set trigger conditions", new[] { - new FieldDefinition - { - Label = "Set desired trigger conditions", - FieldType = FieldType.Flags, - Values = dropDownValues, - Value = edtSamsungPresetTriggerConditions.Tag ?? 0 - } - }); + var values = MessageForms.ShowDialog("Set trigger conditions", new[] { + new FieldDefinition + { + Label = "Set desired trigger conditions", + FieldType = FieldType.Flags, + Values = dropDownValues, + Value = edtSamsungPresetTriggerConditions.Tag ?? 0 + } + }); - if (!values.Any()) - { - return; - } + if (!values.Any()) + { + return; + } - var value = (PresetConditionType)values.First().Value; - edtSamsungPresetTriggerConditions.Tag = value; - edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); - } + var value = (PresetConditionType)values.First().Value; + edtSamsungPresetTriggerConditions.Tag = value; + edtSamsungPresetTriggerConditions.Text = Utils.GetDescriptions((int)value).Join(", "); + } - private void edtSamsungGameBarShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } + private void edtSamsungGameBarShortcut_KeyDown(object sender, KeyEventArgs e) + { + ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); + } - private void edtSamsungGameBarShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } + private void edtSamsungGameBarShortcut_KeyUp(object sender, KeyEventArgs e) + { + KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); + } - private void btnSamsungDeviceOptionsHelp_Click(object sender, EventArgs e) - { - MessageForms.InfoOk( + private void btnSamsungDeviceOptionsHelp_Click(object sender, EventArgs e) + { + MessageForms.InfoOk( @"Notes: - power on after startup requires ""Automatically start after login"" - see Options - power on after resume from standby may need some retries for waking TV - see Options - power off on shutdown: because this app cannot detect a restart, restarting could also trigger this. Hold down Ctrl on restart to prevent power off. - Use Windows power settings: powering on/off of TV will follow Windows settings. If activated, it's better to disable all other power options except power off on shutdown. "); - } + } - private void cbxSamsungPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) - { - if (_samsungService?.SelectedDevice == null) - { - return; - } + private void cbxSamsungPcHdmiPort_SelectedIndexChanged(object sender, EventArgs e) + { + if (_samsungService?.SelectedDevice == null) + { + return; + } - //_samsungService.SelectedDevice.HDMIPortNumber = cbxSamsungPcHdmiPort.SelectedIndex; - } + //_samsungService.SelectedDevice.HDMIPortNumber = cbxSamsungPcHdmiPort.SelectedIndex; + } - private void lvSamsungPresets_ColumnClick(object sender, ColumnClickEventArgs e) - { - FormUtils.ListViewSort(sender, e); - } + private void lvSamsungPresets_ColumnClick(object sender, ColumnClickEventArgs e) + { + FormUtils.ListViewSort(sender, e); + } - private void mnuLgProgram_Click(object sender, EventArgs e) - { - var file = Utils.SelectFile(); + private void mnuLgProgram_Click(object sender, EventArgs e) + { + var file = Utils.SelectFile(); - if (file != null) - { - FormUtils.AddStepToTextBox(edtStepsLg, $"StartProgram({file.FullName})"); - } - } + if (file != null) + { + FormUtils.AddStepToTextBox(edtStepsLg, $"StartProgram({file.FullName})"); + } + } - private void btnSamsungSettings_Click(object sender, EventArgs e) - { - mnuLgSettings.ShowCustom(btnSamsungSettings); - } + private void btnSamsungSettings_Click(object sender, EventArgs e) + { + mnuLgSettings.ShowCustom(btnSamsungSettings); + } - private void lvSamsungPresets_ItemChecked(object sender, ItemCheckedEventArgs e) - { - ServiceFormUtils.ListViewItemChecked(lvSamsungPresets, e); - } + private void lvSamsungPresets_ItemChecked(object sender, ItemCheckedEventArgs e) + { + ServiceFormUtils.ListViewItemChecked(lvSamsungPresets, e); + } - private async void miTestPowerOffOn_Click(object sender, EventArgs e) - { - var device = _samsungService.SelectedDevice; + private async void miTestPowerOffOn_Click(object sender, EventArgs e) + { + var device = _samsungService.SelectedDevice; - if (device == null) - { - return; - } + if (device == null) + { + return; + } - var text = + var text = @"The TV will now power off. Please wait for the TV to be powered off completely (relay click) and press ENTER to wake it again. For waking up to work, you need to activate the following setting on the TV: @@ -941,179 +945,179 @@ Connection > Network > Expert Settings: Do you want to continue?"; - if (MessageForms.QuestionYesNo(text) == DialogResult.Yes) - { - await device.ConnectAsync(); + if (MessageForms.QuestionYesNo(text) == DialogResult.Yes) + { + await device.ConnectAsync(); - await device.PowerOffAsync(); + await device.PowerOffAsync(); - MessageForms.InfoOk("Press ENTER to wake the TV."); + MessageForms.InfoOk("Press ENTER to wake the TV."); - await device.WakeAndConnect(); - } - } + await device.WakeAndConnect(); + } + } - private void miNvSettings_Click(object sender, EventArgs e) - { - var powerRetries = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Maximum number of retries powering on after startup/resume.", - SubLabel = "Retries are necessary to wait for the network link of your pc to be established.", - MinValue = 1, - MaxValue = 40, - Value = _samsungService.Config.PowerOnRetries - }; + private void miNvSettings_Click(object sender, EventArgs e) + { + var powerRetries = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Maximum number of retries powering on after startup/resume.", + SubLabel = "Retries are necessary to wait for the network link of your pc to be established.", + MinValue = 1, + MaxValue = 40, + Value = _samsungService.Config.PowerOnRetries + }; - var shutDownDelayField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Delay when shutting down/restarting PC (milliseconds).", - SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", - MinValue = 0, - MaxValue = 5000, - Value = _samsungService.Config.ShutdownDelay - }; + var shutDownDelayField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Delay when shutting down/restarting PC (milliseconds).", + SubLabel = "This delay may prevent the tv from powering off when restarting the pc.", + MinValue = 0, + MaxValue = 5000, + Value = _samsungService.Config.ShutdownDelay + }; - var buttonDelayField = new FieldDefinition - { - FieldType = FieldType.Numeric, - Label = "Default delay between remote control button presses (milliseconds).", - SubLabel = "Increasing this delay may prevent skipped button presses.", - MinValue = 100, - MaxValue = 2000, - Value = _samsungService.Config.DefaultButtonDelay - }; + var buttonDelayField = new FieldDefinition + { + FieldType = FieldType.Numeric, + Label = "Default delay between remote control button presses (milliseconds).", + SubLabel = "Increasing this delay may prevent skipped button presses.", + MinValue = 100, + MaxValue = 2000, + Value = _samsungService.Config.DefaultButtonDelay + }; - var quickAccessShortcutField = new FieldDefinition - { - FieldType = FieldType.Shortcut, - Label = "Quick Access shortcut", - Value = _samsungService.Config.QuickAccessShortcut - }; + var quickAccessShortcutField = new FieldDefinition + { + FieldType = FieldType.Shortcut, + Label = "Quick Access shortcut", + Value = _samsungService.Config.QuickAccessShortcut + }; - var setDeviceField = new FieldDefinition - { - FieldType = FieldType.CheckBox, - Label = "Automatically set selected device to last powered on", - Value = _samsungService.Config.SetSelectedDeviceByPowerOn - }; + var setDeviceField = new FieldDefinition + { + FieldType = FieldType.CheckBox, + Label = "Automatically set selected device to last powered on", + Value = _samsungService.Config.SetSelectedDeviceByPowerOn + }; - var showAdvancedField = new FieldDefinition - { - FieldType = FieldType.CheckBox, - Label = "Show advanced actions under the Expert-button (Service Menu)", - SubLabel = "WARNING: entering the Service Menu is not recommended. This app and its creator are in no way accountable for any damages it may cause to your tv.", - Value = _samsungService.Config.ShowAdvancedActions - }; + var showAdvancedField = new FieldDefinition + { + FieldType = FieldType.CheckBox, + Label = "Show advanced actions under the Expert-button (Service Menu)", + SubLabel = "WARNING: entering the Service Menu is not recommended. This app and its creator are in no way accountable for any damages it may cause to your tv.", + Value = _samsungService.Config.ShowAdvancedActions + }; - var values = MessageForms.ShowDialog("Samsung controller settings", new[] { powerRetries, shutDownDelayField, buttonDelayField, quickAccessShortcutField, setDeviceField, showAdvancedField }); + var values = MessageForms.ShowDialog("Samsung controller settings", new[] { powerRetries, shutDownDelayField, buttonDelayField, quickAccessShortcutField, setDeviceField, showAdvancedField }); - if (values?.Any() != true) - { - return; - } + if (values?.Any() != true) + { + return; + } - _samsungService.Config.PowerOnRetries = powerRetries.ValueAsInt; - _samsungService.Config.ShutdownDelay = shutDownDelayField.ValueAsInt; - _samsungService.Config.DefaultButtonDelay = buttonDelayField.ValueAsInt; + _samsungService.Config.PowerOnRetries = powerRetries.ValueAsInt; + _samsungService.Config.ShutdownDelay = shutDownDelayField.ValueAsInt; + _samsungService.Config.DefaultButtonDelay = buttonDelayField.ValueAsInt; - var shortcutQA = quickAccessShortcutField.Value.ToString(); + var shortcutQA = quickAccessShortcutField.Value.ToString(); - _samsungService.Config.QuickAccessShortcut = shortcutQA; + _samsungService.Config.QuickAccessShortcut = shortcutQA; - _keyboardShortcutDispatcher.RegisterShortcut(SamsungService.SHORTCUTID_SAMSUNGQA, _samsungService.Config.QuickAccessShortcut); + _keyboardShortcutDispatcher.RegisterShortcut(SamsungService.SHORTCUTID_SAMSUNGQA, _samsungService.Config.QuickAccessShortcut); - _samsungService.Config.SetSelectedDeviceByPowerOn = setDeviceField.ValueAsBool; - _samsungService.Config.ShowAdvancedActions = showAdvancedField.ValueAsBool; - } + _samsungService.Config.SetSelectedDeviceByPowerOn = setDeviceField.ValueAsBool; + _samsungService.Config.ShowAdvancedActions = showAdvancedField.ValueAsBool; + } - private void clbLgPower_SelectedIndexChanged(object sender, EventArgs e) - { - btnSamsungDeviceOptionsSettings.Enabled = clbLgPower.SelectedIndex == 4; - } + private void clbLgPower_SelectedIndexChanged(object sender, EventArgs e) + { + btnSamsungDeviceOptionsSettings.Enabled = clbLgPower.SelectedIndex == 4; + } - private void btnSamsungDeviceOptionsSettings_Click(object sender, EventArgs e) - { - var device = _samsungService.SelectedDevice; - var selectedItem = clbLgPower.SelectedItem; + private void btnSamsungDeviceOptionsSettings_Click(object sender, EventArgs e) + { + var device = _samsungService.SelectedDevice; + var selectedItem = clbLgPower.SelectedItem; - var durationField = new FieldDefinition - { - Label = "Enter the minimal duration the screen saver must be running (in seconds)", - FieldType = FieldType.Numeric, - MinValue = 0, - MaxValue = 3600, - Value = device.Options.ScreenSaverMinimalDuration - }; - var turnOffField = new FieldDefinition - { - Label = "Turn screen off instead of power off", - SubLabel = "NOTE: turning the screen off might not work correctly on all models", - FieldType = FieldType.CheckBox, - Value = device.Options.TurnScreenOffOnScreenSaver - }; - var turnOnField = new FieldDefinition - { - Label = "Turn screen on instead of power on", - FieldType = FieldType.CheckBox, - Value = device.Options.TurnScreenOnAfterScreenSaver - }; - var manualField = new FieldDefinition - { - Label = "Perform action even on manually executed screen saver", - FieldType = FieldType.CheckBox, - Value = device.Options.HandleManualScreenSaver - }; + var durationField = new FieldDefinition + { + Label = "Enter the minimal duration the screen saver must be running (in seconds)", + FieldType = FieldType.Numeric, + MinValue = 0, + MaxValue = 3600, + Value = device.Options.ScreenSaverMinimalDuration + }; + var turnOffField = new FieldDefinition + { + Label = "Turn screen off instead of power off", + SubLabel = "NOTE: turning the screen off might not work correctly on all models", + FieldType = FieldType.CheckBox, + Value = device.Options.TurnScreenOffOnScreenSaver + }; + var turnOnField = new FieldDefinition + { + Label = "Turn screen on instead of power on", + FieldType = FieldType.CheckBox, + Value = device.Options.TurnScreenOnAfterScreenSaver + }; + var manualField = new FieldDefinition + { + Label = "Perform action even on manually executed screen saver", + FieldType = FieldType.CheckBox, + Value = device.Options.HandleManualScreenSaver + }; - if (!MessageForms.ShowDialog($"Settings - {selectedItem}", new[] { durationField, turnOffField, turnOnField, manualField }).Any()) - { - return; - } + if (!MessageForms.ShowDialog($"Settings - {selectedItem}", new[] { durationField, turnOffField, turnOnField, manualField }).Any()) + { + return; + } - device.Options.ScreenSaverMinimalDuration = durationField.ValueAsInt; - device.Options.TurnScreenOffOnScreenSaver = turnOffField.ValueAsBool; - device.Options.TurnScreenOnAfterScreenSaver = turnOnField.ValueAsBool; - device.Options.HandleManualScreenSaver = manualField.ValueAsBool; - } + device.Options.ScreenSaverMinimalDuration = durationField.ValueAsInt; + device.Options.TurnScreenOffOnScreenSaver = turnOffField.ValueAsBool; + device.Options.TurnScreenOnAfterScreenSaver = turnOnField.ValueAsBool; + device.Options.HandleManualScreenSaver = manualField.ValueAsBool; + } - private void btnExpert_Click(object sender, EventArgs e) - { - mnuExpert.ShowCustom(btnExpert); - } + private void btnExpert_Click(object sender, EventArgs e) + { + mnuExpert.ShowCustom(btnExpert); + } - private void mnuExpert_Opening(object sender, CancelEventArgs e) - { - miExpertDummy.Visible = false; + private void mnuExpert_Opening(object sender, CancelEventArgs e) + { + miExpertDummy.Visible = false; - var device = _samsungService.SelectedDevice; + var device = _samsungService.SelectedDevice; - if (device == null) - { - return; - } + if (device == null) + { + return; + } - var actions = device.GetInvokableActions(_samsungService.Config.ShowAdvancedActions); + var actions = device.GetInvokableActions(_samsungService.Config.ShowAdvancedActions); - foreach (var action in actions) - { - FormUtils.BuildMenuItem(mnuExpert.Items, action.Name, action.Name, action, mnuExpertClick); - } - } + foreach (var action in actions) + { + FormUtils.BuildMenuItem(mnuExpert.Items, action.Name, action.Name, action, mnuExpertClick); + } + } - private async void mnuExpertClick(object sender, EventArgs e) - { - var item = (ToolStripMenuItem)sender; - var action = (SamsungDevice.InvokableAction)item.Tag; + private async void mnuExpertClick(object sender, EventArgs e) + { + var item = (ToolStripMenuItem)sender; + var action = (SamsungDevice.InvokableAction)item.Tag; - var device = _samsungService.SelectedDevice; + var device = _samsungService.SelectedDevice; - if (device == null) - { - return; - } + if (device == null) + { + return; + } - await device.ExecuteActionAsync(action, null, Shared.Common.GlobalContext.CurrentContext, _samsungService.Config); - } - } + await device.ExecuteActionAsync(action, null, Shared.Common.GlobalContext.CurrentContext, _samsungService.Config); + } + } } diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs index 71fda2c..e46c335 100644 --- a/ColorControl/Services/Samsung/SamsungService.cs +++ b/ColorControl/Services/Samsung/SamsungService.cs @@ -6,6 +6,7 @@ using ColorControl.Shared.EventDispatcher; using ColorControl.Shared.Native; using ColorControl.Shared.Services; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Win32; using Newtonsoft.Json; using NWin32; using System; @@ -122,6 +123,7 @@ namespace ColorControl.Services.Samsung _powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged); _processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged); _displayChangeEventDispatcher.RegisterAsyncEventHandler(DisplayChangeEventDispatcher.Event_DisplayChanged, DisplayChanged); + _sessionSwitchDispatcher.RegisterAsyncEventHandler(SessionSwitchDispatcher.Event_SessionSwitch, SessionSwitched); } public override List GetInfo() @@ -149,6 +151,20 @@ namespace ColorControl.Services.Samsung } } + private async Task SessionSwitched(object sender, SessionSwitchEventArgs e, CancellationToken token) + { + if (e.Reason == SessionSwitchReason.SessionLock) + { + var devices = Devices.Where(d => d.Options.PowerOffAfterSessionLock); + PowerOffDevices(devices, PowerOnOffState.StandBy); + } + else if (e.Reason == SessionSwitchReason.SessionUnlock) + { + var devices = Devices.Where(d => d.Options.PowerOnAfterSessionUnlock).ToList(); + await PowerOnDevicesTask(devices); + } + } + private void LoadConfig() { _configFilename = Path.Combine(_dataPath, "SamsungConfig.json"); @@ -300,7 +316,7 @@ namespace ColorControl.Services.Samsung return true; } - private async void SelectedDevice_Connected(object sender) + private void SelectedDevice_Connected(object sender) { if (_samsungApps.Any()) { @@ -548,12 +564,12 @@ namespace ColorControl.Services.Samsung return; } - var applicableDevices = Devices.Where(d => - d.Options.PowerOffOnScreenSaver - || d.Options.PowerOnAfterScreenSaver - || (d.Options.TriggersEnabled + var applicableDevices = Devices.Where(d => + d.Options.PowerOffOnScreenSaver + || d.Options.PowerOnAfterScreenSaver + || (d.Options.TriggersEnabled // only these trigger types are ever triggered by a process changed event - && _presets.Any(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch || t.Trigger == PresetTriggerType.ScreensaverStart || t.Trigger == PresetTriggerType.ScreensaverStop) + && _presets.Any(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch || t.Trigger == PresetTriggerType.ScreensaverStart || t.Trigger == PresetTriggerType.ScreensaverStop) && ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))))) .ToList(); @@ -834,7 +850,7 @@ namespace ColorControl.Services.Samsung } } - SavePresets(); + SaveConfig(); return true; } diff --git a/ColorControl/XForms/BrowserWindow.xaml.cs b/ColorControl/XForms/BrowserWindow.xaml.cs index 0db7616..97344a0 100644 --- a/ColorControl/XForms/BrowserWindow.xaml.cs +++ b/ColorControl/XForms/BrowserWindow.xaml.cs @@ -10,96 +10,93 @@ namespace ColorControl.XForms; public partial class BrowserWindow : BaseWindow { - private static BrowserWindow _window; - private readonly GlobalContext _globalContext; - public string Url { get; private set; } + private static BrowserWindow _window; + private readonly GlobalContext _globalContext; + public string Url { get; private set; } - public BrowserWindow(GlobalContext globalContext) - { - InitializeComponent(); + public BrowserWindow(GlobalContext globalContext) + { + InitializeComponent(); - DataContext = this; + DataContext = this; - _globalContext = globalContext; + _globalContext = globalContext; - Title = _globalContext.ApplicationTitleAdmin; + Title = _globalContext.ApplicationTitleAdmin; - Width = _globalContext.Config.XFormWidth; - Height = _globalContext.Config.XFormHeight; - } + Width = _globalContext.Config.XFormWidth; + Height = _globalContext.Config.XFormHeight; + } - private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri) - { - UseShellExecute = true, - }; - System.Diagnostics.Process.Start(processStartInfo); - } + private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri) + { + UseShellExecute = true, + }; + System.Diagnostics.Process.Start(processStartInfo); + } - public static void CreateAndShow(string url, bool show = true) - { - if (Application.Current == null) - { - new Application(); - } + public static void CreateAndShow(string url, bool show = true) + { + Utils.EnsureApplication(); - _window ??= Program.ServiceProvider.GetRequiredService(); + _window ??= Program.ServiceProvider.GetRequiredService(); - if (show) - { - _window.Url = url; - _window.WindowState = WindowState.Normal; - _window.Show(); - _window.Topmost = true; - _window.Topmost = false; - } - } + if (show) + { + _window.Url = url; + _window.WindowState = WindowState.Normal; + _window.Show(); + _window.Topmost = true; + _window.Topmost = false; + } + } - protected override void OnClosing(CancelEventArgs e) - { - e.Cancel = true; + protected override void OnClosing(CancelEventArgs e) + { + e.Cancel = true; - _globalContext.Config.XFormWidth = Width; - _globalContext.Config.XFormHeight = Height; + _globalContext.Config.XFormWidth = Width; + _globalContext.Config.XFormHeight = Height; - if (_globalContext.Config.MinimizeOnClose) - { - Hide(); + if (_globalContext.Config.MinimizeOnClose) + { + Hide(); - return; - } + return; + } - Program.Exit(); - } + Program.Exit(); + } - protected override void OnStateChanged(EventArgs e) - { - if (WindowState == WindowState.Minimized) - { - Hide(); - } + protected override void OnStateChanged(EventArgs e) + { + if (WindowState == WindowState.Minimized) + { + Hide(); + } - base.OnStateChanged(e); - } + base.OnStateChanged(e); + } - private void Close_Click(object sender, RoutedEventArgs e) - { - Close(); - } + private void Close_Click(object sender, RoutedEventArgs e) + { + Close(); + } - private async void Window_Loaded(object sender, RoutedEventArgs e) - { - } + private void Window_Loaded(object sender, RoutedEventArgs e) + { + } - private void webView_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e) - { - var core = _window.webView.CoreWebView2; + private void webView_NavigationStarting(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs e) + { + var core = _window.webView.CoreWebView2; - if (core != null && !core.Settings.UserAgent.Contains("ColorControlEmbedded")) - { - core.Settings.UserAgent = core.Settings.UserAgent + " (ColorControlEmbedded)"; - } + if (core != null && !core.Settings.UserAgent.Contains("ColorControlEmbedded")) + { + core.Settings.UserAgent = core.Settings.UserAgent + " (ColorControlEmbedded)"; + } - } + } } \ No newline at end of file diff --git a/ColorControl/XForms/ColorProfileWindow.xaml.cs b/ColorControl/XForms/ColorProfileWindow.xaml.cs index a26d1c1..fa8232a 100644 --- a/ColorControl/XForms/ColorProfileWindow.xaml.cs +++ b/ColorControl/XForms/ColorProfileWindow.xaml.cs @@ -15,224 +15,221 @@ using System.Windows.Navigation; namespace ColorControl.XForms { - public partial class ColorProfileWindow : BaseWindow - { - private ColorProfileViewModel _viewModel; - private readonly WinApiAdminService _winApiAdminService; - private readonly GlobalContext _globalContext; + public partial class ColorProfileWindow : BaseWindow + { + private ColorProfileViewModel _viewModel; + private readonly WinApiAdminService _winApiAdminService; + private readonly GlobalContext _globalContext; - public ColorProfileWindow(WinApiAdminService winApiAdminService, GlobalContext globalContext, bool isHDR) - { - _winApiAdminService = winApiAdminService; - _globalContext = globalContext; + public ColorProfileWindow(WinApiAdminService winApiAdminService, GlobalContext globalContext, bool isHDR) + { + _winApiAdminService = winApiAdminService; + _globalContext = globalContext; - _viewModel = new ColorProfileViewModel(isHDR); - _viewModel.SetMinMaxTml = globalContext.Config.SetMinTmlAndMaxTml; + _viewModel = new ColorProfileViewModel(isHDR); + _viewModel.SetMinMaxTml = globalContext.Config.SetMinTmlAndMaxTml; - DataContext = _viewModel; - InitializeComponent(); - } + DataContext = _viewModel; + InitializeComponent(); + } - private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri) - { - UseShellExecute = true, - }; - System.Diagnostics.Process.Start(processStartInfo); - } + private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri) + { + UseShellExecute = true, + }; + System.Diagnostics.Process.Start(processStartInfo); + } - public static void CreateAndShow(bool show = true, bool isHDR = true) - { - if (Application.Current == null) - { - new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown }; - } + public static void CreateAndShow(bool show = true, bool isHDR = true) + { + Utils.EnsureApplication(); - var winApiAdminService = Program.ServiceProvider.GetRequiredService(); - var globalContext = Program.ServiceProvider.GetRequiredService(); - var window = new ColorProfileWindow(winApiAdminService, globalContext, isHDR); + var winApiAdminService = Program.ServiceProvider.GetRequiredService(); + var globalContext = Program.ServiceProvider.GetRequiredService(); + var window = new ColorProfileWindow(winApiAdminService, globalContext, isHDR); - if (show) - { - ElementHost.EnableModelessKeyboardInterop(window); - window.WindowState = WindowState.Normal; - window.Show(); - window.Topmost = true; - window.Topmost = false; - } - } + if (show) + { + ElementHost.EnableModelessKeyboardInterop(window); + window.WindowState = WindowState.Normal; + window.Show(); + window.Topmost = true; + window.Topmost = false; + } + } - protected override void OnClosed(EventArgs e) - { - base.OnClosed(e); - } + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + } - protected override void OnClosing(CancelEventArgs e) - { - //e.Cancel = true; + protected override void OnClosing(CancelEventArgs e) + { + //e.Cancel = true; - //Hide(); - } + //Hide(); + } - protected override void OnStateChanged(EventArgs e) - { - if (WindowState == WindowState.Minimized) - { - Close(); - } + protected override void OnStateChanged(EventArgs e) + { + if (WindowState == WindowState.Minimized) + { + Close(); + } - base.OnStateChanged(e); - } + base.OnStateChanged(e); + } - private void Close_Click(object sender, RoutedEventArgs e) - { - Close(); - } + private void Close_Click(object sender, RoutedEventArgs e) + { + Close(); + } - private void Window_Loaded(object sender, RoutedEventArgs e) - { - if (!_viewModel.Displays.Any()) - { - var message = _viewModel.IsHDR ? - "There are no displays available that support HDR and have it enabled. Please activate HDR first." : - "There are no displays available are in SDR mode. Please deactivate HDR first."; + private void Window_Loaded(object sender, RoutedEventArgs e) + { + if (!_viewModel.Displays.Any()) + { + var message = _viewModel.IsHDR ? + "There are no displays available that support HDR and have it enabled. Please activate HDR first." : + "There are no displays available are in SDR mode. Please deactivate HDR first."; - MessageForms.WarningOk(message); - } - } + MessageForms.WarningOk(message); + } + } - private void Button_Click(object sender, RoutedEventArgs e) - { - var command = new GenerateProfileCommand - { - Description = _viewModel.Description, - IsHDRProfile = _viewModel.IsHDR, - MinCLL = _viewModel.MinCLL, - MaxCLL = _viewModel.MaxCLL, - BlackLuminance = _viewModel.BlackLuminance, - WhiteLuminance = _viewModel.WhiteLuminance, - SDRMinBrightness = _viewModel.SDRMinBrightness, - SDRMaxBrightness = _viewModel.SDRMaxBrightness, - SDRTransferFunction = _viewModel.SDRTransferFunction, - SDRBrightnessBoost = _viewModel.SDRBrightnessBoost, - ColorGamut = _viewModel.ColorGamut, - Gamma = _viewModel.CustomGamma, - DevicePrimaries = _viewModel.GetDevicePrimaries(), - }; + private void Button_Click(object sender, RoutedEventArgs e) + { + var command = new GenerateProfileCommand + { + Description = _viewModel.Description, + IsHDRProfile = _viewModel.IsHDR, + MinCLL = _viewModel.MinCLL, + MaxCLL = _viewModel.MaxCLL, + BlackLuminance = _viewModel.BlackLuminance, + WhiteLuminance = _viewModel.WhiteLuminance, + SDRMinBrightness = _viewModel.SDRMinBrightness, + SDRMaxBrightness = _viewModel.SDRMaxBrightness, + SDRTransferFunction = _viewModel.SDRTransferFunction, + SDRBrightnessBoost = _viewModel.SDRBrightnessBoost, + ColorGamut = _viewModel.ColorGamut, + Gamma = _viewModel.CustomGamma, + DevicePrimaries = _viewModel.GetDevicePrimaries(), + }; - var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command); + var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command); - if (_viewModel.SaveOption == SaveOption.SaveToFile) - { - var saveFileDialog = new System.Windows.Forms.SaveFileDialog - { - FileName = _viewModel.NewProfileName - }; - if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) - { - File.WriteAllBytes(saveFileDialog.FileName, bytes); - } - return; - } + if (_viewModel.SaveOption == SaveOption.SaveToFile) + { + var saveFileDialog = new System.Windows.Forms.SaveFileDialog + { + FileName = _viewModel.NewProfileName + }; + if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + File.WriteAllBytes(saveFileDialog.FileName, bytes); + } + return; + } - var tempFilename = Path.GetFullPath(_viewModel.ProfileName, Path.GetTempPath()); + var tempFilename = Path.GetFullPath(_viewModel.ProfileName, Path.GetTempPath()); - if (File.Exists(tempFilename)) - { - File.Delete(tempFilename); - } + if (File.Exists(tempFilename)) + { + File.Delete(tempFilename); + } - File.WriteAllBytes(tempFilename, bytes); + File.WriteAllBytes(tempFilename, bytes); - _winApiAdminService.UninstallColorProfile(tempFilename); + _winApiAdminService.UninstallColorProfile(tempFilename); - CCD.InstallColorProfile(tempFilename); + CCD.InstallColorProfile(tempFilename); - if (_viewModel.SaveOption != SaveOption.InstallAndSetAsDefault) - { - return; - } + if (_viewModel.SaveOption != SaveOption.InstallAndSetAsDefault) + { + return; + } - var displayName = _viewModel.SelectedDisplayName; + var displayName = _viewModel.SelectedDisplayName; - var profileName = Path.GetFileName(tempFilename); + var profileName = Path.GetFileName(tempFilename); - if (CCD.SetDisplayDefaultColorProfile(displayName, profileName, _viewModel.SetMinMaxTml, _viewModel.IsHDR)) - { - if (!_viewModel.ExistingProfiles.Contains(profileName)) - { - _viewModel.ExistingProfiles.Add(profileName); - } - _viewModel.SelectedExistingProfile = profileName; - _viewModel.Update(); - } - } + if (CCD.SetDisplayDefaultColorProfile(displayName, profileName, _viewModel.SetMinMaxTml, _viewModel.IsHDR)) + { + if (!_viewModel.ExistingProfiles.Contains(profileName)) + { + _viewModel.ExistingProfiles.Add(profileName); + } + _viewModel.SelectedExistingProfile = profileName; + _viewModel.Update(); + } + } - private void Button_Click_1(object sender, RoutedEventArgs e) - { - var existingProfile = _viewModel.SelectedExistingProfile; + private void Button_Click_1(object sender, RoutedEventArgs e) + { + var existingProfile = _viewModel.SelectedExistingProfile; - if (existingProfile == ColorProfileViewModel.CreateANewProfile) - { - var openFileDialog = new System.Windows.Forms.OpenFileDialog - { - Filter = "Color profiles (*.icc;*.icm)|*.icc;*.icm" - }; - if (openFileDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) - { - return; - } + if (existingProfile == ColorProfileViewModel.CreateANewProfile) + { + var openFileDialog = new System.Windows.Forms.OpenFileDialog + { + Filter = "Color profiles (*.icc;*.icm)|*.icc;*.icm" + }; + if (openFileDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) + { + return; + } - existingProfile = openFileDialog.FileName; - } + existingProfile = openFileDialog.FileName; + } - if (MessageForms.QuestionYesNo("This will discard all current changes. Do you want to continue?") != System.Windows.Forms.DialogResult.Yes) - { - return; - } + if (MessageForms.QuestionYesNo("This will discard all current changes. Do you want to continue?") != System.Windows.Forms.DialogResult.Yes) + { + return; + } - // Load profile - var profileProperties = MHC2Wrapper.LoadProfile(existingProfile, _viewModel.IsHDR); + // Load profile + var profileProperties = MHC2Wrapper.LoadProfile(existingProfile, _viewModel.IsHDR); - _viewModel.RedPoint = new(profileProperties.DevicePrimaries.Red); - _viewModel.GreenPoint = new(profileProperties.DevicePrimaries.Green); - _viewModel.BluePoint = new(profileProperties.DevicePrimaries.Blue); - _viewModel.WhitePoint = new(profileProperties.DevicePrimaries.White); - _viewModel.ColorGamut = profileProperties.ColorGamut; - _viewModel.BlackLuminance = profileProperties.BlackLuminance; - _viewModel.WhiteLuminance = profileProperties.WhiteLuminance; - _viewModel.SDRTransferFunction = profileProperties.SDRTransferFunction; - _viewModel.CustomGamma = profileProperties.Gamma; - _viewModel.SDRMinBrightness = profileProperties.SDRMinBrightness; - _viewModel.SDRMaxBrightness = profileProperties.SDRMaxBrightness; - _viewModel.SDRBrightnessBoost = profileProperties.SDRBrightnessBoost; - _viewModel.MinCLL = profileProperties.MinCLL; - _viewModel.MaxCLL = profileProperties.MaxCLL; + _viewModel.RedPoint = new(profileProperties.DevicePrimaries.Red); + _viewModel.GreenPoint = new(profileProperties.DevicePrimaries.Green); + _viewModel.BluePoint = new(profileProperties.DevicePrimaries.Blue); + _viewModel.WhitePoint = new(profileProperties.DevicePrimaries.White); + _viewModel.ColorGamut = profileProperties.ColorGamut; + _viewModel.BlackLuminance = profileProperties.BlackLuminance; + _viewModel.WhiteLuminance = profileProperties.WhiteLuminance; + _viewModel.SDRTransferFunction = profileProperties.SDRTransferFunction; + _viewModel.CustomGamma = profileProperties.Gamma; + _viewModel.SDRMinBrightness = profileProperties.SDRMinBrightness; + _viewModel.SDRMaxBrightness = profileProperties.SDRMaxBrightness; + _viewModel.SDRBrightnessBoost = profileProperties.SDRBrightnessBoost; + _viewModel.MinCLL = profileProperties.MinCLL; + _viewModel.MaxCLL = profileProperties.MaxCLL; - _viewModel.Update(); - } + _viewModel.Update(); + } - private void Button_Click_2(object sender, RoutedEventArgs e) - { - var existingProfile = _viewModel.SelectedExistingProfile; + private void Button_Click_2(object sender, RoutedEventArgs e) + { + var existingProfile = _viewModel.SelectedExistingProfile; - if (existingProfile == ColorProfileViewModel.CreateANewProfile) - { - return; - } + if (existingProfile == ColorProfileViewModel.CreateANewProfile) + { + return; + } - if (MessageForms.QuestionYesNo($"Are you sure you want to remove the color profile '{existingProfile}'?") != System.Windows.Forms.DialogResult.Yes) - { - return; - } + if (MessageForms.QuestionYesNo($"Are you sure you want to remove the color profile '{existingProfile}'?") != System.Windows.Forms.DialogResult.Yes) + { + return; + } - if (_winApiAdminService.UninstallColorProfile(existingProfile)) - { - _viewModel.SelectedExistingProfile = ColorProfileViewModel.CreateANewProfile; - _viewModel.ExistingProfiles.Remove(existingProfile); - _viewModel.Update(); - } - } - } + if (_winApiAdminService.UninstallColorProfile(existingProfile)) + { + _viewModel.SelectedExistingProfile = ColorProfileViewModel.CreateANewProfile; + _viewModel.ExistingProfiles.Remove(existingProfile); + _viewModel.Update(); + } + } + } } \ No newline at end of file diff --git a/ColorControl/XForms/LogWindow.xaml.cs b/ColorControl/XForms/LogWindow.xaml.cs index 0963209..68229b0 100644 --- a/ColorControl/XForms/LogWindow.xaml.cs +++ b/ColorControl/XForms/LogWindow.xaml.cs @@ -47,10 +47,7 @@ namespace ColorControl.XForms public static void CreateAndShow(bool show = true) { - if (Application.Current == null) - { - new Application(); - } + Utils.EnsureApplication(); _window ??= Program.ServiceProvider.GetRequiredService(); diff --git a/LittleCms/CmsDefinition.cs b/LittleCms/CmsDefinition.cs index 1394831..1af5138 100644 --- a/LittleCms/CmsDefinition.cs +++ b/LittleCms/CmsDefinition.cs @@ -432,6 +432,10 @@ namespace LittleCms RELATIVE_COLORIMETRIC = 1, SATURATION = 2, ABSOLUTE_COLORIMETRIC = 3, + + CPST_STANDARD_DISPLAY_COLOR_MODE = 7, + CPST_EXTENDED_DISPLAY_COLOR_MODE = 8, + // Non-ICC intents PRESERVE_K_ONLY_PERCEPTUAL = 10, PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC = 11, diff --git a/LittleCms/IccProfile.cs b/LittleCms/IccProfile.cs index 24d0e47..bdbd0c3 100644 --- a/LittleCms/IccProfile.cs +++ b/LittleCms/IccProfile.cs @@ -8,7 +8,7 @@ namespace LittleCms public class IccProfile : CmsObject { public override CmsContext Context { get; } - public byte[] RawBytes { get; } + public byte[]? RawBytes { get; } public IccProfile(IntPtr handle, bool moveOwnership) : base(handle, moveOwnership) { diff --git a/MHC2Gen/IccContext.cs b/MHC2Gen/IccContext.cs index b1bd975..5fcf9b5 100644 --- a/MHC2Gen/IccContext.cs +++ b/MHC2Gen/IccContext.cs @@ -9,1208 +9,1208 @@ using System.Text.Json; namespace MHC2Gen { - class MHC2Tag - { - public const TagSignature Signature = (TagSignature)0x4D484332; + class MHC2Tag + { + public const TagSignature Signature = (TagSignature)0x4D484332; - public double MinCLL { get; set; } - public double MaxCLL { get; set; } - public double[,]? Matrix3x4 { get; set; } - public double[,]? RegammaLUT { get; set; } + public double MinCLL { get; set; } + public double MaxCLL { get; set; } + public double[,]? Matrix3x4 { get; set; } + public double[,]? RegammaLUT { get; set; } - public MHC2Tag() { } + public MHC2Tag() { } - public MHC2Tag(ReadOnlySpan bytes) - { - LoadFromBytes(bytes); - } + public MHC2Tag(ReadOnlySpan bytes) + { + LoadFromBytes(bytes); + } - private void LoadFromBytes(ReadOnlySpan bytes) - { - var ms0 = new MemoryStream(bytes.ToArray()); - var reader = new BinaryReader(ms0); + private void LoadFromBytes(ReadOnlySpan bytes) + { + var ms0 = new MemoryStream(bytes.ToArray()); + var reader = new BinaryReader(ms0); - // Signature - reader.ReadBytes(4); - // 0 - reader.ReadBytes(4); + // Signature + reader.ReadBytes(4); + // 0 + reader.ReadBytes(4); - // Lut Size - var lutSize = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); + // Lut Size + var lutSize = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); - MinCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); - MaxCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); + MinCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); + MaxCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); - // Matrix offset - var maxtrixOffset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); + // Matrix offset + var maxtrixOffset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); - // Lut0 offset - var lut0Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); + // Lut0 offset + var lut0Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); - // Lut1 offset - var lut1Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); + // Lut1 offset + var lut1Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); - // Lut2 offset - var lut2Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); + // Lut2 offset + var lut2Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)); - Matrix3x4 = new double[3, 4]; + Matrix3x4 = new double[3, 4]; - for (var x = 0; x < 3; x++) - { - for (var y = 0; y < 4; y++) - { - Matrix3x4[x, y] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); - } - } - - RegammaLUT = new double[3, lutSize]; - - for (var c = 0; c < 3; c++) - { - var discard = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(8)); - - for (var x = 0; x < lutSize; x++) - { - RegammaLUT[c, x] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); - } - } - } - - public void ApplySdrAcm(double whiteLuminance = 120.0, double blackLuminance = 0.0, double gamma = 2.2, double boostPercentage = 0, double shadowDetailBoost = 0) - { - var lutSize = 1024; - var amplifier = boostPercentage == 0 ? 1 : 1 + boostPercentage / 100; - - RegammaLUT = new double[3, lutSize]; - - for (var i = 0; i < lutSize; i++) - { - var (_, value) = CmsFunctions.SrgbAcm(i, whiteLuminance, blackLuminance, gamma, lutSize - 1); - - // Average between sSRGB and Piecewise gamma - if (shadowDetailBoost > 0) - { - var piecewiseValue = (double)i / 1023; - - var correctedBoost = (shadowDetailBoost * (1023 - i)) / 1023; - - value = ((value * (100 - correctedBoost)) + (piecewiseValue * correctedBoost)) / 100; - } - - for (var c = 0; c < 3; c++) - { - //var tempPercentage = boostPercentage * ((double)i / lutSize); - - //var amplifier = tempPercentage == 0 ? 1 : tempPercentage > 0 ? 1 + tempPercentage / 100 : 1 - tempPercentage / 100; - - RegammaLUT[c, i] = value * amplifier; - } - } - } - - public void ApplyPiecewise(double boostPercentage = 0) - { - var lutSize = 1024; - var amplifier = boostPercentage == 0 ? 1 : 1 + boostPercentage / 100; - - RegammaLUT = new double[3, lutSize]; - - for (var i = 0; i < lutSize; i++) - { - var value = (double)i / 1023; - - for (var c = 0; c < 3; c++) - { - RegammaLUT[c, i] = value * amplifier; - } - } - } - - public void ApplyGamma() - { - var lutSize = 1024; - - RegammaLUT = new double[3, lutSize]; - - for (var i = 0; i < lutSize; i++) - { - var value = CmsFunctions.RgbToLinear((double)i / (lutSize - 1), 1.0); - - for (var c = 0; c < 3; c++) - { - RegammaLUT[c, i] = value; - } - } - } - - private static int EncodeS15F16(double value) - { - var x = (int)Math.Round(value * 65536); - return x; - } - - private static double DecodeS15F16(int value) - { - return (double)value / 65536; - } - - public byte[] ToBytes() - { - if (RegammaLUT!.GetLength(0) != 3) throw new ArrayTypeMismatchException(); - var lut_size = RegammaLUT.GetLength(1); - if (lut_size <= 1 || lut_size > 4096) throw new IndexOutOfRangeException(); - if (Matrix3x4!.Length != 12 || Matrix3x4.GetLength(0) != 3) throw new ArrayTypeMismatchException(); - var ms0 = new MemoryStream(); - var writer = new BinaryWriter(ms0); - // type - writer.Write(BinaryPrimitives.ReverseEndianness((int)Signature)); - writer.Write(BinaryPrimitives.ReverseEndianness(0)); - - writer.Write(BinaryPrimitives.ReverseEndianness(lut_size)); - writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MinCLL))); - writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MaxCLL))); - // matrix offset - writer.Write(BinaryPrimitives.ReverseEndianness(36)); - // lut0 offset - writer.Write(BinaryPrimitives.ReverseEndianness(84)); - // lut1 offset - var lut1_offset = 84 + 8 + lut_size * 4; - writer.Write(BinaryPrimitives.ReverseEndianness(lut1_offset)); - // lut2 offset - var lut2_offset = lut1_offset + 8 + lut_size * 4; - writer.Write(BinaryPrimitives.ReverseEndianness(lut2_offset)); - - foreach (var e in Matrix3x4) - { - writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(e))); - } - - for (int ch = 0; ch < 3; ch++) - { - writer.Write(new ReadOnlySpan(new byte[] { (byte)'s', (byte)'f', (byte)'3', (byte)'2', 0, 0, 0, 0 })); - for (int i = 0; i < lut_size; i++) - { - writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(RegammaLUT[ch, i]))); - } - } - writer.Flush(); - return ms0.ToArray(); - } - - internal static MHC2Tag? LoadFromProfile(IccProfile profile) - { - var bytes = profile.RawBytes; - - if (bytes == null) - { - return null; - } - - // 0x4D484332 - var index = -1; - var first = true; - for (var i = 0; i < bytes.Length; i += 4) - { - var chunk = bytes.AsSpan(new Range(i, i + 4)); - - if (chunk[0] == 77 && chunk[1] == 72 && chunk[2] == 67 && chunk[3] == 50) - { - index = i; - - if (first) - { - index = -1; - first = false; - continue; - } - break; - } - } - - if (index == -1) - { - return null; - } - - var mhc2Bytes = bytes.AsSpan(Range.StartAt(index)); - - return new MHC2Tag(mhc2Bytes); - } - } - - internal class ExtraInfoTag - { - public SDRTransferFunction SDRTransferFunction { get; set; } - public double Gamma { get; set; } - public double SDRMinBrightness { get; set; } - public double SDRMaxBrightness { get; set; } - public double SDRBrightnessBoost { get; set; } - public double ShadowDetailBoost { get; set; } - public ColorGamut TargetGamut { get; set; } - } - - internal class IccContext - { - protected IccProfile profile; - public CIEXYZ IlluminantRelativeWhitePoint { get; } - public Matrix? ChromaticAdaptionMatrix { get; } - public Matrix? InverseChromaticAdaptionMatrix { get; } - public RgbPrimaries ProfilePrimaries { get; } - public MHC2Tag? MHC2 { get; set; } - - public IccContext(IccProfile profile) - { - if (profile.PCS != ColorSpaceSignature.XYZ || profile.ColorSpace != ColorSpaceSignature.Rgb) - { - throw new CmsException(CmsError.COLORSPACE_CHECK, "ICC profile is not XYZ->RGB"); - } - this.profile = profile; - { - if (profile.TryReadTag(SafeTagSignature.ChromaticAdaptationTag, out var chad)) - ChromaticAdaptionMatrix = DenseMatrix.OfArray(chad); - InverseChromaticAdaptionMatrix = ChromaticAdaptionMatrix?.Inverse(); - } - (IlluminantRelativeWhitePoint, ProfilePrimaries) = PopulatePrimaries(); - - MHC2 = MHC2Tag.LoadFromProfile(profile); - } - - private unsafe CIEXYZ GetIlluminantReletiveWhitePoint() - { - if (profile.TryReadTag(SafeTagSignature.MediaWhitePointTag, out var icc_wtpt)) - { - if (ChromaticAdaptionMatrix == null || profile.HeaderCreator == 0x6170706c /* 'aapl' */) - { - // for profiels without 'chad' tag and Apple profiles, mediaWhitepointTag is illuminant-relative - return icc_wtpt; - } - else - { - // ... otherwise it is PCS-relative - var pcs_wtpt = icc_wtpt; - if (ChromaticAdaptionMatrix != null) - { - return ApplyInverseChad(pcs_wtpt); - } - } - } - if (ChromaticAdaptionMatrix != null) - { - // no wtpt in icc, sum RGB and reverse chad - var pcs_rXYZ = profile.ReadTag(SafeTagSignature.RedColorantTag); - var pcs_gXYZ = profile.ReadTag(SafeTagSignature.GreenColorantTag); - var pcs_bXYZ = profile.ReadTag(SafeTagSignature.BlueColorantTag); - var pcs_sumrgb = pcs_rXYZ + pcs_gXYZ + pcs_bXYZ; - - return ApplyInverseChad(pcs_sumrgb); - } - else - { - throw new Exception("malformed profile: missing wtpt and chad"); - } - } - - protected CIEXYZ ApplyInverseChad(in CIEXYZ val) - { - var vec = InverseChromaticAdaptionMatrix!.Multiply(new DenseVector(new[] { val.X, val.Y, val.Z })); - return new() { X = vec[0], Y = vec[1], Z = vec[2] }; - } - - - /// - /// use lcms transform to get illuminant-relative primaries. - /// - private unsafe (CIEXYZ, RgbPrimaries) PopulatePrimaries() - { - var ctx = new CmsContext(); - - ctx.SetAdaptionState(0); - - var xyzprof = IccProfile.CreateXYZ(); - var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGBDouble, xyzprof, CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default); - var pixels = new ReadOnlySpan(new double[] { - 1, 0, 0, - 0, 1, 0, - 0, 0, 1, - 1, 1, 1 - }); - Span xyz = stackalloc double[3]; - - - t.DoTransform(pixels.Slice(0), xyz, 1); - var rXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; - t.DoTransform(pixels.Slice(3), xyz, 1); - var gXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; - t.DoTransform(pixels.Slice(6), xyz, 1); - var bXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; - t.DoTransform(pixels.Slice(9), xyz, 1); - var wXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; - - - return (wXYZ, new(rXYZ.ToXY(), gXYZ.ToXY(), bXYZ.ToXY(), wXYZ.ToXY())); - - } - - public string GetDescription() - { - return profile.GetInfo(InfoType.Description); - } - - public CIEXYZ GetIlluminantRelativeBlackPoint() - { - // NOTE: mediaBlackPointTag is no longer in ICC standard - if (profile.TryReadTag(SafeTagSignature.MediaBlackPointTag, out var bkpt)) - { - // no chad in profile, bkpt is illuminant-relative - if (ChromaticAdaptionMatrix == null) - { - return bkpt; - } - else - { - return ApplyInverseChad(bkpt); - } - } - - // no bkpt in tag, use lcms transform - var ctx = new CmsContext(); - ctx.SetAdaptionState(0); - var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGB8, IccProfile.CreateXYZ(), CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default); - var input = new ReadOnlySpan(new byte[] { 0, 0, 0 }); - Span outbuf = stackalloc double[3]; - t.DoTransform(input, outbuf, 1); - bkpt = new CIEXYZ { X = outbuf[0], Y = outbuf[1], Z = outbuf[2] }; - return bkpt; - } - - public void WriteIlluminantRelativeMediaBlackPoint(in CIEXYZ value) - { - CIEXYZ valueToWrite; - if (ChromaticAdaptionMatrix != null) - { - var vec = new DenseVector(new double[] { value.X, value.Y, value.Z }); - var pcs_vec = ChromaticAdaptionMatrix * vec; - valueToWrite = new() { X = pcs_vec[0], Y = pcs_vec[1], Z = pcs_vec[2] }; - } - else - { - valueToWrite = value; - } - profile.WriteTag(SafeTagSignature.MediaBlackPointTag, valueToWrite); - } - - public static Matrix GetChromaticAdaptationMatrix(CIEXYZ sourceIlluminant, CIEXYZ targetIlluminant) - { - // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html - // Bradford - var M_a = DenseMatrix.OfArray(new[,] { - { 0.8951, 0.2664, -0.1614 }, - { -0.7502, 1.7135, 0.0367 }, - { 0.0389, -0.0685, 1.0296 }, - }); - - var M_a_inv = M_a.Inverse(); - - var cone_s_vec = M_a * new DenseVector(new double[] { sourceIlluminant.X, sourceIlluminant.Y, sourceIlluminant.Z }); - var cone_t_vec = M_a * new DenseVector(new double[] { targetIlluminant.X, targetIlluminant.Y, targetIlluminant.Z }); - - var M = M_a_inv * DenseMatrix.Build.DenseOfDiagonalVector(cone_t_vec / cone_s_vec) * M_a; - - return M; - } - } - - internal class DeviceIccContext : IccContext - { - CIEXYZ illuminantRelativeBlackPoint; - public double min_nits; - public double max_nits; - ToneCurve profileRedToneCurve; - ToneCurve profileGreenToneCurve; - ToneCurve profileBlueToneCurve; - ToneCurve profileRedReverseToneCurve; - ToneCurve profileGreenReverseToneCurve; - ToneCurve profileBlueReverseToneCurve; - - public ExtraInfoTag? ExtraInfoTag { get; } - - public bool UseChromaticAdaptation { get; set; } - - public DeviceIccContext(IccProfile profile) : base(profile) - { - illuminantRelativeBlackPoint = GetIlluminantRelativeBlackPoint(); - (max_nits, min_nits) = GetProfileLuminance(); - profileRedToneCurve = profile.ReadTag(SafeTagSignature.RedTRCTag); - profileGreenToneCurve = profile.ReadTag(SafeTagSignature.GreenTRCTag); - profileBlueToneCurve = profile.ReadTag(SafeTagSignature.BlueTRCTag); - profileRedReverseToneCurve = profileRedToneCurve.Reverse(); - profileGreenReverseToneCurve = profileGreenToneCurve.Reverse(); - profileBlueReverseToneCurve = profileBlueToneCurve.Reverse(); - - if (profile.ContainsTag(TagSignature.ScreeningDescTag)) - { - var ccDesc = profile.ReadTag(SafeTagSignature.ScreeningDescTag); - var json = ccDesc?.Get("en", "US"); - if (json != null) - { - ExtraInfoTag = JsonSerializer.Deserialize(json); - } - } - } - - public static Matrix RgbToXYZ(RgbPrimaries primaries) - { - var rXYZ = primaries.Red.ToXYZ(); - var gXYZ = primaries.Green.ToXYZ(); - var bXYZ = primaries.Blue.ToXYZ(); - var wXYZ = primaries.White.ToXYZ(); - - var S = DenseMatrix.OfArray(new[,] { - {rXYZ.X, gXYZ.X, bXYZ.X}, - {rXYZ.Y, gXYZ.Y, bXYZ.Y}, - {rXYZ.Z, gXYZ.Z, bXYZ.Z}, - }).Inverse().Multiply(DenseMatrix.OfArray(new[,] { { wXYZ.X }, { wXYZ.Y }, { wXYZ.Z } })); - - var M = DenseMatrix.OfArray(new[,] { - {S[0,0] * rXYZ.X, S[1,0]*gXYZ.X, S[2,0]*bXYZ.X }, - {S[0,0] * rXYZ.Y, S[1,0]*gXYZ.Y, S[2,0]*bXYZ.Y }, - {S[0,0] * rXYZ.Z, S[1,0]*gXYZ.Z, S[2,0]*bXYZ.Z }, - }); - return M; - } - - public static Matrix XYZToRgb(RgbPrimaries primaries) => RgbToXYZ(primaries).Inverse(); - - public static Matrix RgbToRgb(RgbPrimaries from, RgbPrimaries to) - { - var M1 = RgbToXYZ(from); - var M2 = XYZToRgb(to); - return M2 * M1; - } - - public string GetDeviceDescription() - { - var model = profile.GetInfo(InfoType.Model); - if (!string.IsNullOrEmpty(model)) return model; - var desc = GetDescription(); - if (!string.IsNullOrEmpty(desc)) return desc; - return ""; - } - - private (double MaxNits, double MinNits) GetProfileLuminance() - { - var wtpt = IlluminantRelativeWhitePoint; - double max_nits = 80; - if (profile.TryReadTag(SafeTagSignature.LuminanceTag, out var lumi)) - { - max_nits = lumi.Y; - } - var min_nits = 0.0; - var bkpt = illuminantRelativeBlackPoint; - if (bkpt.Y != 0) - { - var bkpt_scale = bkpt.Y / wtpt.Y; - min_nits = max_nits * bkpt_scale; - } - return (max_nits, min_nits); - } - - public IccProfile CreateMhc2CscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB") - { - var wtpt = IlluminantRelativeWhitePoint; - var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); - - var devicePrimaries = ProfilePrimaries; - - var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; - - var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!; - var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc }; - - sourcePrimaries ??= RgbPrimaries.sRGB; - - var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB); - var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB); - - - Matrix user_matrix = DenseMatrix.CreateIdentity(3); - - // pipeline here: input signal converted to XYZ (interpreted as sRGB) - - if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) - { - user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; - } - - // pipeline here: input signal converted to XYZ (interpreted as custom RGB) - - if (UseChromaticAdaptation) - { - user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; - } - - // pipeline here: input signal XYZ adapted to device white point - - // hook: scale white point - - var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); - var mapped_y = source_white_to_xyz[1]; - var profile_max_nits = max_nits * (mapped_y / wtpt.Y); - - // end hook - - user_matrix = XYZToRgb(devicePrimaries) * user_matrix; - - // pipeline here: linear device RGB - - // hack: eliminate fixed sRGB to XYZ transform - - user_matrix = srgb_to_xyz * user_matrix; - - var mhc2_matrix = new double[,] { - { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, - { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, - { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, - }; - - double[,] mhc2_lut; - if (vcgt != null) - { - var lut_size = 1024; - mhc2_lut = new double[3, lut_size]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var input = (float)iinput / (lut_size - 1); - var linear = sourceEotf[ch].EvalF32(input); - var dev_output = deviceOetf[ch].EvalF32(linear); - if (vcgt != null) - { - dev_output = vcgt[ch].EvalF32(dev_output); - } - mhc2_lut[ch, iinput] = dev_output; - } - } - } - else - { - mhc2_lut = new double[,] - { - { 0, 1 }, - { 0, 1 }, - { 0, 1 }, - }; - } - - var mhc2d = new MHC2Tag - { - MinCLL = min_nits, - MaxCLL = profile_max_nits, - Matrix3x4 = mhc2_matrix, - RegammaLUT = mhc2_lut - }; - - var mhc2 = mhc2d.ToBytes(); - - var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE - { - Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(), - Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(), - Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY() - }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); - - outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); - - var outctx = new IccContext(outputProfile); - outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); - - // copy device description from device profile - var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; - - unsafe - { - foreach (var tag in copy_tags) - { - var tag_ptr = profile.ReadTag(tag); - if (tag_ptr != null) - { - outputProfile.WriteTag(tag, tag_ptr); - } - } - } - - // set output profile description - outputProfile.HeaderManufacturer = profile.HeaderManufacturer; - outputProfile.HeaderModel = profile.HeaderModel; - outputProfile.HeaderAttributes = profile.HeaderAttributes; - outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC; - - var new_desc = $"CSC: {sourceDescription} ({GetDeviceDescription()})"; - var new_desc_mlu = new MLU(new_desc); - outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); - - outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); - - outputProfile.ComputeProfileId(); - - return outputProfile; - } - - public IccProfile CreatePQ10DecodeIcc(double? maxBrightnessOverride = null, double? minBrightnessOverride = null) - { - var sourcePrimaries = RgbPrimaries.Rec2020; - var devicePrimaries = ProfilePrimaries; - - Matrix user_matrix = DenseMatrix.CreateIdentity(3); - - - if (UseChromaticAdaptation) - { - user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; - } - - - // var rgb_transform = RgbToRgb(sourcePrimaries, devicePrimaries); - // rgb_transform = XYZToRgb(devicePrimaries) * RgbToXYZ(sourcePrimaries); - // var xyz_transform = RgbToXYZ(sourcePrimaries) * rgb_transform * XYZToRgb(sourcePrimaries); - user_matrix = RgbToXYZ(sourcePrimaries) * XYZToRgb(devicePrimaries) * user_matrix; - - var mhc2_matrix = new double[,] { - { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, - { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, - { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, - }; - - var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); - var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; - - var use_max_nits = maxBrightnessOverride ?? max_nits; - var use_min_nits = minBrightnessOverride ?? min_nits; - - var lut_size = 4096; - var mhc2_lut = new double[3, 4096]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var pqinput = (double)iinput / (lut_size - 1); - var nits = ST2084.SignalToNits(pqinput); - var linear = Math.Max(nits - use_min_nits, 0) / (use_max_nits - use_min_nits); - var dev_output = deviceOetf[ch].EvalF32((float)linear); - if (vcgt != null) - { - dev_output = vcgt[ch].EvalF32(dev_output); - } - // Console.WriteLine($"Channel {ch}: PQ {iinput} -> {nits} cd/m2 -> SDR {dev_output * 255}"); - mhc2_lut[ch, iinput] = dev_output; - } - } - - var mhc2d = new MHC2Tag - { - MinCLL = use_min_nits, - MaxCLL = use_max_nits, - Matrix3x4 = mhc2_matrix, - RegammaLUT = mhc2_lut - }; - - var mhc2 = mhc2d.ToBytes(); - - var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE - { - Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), - Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), - Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() - }, new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve)); - - // copy characteristics from device profile - var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; - unsafe - { - foreach (var tag in copy_tags) - { - var tag_ptr = profile.ReadTag(tag); - if (tag_ptr != null) - { - outputProfile.WriteTag(tag, tag_ptr); - } - } - } - - outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = use_max_nits }); - - // the profile is not read by regular applications - // var outctx = new IccContext(outputProfile); - // outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); - - // set output profile description - outputProfile.HeaderManufacturer = profile.HeaderManufacturer; - outputProfile.HeaderModel = profile.HeaderModel; - outputProfile.HeaderAttributes = profile.HeaderAttributes; - outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC; - - var new_desc = $"CSC: HDR10 to SDR ({GetDeviceDescription()}, {use_max_nits:0} nits)"; - var new_desc_mlu = new MLU(new_desc); - outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); - - outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); - - outputProfile.ComputeProfileId(); - - return outputProfile; - } - - public IccProfile CreateSdrAcmIcc(bool calibrateTransfer) - { - Matrix user_matrix = DenseMatrix.CreateIdentity(3); - - if (UseChromaticAdaptation) - { - user_matrix = GetChromaticAdaptationMatrix(new CIExy { x = 0.3127, y = 0.3290 }.ToXYZ(), ProfilePrimaries.White.ToXYZ()) * user_matrix; - } - - var mhc2_matrix = new double[,] { - { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, - { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, - { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, - }; - - double[,] mhc2_lut; - - var outputprofileTrc = new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve); - var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); - - if (calibrateTransfer) - { - var sourceEotf = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag); - outputprofileTrc = new RgbToneCurve(sourceEotf, sourceEotf, sourceEotf); - - var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; - var lut_size = 1024; - mhc2_lut = new double[3, lut_size]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var input = (float)iinput / (lut_size - 1); - var linear = sourceEotf.EvalF32(input); - var dev_output = deviceOetf[ch].EvalF32(linear); - if (vcgt != null) - { - dev_output = vcgt[ch].EvalF32(dev_output); - } - mhc2_lut[ch, iinput] = dev_output; - } - - } - } - else if (vcgt != null) - { - // move vcgt to mhc2 only - var lut_size = 1024; - mhc2_lut = new double[3, lut_size]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var input = (float)iinput / (lut_size - 1); - var dev_output = vcgt[ch].EvalF32(input); - mhc2_lut[ch, iinput] = dev_output; - } - } - } - else - { - mhc2_lut = new double[,] { { 0, 1 }, { 0, 1 }, { 0, 1 } }; - } - - - var mhc2d = new MHC2Tag - { - MinCLL = min_nits, - MaxCLL = max_nits, - Matrix3x4 = mhc2_matrix, - RegammaLUT = mhc2_lut - }; - - var mhc2 = mhc2d.ToBytes(); - - var devicePrimaries = ProfilePrimaries; - var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE - { - Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), - Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), - Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() - }, outputprofileTrc); - - // copy characteristics from device profile - var copy_tags = new TagSignature[] { - TagSignature.LuminanceTag, - TagSignature.DeviceMfgDescTag, - TagSignature.DeviceModelDescTag, - }; - - unsafe - { - foreach (var tag in copy_tags) - { - var tag_ptr = profile.ReadTag(tag); - outputProfile.WriteTag(tag, tag_ptr); - } - } - - // the profile is not read by regular applications - //var outctx = new IccContext(outputProfile); - //outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); - - // SDR ACM will not work if the profile has negative XYZ colorants (possibly due to limited precision) - foreach (var sig in new ISafeTagSignature[] { SafeTagSignature.RedColorantTag, SafeTagSignature.GreenColorantTag, SafeTagSignature.BlueColorantTag }) - { - var xyz = outputProfile.ReadTag(sig); - if (xyz.X < 0) xyz.X = 0; - if (xyz.Y < 0) xyz.Y = 0; - if (xyz.Z < 0) xyz.Z = 0; - outputProfile.WriteTag(sig, xyz); - } - - // set output profile description - - outputProfile.HeaderManufacturer = profile.HeaderManufacturer; - outputProfile.HeaderModel = profile.HeaderModel; - outputProfile.HeaderAttributes = profile.HeaderAttributes; - outputProfile.HeaderRenderingIntent = profile.HeaderRenderingIntent; - - // outputProfile.ProfileVersion = profile.ProfileVersion; - - var new_desc = $"SDR ACM: {profile.GetInfo(InfoType.Description)}"; - var new_desc_mlu = new MLU(new_desc); - outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); - - outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); - - outputProfile.ComputeProfileId(); - - return outputProfile; - } - - public IccProfile CreateIcc(GenerateProfileCommand command) - { - var maxNits = command.WhiteLuminance; - - //var wtpt = IlluminantRelativeWhitePoint; - - var devicePrimaries = command.DevicePrimaries; // new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 }); - - var srgbTrc = profile.ReadTag(SafeTagSignature.RedTRCTag)!; - - var sourcePrimaries = RgbPrimaries.sRGB; - - var xyz_to_srgb = XYZToRgb(sourcePrimaries); - - Matrix user_matrix = DenseMatrix.CreateIdentity(3); - - // pipeline here: input signal converted to XYZ (interpreted as sRGB) - - if (command.ColorGamut != ColorGamut.Native && !ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) - { - user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; - } - - // pipeline here: input signal converted to XYZ (interpreted as custom RGB) - - if (UseChromaticAdaptation) - { - user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; - } - - // pipeline here: input signal XYZ adapted to device white point - - // hook: scale white point - - //var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); - //var mapped_y = source_white_to_xyz[1]; - //var profile_max_nits = max_nits * (mapped_y / wtpt.Y); - var profile_max_nits = maxNits; - - // end hook - - // pipeline here: linear device RGB - - // hack: eliminate fixed sRGB to XYZ transform - - // CSC - var targetPrimaries = command.ColorGamut switch - { - ColorGamut.Native => devicePrimaries, - ColorGamut.sRGB => RgbPrimaries.sRGB, - ColorGamut.P3 => RgbPrimaries.P3D65, - ColorGamut.Rec2020 => RgbPrimaries.Rec2020, - ColorGamut.AdobeRGB => RgbPrimaries.AdobeRGB, - _ => devicePrimaries - }; - - if (command.ColorGamut != ColorGamut.Native) - { - var srgb_to_xyz = RgbToXYZ(targetPrimaries); - - user_matrix = XYZToRgb(devicePrimaries) * user_matrix; - - user_matrix = srgb_to_xyz * user_matrix; - } - - var mhc2_matrix = new double[,] { - { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, - { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, - { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, - }; + for (var x = 0; x < 3; x++) + { + for (var y = 0; y < 4; y++) + { + Matrix3x4[x, y] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); + } + } + + RegammaLUT = new double[3, lutSize]; + + for (var c = 0; c < 3; c++) + { + var discard = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(8)); + + for (var x = 0; x < lutSize; x++) + { + RegammaLUT[c, x] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4))); + } + } + } + + public void ApplySdrAcm(double whiteLuminance = 120.0, double blackLuminance = 0.0, double gamma = 2.2, double boostPercentage = 0, double shadowDetailBoost = 0) + { + var lutSize = 1024; + var amplifier = boostPercentage == 0 ? 1 : 1 + boostPercentage / 100; + + RegammaLUT = new double[3, lutSize]; + + for (var i = 0; i < lutSize; i++) + { + var (_, value) = CmsFunctions.SrgbAcm(i, whiteLuminance, blackLuminance, gamma, lutSize - 1); + + // Average between sSRGB and Piecewise gamma + if (shadowDetailBoost > 0) + { + var piecewiseValue = (double)i / 1023; + + var correctedBoost = (shadowDetailBoost * (1023 - i)) / 1023; + + value = ((value * (100 - correctedBoost)) + (piecewiseValue * correctedBoost)) / 100; + } + + for (var c = 0; c < 3; c++) + { + //var tempPercentage = boostPercentage * ((double)i / lutSize); + + //var amplifier = tempPercentage == 0 ? 1 : tempPercentage > 0 ? 1 + tempPercentage / 100 : 1 - tempPercentage / 100; + + RegammaLUT[c, i] = value * amplifier; + } + } + } + + public void ApplyPiecewise(double boostPercentage = 0) + { + var lutSize = 1024; + var amplifier = boostPercentage == 0 ? 1 : 1 + boostPercentage / 100; + + RegammaLUT = new double[3, lutSize]; + + for (var i = 0; i < lutSize; i++) + { + var value = (double)i / 1023; + + for (var c = 0; c < 3; c++) + { + RegammaLUT[c, i] = value * amplifier; + } + } + } + + public void ApplyGamma() + { + var lutSize = 1024; + + RegammaLUT = new double[3, lutSize]; + + for (var i = 0; i < lutSize; i++) + { + var value = CmsFunctions.RgbToLinear((double)i / (lutSize - 1), 1.0); + + for (var c = 0; c < 3; c++) + { + RegammaLUT[c, i] = value; + } + } + } + + private static int EncodeS15F16(double value) + { + var x = (int)Math.Round(value * 65536); + return x; + } + + private static double DecodeS15F16(int value) + { + return (double)value / 65536; + } + + public byte[] ToBytes() + { + if (RegammaLUT!.GetLength(0) != 3) throw new ArrayTypeMismatchException(); + var lut_size = RegammaLUT.GetLength(1); + if (lut_size <= 1 || lut_size > 4096) throw new IndexOutOfRangeException(); + if (Matrix3x4!.Length != 12 || Matrix3x4.GetLength(0) != 3) throw new ArrayTypeMismatchException(); + var ms0 = new MemoryStream(); + var writer = new BinaryWriter(ms0); + // type + writer.Write(BinaryPrimitives.ReverseEndianness((int)Signature)); + writer.Write(BinaryPrimitives.ReverseEndianness(0)); + + writer.Write(BinaryPrimitives.ReverseEndianness(lut_size)); + writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MinCLL))); + writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MaxCLL))); + // matrix offset + writer.Write(BinaryPrimitives.ReverseEndianness(36)); + // lut0 offset + writer.Write(BinaryPrimitives.ReverseEndianness(84)); + // lut1 offset + var lut1_offset = 84 + 8 + lut_size * 4; + writer.Write(BinaryPrimitives.ReverseEndianness(lut1_offset)); + // lut2 offset + var lut2_offset = lut1_offset + 8 + lut_size * 4; + writer.Write(BinaryPrimitives.ReverseEndianness(lut2_offset)); + + foreach (var e in Matrix3x4) + { + writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(e))); + } + + for (int ch = 0; ch < 3; ch++) + { + writer.Write(new ReadOnlySpan(new byte[] { (byte)'s', (byte)'f', (byte)'3', (byte)'2', 0, 0, 0, 0 })); + for (int i = 0; i < lut_size; i++) + { + writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(RegammaLUT[ch, i]))); + } + } + writer.Flush(); + return ms0.ToArray(); + } + + internal static MHC2Tag? LoadFromProfile(IccProfile profile) + { + var bytes = profile.RawBytes; + + if (bytes == null) + { + return null; + } + + // 0x4D484332 + var index = -1; + var first = true; + for (var i = 0; i < bytes.Length; i += 4) + { + var chunk = bytes.AsSpan(new Range(i, i + 4)); + + if (chunk[0] == 77 && chunk[1] == 72 && chunk[2] == 67 && chunk[3] == 50) + { + index = i; + + if (first) + { + index = -1; + first = false; + continue; + } + break; + } + } + + if (index == -1) + { + return null; + } + + var mhc2Bytes = bytes.AsSpan(Range.StartAt(index)); + + return new MHC2Tag(mhc2Bytes); + } + } + + internal class ExtraInfoTag + { + public SDRTransferFunction SDRTransferFunction { get; set; } + public double Gamma { get; set; } + public double SDRMinBrightness { get; set; } + public double SDRMaxBrightness { get; set; } + public double SDRBrightnessBoost { get; set; } + public double ShadowDetailBoost { get; set; } + public ColorGamut TargetGamut { get; set; } + } + + internal class IccContext + { + protected IccProfile profile; + public CIEXYZ IlluminantRelativeWhitePoint { get; } + public Matrix? ChromaticAdaptionMatrix { get; } + public Matrix? InverseChromaticAdaptionMatrix { get; } + public RgbPrimaries ProfilePrimaries { get; } + public MHC2Tag? MHC2 { get; set; } + + public IccContext(IccProfile profile) + { + if (profile.PCS != ColorSpaceSignature.XYZ || profile.ColorSpace != ColorSpaceSignature.Rgb) + { + throw new CmsException(CmsError.COLORSPACE_CHECK, "ICC profile is not XYZ->RGB"); + } + this.profile = profile; + { + if (profile.TryReadTag(SafeTagSignature.ChromaticAdaptationTag, out var chad)) + ChromaticAdaptionMatrix = DenseMatrix.OfArray(chad); + InverseChromaticAdaptionMatrix = ChromaticAdaptionMatrix?.Inverse(); + } + (IlluminantRelativeWhitePoint, ProfilePrimaries) = PopulatePrimaries(); + + MHC2 = MHC2Tag.LoadFromProfile(profile); + } + + private unsafe CIEXYZ GetIlluminantReletiveWhitePoint() + { + if (profile.TryReadTag(SafeTagSignature.MediaWhitePointTag, out var icc_wtpt)) + { + if (ChromaticAdaptionMatrix == null || profile.HeaderCreator == 0x6170706c /* 'aapl' */) + { + // for profiels without 'chad' tag and Apple profiles, mediaWhitepointTag is illuminant-relative + return icc_wtpt; + } + else + { + // ... otherwise it is PCS-relative + var pcs_wtpt = icc_wtpt; + if (ChromaticAdaptionMatrix != null) + { + return ApplyInverseChad(pcs_wtpt); + } + } + } + if (ChromaticAdaptionMatrix != null) + { + // no wtpt in icc, sum RGB and reverse chad + var pcs_rXYZ = profile.ReadTag(SafeTagSignature.RedColorantTag); + var pcs_gXYZ = profile.ReadTag(SafeTagSignature.GreenColorantTag); + var pcs_bXYZ = profile.ReadTag(SafeTagSignature.BlueColorantTag); + var pcs_sumrgb = pcs_rXYZ + pcs_gXYZ + pcs_bXYZ; + + return ApplyInverseChad(pcs_sumrgb); + } + else + { + throw new Exception("malformed profile: missing wtpt and chad"); + } + } + + protected CIEXYZ ApplyInverseChad(in CIEXYZ val) + { + var vec = InverseChromaticAdaptionMatrix!.Multiply(new DenseVector(new[] { val.X, val.Y, val.Z })); + return new() { X = vec[0], Y = vec[1], Z = vec[2] }; + } + + + /// + /// use lcms transform to get illuminant-relative primaries. + /// + private unsafe (CIEXYZ, RgbPrimaries) PopulatePrimaries() + { + var ctx = new CmsContext(); + + ctx.SetAdaptionState(0); + + var xyzprof = IccProfile.CreateXYZ(); + var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGBDouble, xyzprof, CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default); + var pixels = new ReadOnlySpan(new double[] { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + 1, 1, 1 + }); + Span xyz = stackalloc double[3]; + + + t.DoTransform(pixels.Slice(0), xyz, 1); + var rXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; + t.DoTransform(pixels.Slice(3), xyz, 1); + var gXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; + t.DoTransform(pixels.Slice(6), xyz, 1); + var bXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; + t.DoTransform(pixels.Slice(9), xyz, 1); + var wXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] }; + + + return (wXYZ, new(rXYZ.ToXY(), gXYZ.ToXY(), bXYZ.ToXY(), wXYZ.ToXY())); + + } + + public string GetDescription() + { + return profile.GetInfo(InfoType.Description); + } + + public CIEXYZ GetIlluminantRelativeBlackPoint() + { + // NOTE: mediaBlackPointTag is no longer in ICC standard + if (profile.TryReadTag(SafeTagSignature.MediaBlackPointTag, out var bkpt)) + { + // no chad in profile, bkpt is illuminant-relative + if (ChromaticAdaptionMatrix == null) + { + return bkpt; + } + else + { + return ApplyInverseChad(bkpt); + } + } + + // no bkpt in tag, use lcms transform + var ctx = new CmsContext(); + ctx.SetAdaptionState(0); + var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGB8, IccProfile.CreateXYZ(), CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default); + var input = new ReadOnlySpan(new byte[] { 0, 0, 0 }); + Span outbuf = stackalloc double[3]; + t.DoTransform(input, outbuf, 1); + bkpt = new CIEXYZ { X = outbuf[0], Y = outbuf[1], Z = outbuf[2] }; + return bkpt; + } + + public void WriteIlluminantRelativeMediaBlackPoint(in CIEXYZ value) + { + CIEXYZ valueToWrite; + if (ChromaticAdaptionMatrix != null) + { + var vec = new DenseVector(new double[] { value.X, value.Y, value.Z }); + var pcs_vec = ChromaticAdaptionMatrix * vec; + valueToWrite = new() { X = pcs_vec[0], Y = pcs_vec[1], Z = pcs_vec[2] }; + } + else + { + valueToWrite = value; + } + profile.WriteTag(SafeTagSignature.MediaBlackPointTag, valueToWrite); + } + + public static Matrix GetChromaticAdaptationMatrix(CIEXYZ sourceIlluminant, CIEXYZ targetIlluminant) + { + // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html + // Bradford + var M_a = DenseMatrix.OfArray(new[,] { + { 0.8951, 0.2664, -0.1614 }, + { -0.7502, 1.7135, 0.0367 }, + { 0.0389, -0.0685, 1.0296 }, + }); + + var M_a_inv = M_a.Inverse(); + + var cone_s_vec = M_a * new DenseVector(new double[] { sourceIlluminant.X, sourceIlluminant.Y, sourceIlluminant.Z }); + var cone_t_vec = M_a * new DenseVector(new double[] { targetIlluminant.X, targetIlluminant.Y, targetIlluminant.Z }); + + var M = M_a_inv * DenseMatrix.Build.DenseOfDiagonalVector(cone_t_vec / cone_s_vec) * M_a; + + return M; + } + } + + internal class DeviceIccContext : IccContext + { + CIEXYZ illuminantRelativeBlackPoint; + public double min_nits; + public double max_nits; + ToneCurve profileRedToneCurve; + ToneCurve profileGreenToneCurve; + ToneCurve profileBlueToneCurve; + ToneCurve profileRedReverseToneCurve; + ToneCurve profileGreenReverseToneCurve; + ToneCurve profileBlueReverseToneCurve; + + public ExtraInfoTag? ExtraInfoTag { get; } + + public bool UseChromaticAdaptation { get; set; } + + public DeviceIccContext(IccProfile profile) : base(profile) + { + illuminantRelativeBlackPoint = GetIlluminantRelativeBlackPoint(); + (max_nits, min_nits) = GetProfileLuminance(); + profileRedToneCurve = profile.ReadTag(SafeTagSignature.RedTRCTag); + profileGreenToneCurve = profile.ReadTag(SafeTagSignature.GreenTRCTag); + profileBlueToneCurve = profile.ReadTag(SafeTagSignature.BlueTRCTag); + profileRedReverseToneCurve = profileRedToneCurve.Reverse(); + profileGreenReverseToneCurve = profileGreenToneCurve.Reverse(); + profileBlueReverseToneCurve = profileBlueToneCurve.Reverse(); + + if (profile.ContainsTag(TagSignature.ScreeningDescTag)) + { + var ccDesc = profile.ReadTag(SafeTagSignature.ScreeningDescTag); + var json = ccDesc?.Get("en", "US"); + if (json != null) + { + ExtraInfoTag = JsonSerializer.Deserialize(json); + } + } + } + + public static Matrix RgbToXYZ(RgbPrimaries primaries) + { + var rXYZ = primaries.Red.ToXYZ(); + var gXYZ = primaries.Green.ToXYZ(); + var bXYZ = primaries.Blue.ToXYZ(); + var wXYZ = primaries.White.ToXYZ(); + + var S = DenseMatrix.OfArray(new[,] { + {rXYZ.X, gXYZ.X, bXYZ.X}, + {rXYZ.Y, gXYZ.Y, bXYZ.Y}, + {rXYZ.Z, gXYZ.Z, bXYZ.Z}, + }).Inverse().Multiply(DenseMatrix.OfArray(new[,] { { wXYZ.X }, { wXYZ.Y }, { wXYZ.Z } })); + + var M = DenseMatrix.OfArray(new[,] { + {S[0,0] * rXYZ.X, S[1,0]*gXYZ.X, S[2,0]*bXYZ.X }, + {S[0,0] * rXYZ.Y, S[1,0]*gXYZ.Y, S[2,0]*bXYZ.Y }, + {S[0,0] * rXYZ.Z, S[1,0]*gXYZ.Z, S[2,0]*bXYZ.Z }, + }); + return M; + } + + public static Matrix XYZToRgb(RgbPrimaries primaries) => RgbToXYZ(primaries).Inverse(); + + public static Matrix RgbToRgb(RgbPrimaries from, RgbPrimaries to) + { + var M1 = RgbToXYZ(from); + var M2 = XYZToRgb(to); + return M2 * M1; + } + + public string GetDeviceDescription() + { + var model = profile.GetInfo(InfoType.Model); + if (!string.IsNullOrEmpty(model)) return model; + var desc = GetDescription(); + if (!string.IsNullOrEmpty(desc)) return desc; + return ""; + } + + private (double MaxNits, double MinNits) GetProfileLuminance() + { + var wtpt = IlluminantRelativeWhitePoint; + double max_nits = 80; + if (profile.TryReadTag(SafeTagSignature.LuminanceTag, out var lumi)) + { + max_nits = lumi.Y; + } + var min_nits = 0.0; + var bkpt = illuminantRelativeBlackPoint; + if (bkpt.Y != 0) + { + var bkpt_scale = bkpt.Y / wtpt.Y; + min_nits = max_nits * bkpt_scale; + } + return (max_nits, min_nits); + } + + public IccProfile CreateMhc2CscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB") + { + var wtpt = IlluminantRelativeWhitePoint; + var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); + + var devicePrimaries = ProfilePrimaries; + + var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; + + var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!; + var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc }; + + sourcePrimaries ??= RgbPrimaries.sRGB; + + var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB); + var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB); + + + Matrix user_matrix = DenseMatrix.CreateIdentity(3); + + // pipeline here: input signal converted to XYZ (interpreted as sRGB) + + if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) + { + user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; + } + + // pipeline here: input signal converted to XYZ (interpreted as custom RGB) + + if (UseChromaticAdaptation) + { + user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; + } + + // pipeline here: input signal XYZ adapted to device white point + + // hook: scale white point + + var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); + var mapped_y = source_white_to_xyz[1]; + var profile_max_nits = max_nits * (mapped_y / wtpt.Y); + + // end hook + + user_matrix = XYZToRgb(devicePrimaries) * user_matrix; + + // pipeline here: linear device RGB + + // hack: eliminate fixed sRGB to XYZ transform + + user_matrix = srgb_to_xyz * user_matrix; + + var mhc2_matrix = new double[,] { + { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, + { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, + { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, + }; + + double[,] mhc2_lut; + if (vcgt != null) + { + var lut_size = 1024; + mhc2_lut = new double[3, lut_size]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var input = (float)iinput / (lut_size - 1); + var linear = sourceEotf[ch].EvalF32(input); + var dev_output = deviceOetf[ch].EvalF32(linear); + if (vcgt != null) + { + dev_output = vcgt[ch].EvalF32(dev_output); + } + mhc2_lut[ch, iinput] = dev_output; + } + } + } + else + { + mhc2_lut = new double[,] + { + { 0, 1 }, + { 0, 1 }, + { 0, 1 }, + }; + } + + var mhc2d = new MHC2Tag + { + MinCLL = min_nits, + MaxCLL = profile_max_nits, + Matrix3x4 = mhc2_matrix, + RegammaLUT = mhc2_lut + }; + + var mhc2 = mhc2d.ToBytes(); + + var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE + { + Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(), + Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(), + Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY() + }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); + + outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); + + var outctx = new IccContext(outputProfile); + outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); + + // copy device description from device profile + var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; + + unsafe + { + foreach (var tag in copy_tags) + { + var tag_ptr = profile.ReadTag(tag); + if (tag_ptr != null) + { + outputProfile.WriteTag(tag, tag_ptr); + } + } + } + + // set output profile description + outputProfile.HeaderManufacturer = profile.HeaderManufacturer; + outputProfile.HeaderModel = profile.HeaderModel; + outputProfile.HeaderAttributes = profile.HeaderAttributes; + outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC; + + var new_desc = $"CSC: {sourceDescription} ({GetDeviceDescription()})"; + var new_desc_mlu = new MLU(new_desc); + outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); + + outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); + + outputProfile.ComputeProfileId(); + + return outputProfile; + } + + public IccProfile CreatePQ10DecodeIcc(double? maxBrightnessOverride = null, double? minBrightnessOverride = null) + { + var sourcePrimaries = RgbPrimaries.Rec2020; + var devicePrimaries = ProfilePrimaries; + + Matrix user_matrix = DenseMatrix.CreateIdentity(3); + + + if (UseChromaticAdaptation) + { + user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; + } + + + // var rgb_transform = RgbToRgb(sourcePrimaries, devicePrimaries); + // rgb_transform = XYZToRgb(devicePrimaries) * RgbToXYZ(sourcePrimaries); + // var xyz_transform = RgbToXYZ(sourcePrimaries) * rgb_transform * XYZToRgb(sourcePrimaries); + user_matrix = RgbToXYZ(sourcePrimaries) * XYZToRgb(devicePrimaries) * user_matrix; + + var mhc2_matrix = new double[,] { + { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, + { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, + { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, + }; + + var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); + var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; + + var use_max_nits = maxBrightnessOverride ?? max_nits; + var use_min_nits = minBrightnessOverride ?? min_nits; + + var lut_size = 4096; + var mhc2_lut = new double[3, 4096]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var pqinput = (double)iinput / (lut_size - 1); + var nits = ST2084.SignalToNits(pqinput); + var linear = Math.Max(nits - use_min_nits, 0) / (use_max_nits - use_min_nits); + var dev_output = deviceOetf[ch].EvalF32((float)linear); + if (vcgt != null) + { + dev_output = vcgt[ch].EvalF32(dev_output); + } + // Console.WriteLine($"Channel {ch}: PQ {iinput} -> {nits} cd/m2 -> SDR {dev_output * 255}"); + mhc2_lut[ch, iinput] = dev_output; + } + } + + var mhc2d = new MHC2Tag + { + MinCLL = use_min_nits, + MaxCLL = use_max_nits, + Matrix3x4 = mhc2_matrix, + RegammaLUT = mhc2_lut + }; + + var mhc2 = mhc2d.ToBytes(); + + var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE + { + Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), + Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), + Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() + }, new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve)); + + // copy characteristics from device profile + var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; + unsafe + { + foreach (var tag in copy_tags) + { + var tag_ptr = profile.ReadTag(tag); + if (tag_ptr != null) + { + outputProfile.WriteTag(tag, tag_ptr); + } + } + } + + outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = use_max_nits }); + + // the profile is not read by regular applications + // var outctx = new IccContext(outputProfile); + // outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); + + // set output profile description + outputProfile.HeaderManufacturer = profile.HeaderManufacturer; + outputProfile.HeaderModel = profile.HeaderModel; + outputProfile.HeaderAttributes = profile.HeaderAttributes; + outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC; + + var new_desc = $"CSC: HDR10 to SDR ({GetDeviceDescription()}, {use_max_nits:0} nits)"; + var new_desc_mlu = new MLU(new_desc); + outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); + + outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); + + outputProfile.ComputeProfileId(); + + return outputProfile; + } + + public IccProfile CreateSdrAcmIcc(bool calibrateTransfer) + { + Matrix user_matrix = DenseMatrix.CreateIdentity(3); + + if (UseChromaticAdaptation) + { + user_matrix = GetChromaticAdaptationMatrix(new CIExy { x = 0.3127, y = 0.3290 }.ToXYZ(), ProfilePrimaries.White.ToXYZ()) * user_matrix; + } + + var mhc2_matrix = new double[,] { + { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, + { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, + { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, + }; + + double[,] mhc2_lut; + + var outputprofileTrc = new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve); + var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); + + if (calibrateTransfer) + { + var sourceEotf = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag); + outputprofileTrc = new RgbToneCurve(sourceEotf, sourceEotf, sourceEotf); + + var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; + var lut_size = 1024; + mhc2_lut = new double[3, lut_size]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var input = (float)iinput / (lut_size - 1); + var linear = sourceEotf.EvalF32(input); + var dev_output = deviceOetf[ch].EvalF32(linear); + if (vcgt != null) + { + dev_output = vcgt[ch].EvalF32(dev_output); + } + mhc2_lut[ch, iinput] = dev_output; + } + + } + } + else if (vcgt != null) + { + // move vcgt to mhc2 only + var lut_size = 1024; + mhc2_lut = new double[3, lut_size]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var input = (float)iinput / (lut_size - 1); + var dev_output = vcgt[ch].EvalF32(input); + mhc2_lut[ch, iinput] = dev_output; + } + } + } + else + { + mhc2_lut = new double[,] { { 0, 1 }, { 0, 1 }, { 0, 1 } }; + } + + + var mhc2d = new MHC2Tag + { + MinCLL = min_nits, + MaxCLL = max_nits, + Matrix3x4 = mhc2_matrix, + RegammaLUT = mhc2_lut + }; + + var mhc2 = mhc2d.ToBytes(); + + var devicePrimaries = ProfilePrimaries; + var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE + { + Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), + Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), + Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() + }, outputprofileTrc); + + // copy characteristics from device profile + var copy_tags = new TagSignature[] { + TagSignature.LuminanceTag, + TagSignature.DeviceMfgDescTag, + TagSignature.DeviceModelDescTag, + }; + + unsafe + { + foreach (var tag in copy_tags) + { + var tag_ptr = profile.ReadTag(tag); + outputProfile.WriteTag(tag, tag_ptr); + } + } + + // the profile is not read by regular applications + //var outctx = new IccContext(outputProfile); + //outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); + + // SDR ACM will not work if the profile has negative XYZ colorants (possibly due to limited precision) + foreach (var sig in new ISafeTagSignature[] { SafeTagSignature.RedColorantTag, SafeTagSignature.GreenColorantTag, SafeTagSignature.BlueColorantTag }) + { + var xyz = outputProfile.ReadTag(sig); + if (xyz.X < 0) xyz.X = 0; + if (xyz.Y < 0) xyz.Y = 0; + if (xyz.Z < 0) xyz.Z = 0; + outputProfile.WriteTag(sig, xyz); + } + + // set output profile description + + outputProfile.HeaderManufacturer = profile.HeaderManufacturer; + outputProfile.HeaderModel = profile.HeaderModel; + outputProfile.HeaderAttributes = profile.HeaderAttributes; + outputProfile.HeaderRenderingIntent = profile.HeaderRenderingIntent; + + // outputProfile.ProfileVersion = profile.ProfileVersion; + + var new_desc = $"SDR ACM: {profile.GetInfo(InfoType.Description)}"; + var new_desc_mlu = new MLU(new_desc); + outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); + + outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); + + outputProfile.ComputeProfileId(); + + return outputProfile; + } + + public IccProfile CreateIcc(GenerateProfileCommand command) + { + var maxNits = command.WhiteLuminance; + + //var wtpt = IlluminantRelativeWhitePoint; + + var devicePrimaries = command.DevicePrimaries; // new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 }); + + var srgbTrc = profile.ReadTag(SafeTagSignature.RedTRCTag)!; + + var sourcePrimaries = RgbPrimaries.sRGB; + + var xyz_to_srgb = XYZToRgb(sourcePrimaries); + + Matrix user_matrix = DenseMatrix.CreateIdentity(3); + + // pipeline here: input signal converted to XYZ (interpreted as sRGB) + + if (command.ColorGamut != ColorGamut.Native && !ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) + { + user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; + } + + // pipeline here: input signal converted to XYZ (interpreted as custom RGB) + + if (UseChromaticAdaptation) + { + user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; + } + + // pipeline here: input signal XYZ adapted to device white point + + // hook: scale white point + + //var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); + //var mapped_y = source_white_to_xyz[1]; + //var profile_max_nits = max_nits * (mapped_y / wtpt.Y); + var profile_max_nits = maxNits; + + // end hook + + // pipeline here: linear device RGB + + // hack: eliminate fixed sRGB to XYZ transform + + // CSC + var targetPrimaries = command.ColorGamut switch + { + ColorGamut.Native => devicePrimaries, + ColorGamut.sRGB => RgbPrimaries.sRGB, + ColorGamut.P3 => RgbPrimaries.P3D65, + ColorGamut.Rec2020 => RgbPrimaries.Rec2020, + ColorGamut.AdobeRGB => RgbPrimaries.AdobeRGB, + _ => devicePrimaries + }; + + if (command.ColorGamut != ColorGamut.Native) + { + var srgb_to_xyz = RgbToXYZ(targetPrimaries); + + user_matrix = XYZToRgb(devicePrimaries) * user_matrix; + + user_matrix = srgb_to_xyz * user_matrix; + } + + var mhc2_matrix = new double[,] { + { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, + { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, + { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, + }; - MHC2 = new MHC2Tag - { - MinCLL = command.MinCLL, - MaxCLL = command.MaxCLL - }; + MHC2 = new MHC2Tag + { + MinCLL = command.MinCLL, + MaxCLL = command.MaxCLL + }; - //if (!command.IsHDRProfile) - //{ - // MHC2.ApplyGamma(); - //} - if (!command.IsHDRProfile || command.SDRTransferFunction == SDRTransferFunction.Piecewise) - { - MHC2.ApplyPiecewise(command.SDRBrightnessBoost); - } - else if (command.SDRTransferFunction == SDRTransferFunction.PurePower) - { - MHC2.ApplySdrAcm(command.SDRMaxBrightness, command.SDRMinBrightness, command.Gamma, command.SDRBrightnessBoost, command.ShadowDetailBoost); - } - else if (command.SDRTransferFunction == SDRTransferFunction.BT_1886) - { - MHC2.ApplySdrAcm(120, 0.03, 2.4, command.SDRBrightnessBoost, command.ShadowDetailBoost); - } + //if (!command.IsHDRProfile) + //{ + // MHC2.ApplyGamma(); + //} + if (!command.IsHDRProfile || command.SDRTransferFunction == SDRTransferFunction.Piecewise) + { + MHC2.ApplyPiecewise(command.SDRBrightnessBoost); + } + else if (command.SDRTransferFunction == SDRTransferFunction.PurePower) + { + MHC2.ApplySdrAcm(command.SDRMaxBrightness, command.SDRMinBrightness, command.Gamma, command.SDRBrightnessBoost, command.ShadowDetailBoost); + } + else if (command.SDRTransferFunction == SDRTransferFunction.BT_1886) + { + MHC2.ApplySdrAcm(120, 0.03, 2.4, command.SDRBrightnessBoost, command.ShadowDetailBoost); + } - MHC2.Matrix3x4 = mhc2_matrix; + MHC2.Matrix3x4 = mhc2_matrix; - var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE - { - Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), - Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), - Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() - }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); + var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE + { + Red = devicePrimaries.Red.ToXYZ().ToCIExyY(), + Green = devicePrimaries.Green.ToXYZ().ToCIExyY(), + Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY() + }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); - outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); + outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); - var outctx = new IccContext(outputProfile); - outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); + var outctx = new IccContext(outputProfile); + outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); - // copy device description from device profile - var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; + // copy device description from device profile + var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; - unsafe - { - foreach (var tag in copy_tags) - { - var tag_ptr = profile.ReadTag(tag); - if (tag_ptr != null) - { - outputProfile.WriteTag(tag, tag_ptr); - } - } - } + unsafe + { + foreach (var tag in copy_tags) + { + var tag_ptr = profile.ReadTag(tag); + if (tag_ptr != null) + { + outputProfile.WriteTag(tag, tag_ptr); + } + } + } - // set output profile description - outputProfile.HeaderManufacturer = profile.HeaderManufacturer; - outputProfile.HeaderModel = profile.HeaderModel; - outputProfile.HeaderAttributes = profile.HeaderAttributes; - outputProfile.HeaderRenderingIntent = RenderingIntent.PERCEPTUAL; + // set output profile description + outputProfile.HeaderManufacturer = profile.HeaderManufacturer; + outputProfile.HeaderModel = profile.HeaderModel; + outputProfile.HeaderAttributes = profile.HeaderAttributes; + outputProfile.HeaderRenderingIntent = RenderingIntent.PERCEPTUAL; - var descAppendix = $" ({GetDeviceDescription()})"; - var new_desc = command.Description ?? ""; - if (!new_desc.Contains(descAppendix)) - { - new_desc += descAppendix; - } - var new_desc_mlu = new MLU(new_desc); - outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); - - var extraInfoTag = new ExtraInfoTag - { - SDRTransferFunction = command.SDRTransferFunction, - Gamma = command.Gamma, - SDRMinBrightness = command.SDRMinBrightness, - SDRMaxBrightness = command.SDRMaxBrightness, - SDRBrightnessBoost = command.SDRBrightnessBoost, - ShadowDetailBoost = command.ShadowDetailBoost, - TargetGamut = command.ColorGamut - }; - - var ccDesc = JsonSerializer.Serialize(extraInfoTag); - - outputProfile.WriteTag(SafeTagSignature.ScreeningDescTag, new MLU(ccDesc)); - - outputProfile.WriteRawTag(MHC2Tag.Signature, MHC2.ToBytes()); - - outputProfile.ComputeProfileId(); - - return outputProfile; - } - - public IccProfile CreateCscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB") - { - var wtpt = IlluminantRelativeWhitePoint; - var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); - - var customPrimaries = new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 }); - - var devicePrimaries = customPrimaries; - - var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; - - var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!; - var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc }; - - sourcePrimaries ??= RgbPrimaries.sRGB; - - var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB); - var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB); - - - Matrix user_matrix = DenseMatrix.CreateIdentity(3); - - // pipeline here: input signal converted to XYZ (interpreted as sRGB) - - if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) - { - user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; - } - - // pipeline here: input signal converted to XYZ (interpreted as custom RGB) - - if (UseChromaticAdaptation) - { - user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; - } - - // pipeline here: input signal XYZ adapted to device white point - - // hook: scale white point - - var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); - var mapped_y = source_white_to_xyz[1]; - var profile_max_nits = max_nits * (mapped_y / wtpt.Y); - - // end hook - - user_matrix = XYZToRgb(devicePrimaries) * user_matrix; - - // pipeline here: linear device RGB - - // hack: eliminate fixed sRGB to XYZ transform - - user_matrix = srgb_to_xyz * user_matrix; - - var mhc2_matrix = new double[,] { - { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, - { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, - { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, - }; - - double[,] mhc2_lut; - if (vcgt != null) - { - var lut_size = 1024; - mhc2_lut = new double[3, lut_size]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var input = (float)iinput / (lut_size - 1); - var linear = sourceEotf[ch].EvalF32(input); - var dev_output = deviceOetf[ch].EvalF32(linear); - if (vcgt != null) - { - dev_output = vcgt[ch].EvalF32(dev_output); - } - mhc2_lut[ch, iinput] = dev_output; - } - } - } - else - { - var lut_size = 1024; - mhc2_lut = new double[3, lut_size]; - for (int ch = 0; ch < 3; ch++) - { - for (int iinput = 0; iinput < lut_size; iinput++) - { - var input = (float)iinput / (lut_size - 1); - var linear = sourceEotf[ch].EvalF32(input); + var descAppendix = $" ({GetDeviceDescription()})"; + var new_desc = command.Description ?? ""; + if (!new_desc.Contains(descAppendix)) + { + new_desc += descAppendix; + } + var new_desc_mlu = new MLU(new_desc); + outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); + + var extraInfoTag = new ExtraInfoTag + { + SDRTransferFunction = command.SDRTransferFunction, + Gamma = command.Gamma, + SDRMinBrightness = command.SDRMinBrightness, + SDRMaxBrightness = command.SDRMaxBrightness, + SDRBrightnessBoost = command.SDRBrightnessBoost, + ShadowDetailBoost = command.ShadowDetailBoost, + TargetGamut = command.ColorGamut + }; + + var ccDesc = JsonSerializer.Serialize(extraInfoTag); + + outputProfile.WriteTag(SafeTagSignature.ScreeningDescTag, new MLU(ccDesc)); + + outputProfile.WriteRawTag(MHC2Tag.Signature, MHC2.ToBytes()); + + outputProfile.ComputeProfileId(); + + return outputProfile; + } + + public IccProfile CreateCscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB") + { + var wtpt = IlluminantRelativeWhitePoint; + var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray(); + + var customPrimaries = new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 }); + + var devicePrimaries = customPrimaries; + + var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve }; + + var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!; + var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc }; + + sourcePrimaries ??= RgbPrimaries.sRGB; + + var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB); + var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB); + + + Matrix user_matrix = DenseMatrix.CreateIdentity(3); + + // pipeline here: input signal converted to XYZ (interpreted as sRGB) + + if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB)) + { + user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix; + } + + // pipeline here: input signal converted to XYZ (interpreted as custom RGB) + + if (UseChromaticAdaptation) + { + user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix; + } + + // pipeline here: input signal XYZ adapted to device white point + + // hook: scale white point + + var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 }); + var mapped_y = source_white_to_xyz[1]; + var profile_max_nits = max_nits * (mapped_y / wtpt.Y); + + // end hook + + user_matrix = XYZToRgb(devicePrimaries) * user_matrix; + + // pipeline here: linear device RGB + + // hack: eliminate fixed sRGB to XYZ transform + + user_matrix = srgb_to_xyz * user_matrix; + + var mhc2_matrix = new double[,] { + { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 }, + { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 }, + { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 }, + }; + + double[,] mhc2_lut; + if (vcgt != null) + { + var lut_size = 1024; + mhc2_lut = new double[3, lut_size]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var input = (float)iinput / (lut_size - 1); + var linear = sourceEotf[ch].EvalF32(input); + var dev_output = deviceOetf[ch].EvalF32(linear); + if (vcgt != null) + { + dev_output = vcgt[ch].EvalF32(dev_output); + } + mhc2_lut[ch, iinput] = dev_output; + } + } + } + else + { + var lut_size = 1024; + mhc2_lut = new double[3, lut_size]; + for (int ch = 0; ch < 3; ch++) + { + for (int iinput = 0; iinput < lut_size; iinput++) + { + var input = (float)iinput / (lut_size - 1); + var linear = sourceEotf[ch].EvalF32(input); - var dev_output = CmsFunctions.RgbToLinear(linear, 1.6); + var dev_output = CmsFunctions.RgbToLinear(linear, 1.6); - //var dev_output = deviceOetf[ch].EvalF32(input); - //if (vcgt != null) - //{ - // dev_output = vcgt[ch].EvalF32(dev_output); - //} - mhc2_lut[ch, iinput] = dev_output; - } - } - } - - var mhc2d = new MHC2Tag - { - MinCLL = min_nits, - MaxCLL = profile_max_nits, - Matrix3x4 = mhc2_matrix, - RegammaLUT = mhc2_lut - }; - - var mhc2 = mhc2d.ToBytes(); - - var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE - { - Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(), - Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(), - Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY() - }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); - - outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); - - var outctx = new IccContext(outputProfile); - outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); - - // copy device description from device profile - var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; - - unsafe - { - foreach (var tag in copy_tags) - { - var tag_ptr = profile.ReadTag(tag); - if (tag_ptr != null) - { - outputProfile.WriteTag(tag, tag_ptr); - } - } - } - - // set output profile description - outputProfile.HeaderManufacturer = profile.HeaderManufacturer; - outputProfile.HeaderModel = profile.HeaderModel; - outputProfile.HeaderAttributes = profile.HeaderAttributes; - outputProfile.HeaderRenderingIntent = RenderingIntent.PERCEPTUAL; - - var new_desc = $"XCSC: {sourceDescription} ({GetDeviceDescription()})"; - var new_desc_mlu = new MLU(new_desc); - outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); - - outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); - - outputProfile.ComputeProfileId(); - - return outputProfile; - } - - } + //var dev_output = deviceOetf[ch].EvalF32(input); + //if (vcgt != null) + //{ + // dev_output = vcgt[ch].EvalF32(dev_output); + //} + mhc2_lut[ch, iinput] = dev_output; + } + } + } + + var mhc2d = new MHC2Tag + { + MinCLL = min_nits, + MaxCLL = profile_max_nits, + Matrix3x4 = mhc2_matrix, + RegammaLUT = mhc2_lut + }; + + var mhc2 = mhc2d.ToBytes(); + + var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE + { + Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(), + Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(), + Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY() + }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc)); + + outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits }); + + var outctx = new IccContext(outputProfile); + outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint); + + // copy device description from device profile + var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag }; + + unsafe + { + foreach (var tag in copy_tags) + { + var tag_ptr = profile.ReadTag(tag); + if (tag_ptr != null) + { + outputProfile.WriteTag(tag, tag_ptr); + } + } + } + + // set output profile description + outputProfile.HeaderManufacturer = profile.HeaderManufacturer; + outputProfile.HeaderModel = profile.HeaderModel; + outputProfile.HeaderAttributes = profile.HeaderAttributes; + outputProfile.HeaderRenderingIntent = RenderingIntent.PERCEPTUAL; + + var new_desc = $"XCSC: {sourceDescription} ({GetDeviceDescription()})"; + var new_desc_mlu = new MLU(new_desc); + outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu); + + outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2); + + outputProfile.ComputeProfileId(); + + return outputProfile; + } + + } } diff --git a/MHC2Gen/MHC2Wrapper.cs b/MHC2Gen/MHC2Wrapper.cs index 58b2277..22f1038 100644 --- a/MHC2Gen/MHC2Wrapper.cs +++ b/MHC2Gen/MHC2Wrapper.cs @@ -39,7 +39,7 @@ public class MHC2Wrapper return (deviceContext.min_nits, deviceContext.max_nits); } - public static GenerateProfileCommand LoadProfile(string fileName, bool isHDRProfile) + public static GenerateProfileCommand LoadProfile(string fileName, bool isAssociatedAsHdr) { if (fileName.IndexOf("\\") == -1 || !File.Exists(fileName)) { @@ -56,7 +56,7 @@ public class MHC2Wrapper { Description = deviceContext.GetDescription(), HasExtraInfo = deviceContext.ExtraInfoTag != null, - IsHDRProfile = isHDRProfile, + IsHDRProfile = isAssociatedAsHdr, BlackLuminance = deviceContext.min_nits, WhiteLuminance = deviceContext.max_nits, ColorGamut = deviceContext.ExtraInfoTag?.TargetGamut ?? ColorGamut.Native, diff --git a/Shared/Common/Utils.cs b/Shared/Common/Utils.cs index 4800634..0f46470 100644 --- a/Shared/Common/Utils.cs +++ b/Shared/Common/Utils.cs @@ -753,13 +753,21 @@ The best and suggested method to provide this is via a Windows Service. Only whe return SHA256CheckSum(filePath1) == SHA256CheckSum(filePath2); } - public static string ToUnitString(this uint value, int div = 1000, string units = "MHz") => $"{value / div}{units}"; - public static string ToUnitString(this int value, int div = 1000, string units = "MHz") => $"{value / div}{units}"; + public static string ToKiloUnitString(this uint value, int div = 1000, string units = "MHz") => $"{value / div}{units}"; + public static string ToKiloUnitString(this int value, int div = 1000, string units = "MHz") => $"{value / div}{units}"; + public static string ToUnitString(this uint value, string units = "MHz") => $"{value}{units}"; + public static string ToSignedUnitString(this int value, string units = "MHz") => $"{(value >= 0 ? "+" + value : value)}{units}"; + public static string ToSignedUnitString(this uint value, string units = "MHz") => $"{(value >= 0 ? "+" + value : value)}{units}"; public static string ToUnitString(string units = "°", params double?[] values) { var notNullValues = values.Where(v => v.HasValue).Select(v => $"{v}{units}"); return string.Join("/", notNullValues); } + + public static System.Windows.Application EnsureApplication() + { + return System.Windows.Application.Current ?? new System.Windows.Application { ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown }; + } } } diff --git a/Shared/Contracts/DisplayInfo/ColorProfileDto.cs b/Shared/Contracts/DisplayInfo/ColorProfileDto.cs index a53e709..31e316c 100644 --- a/Shared/Contracts/DisplayInfo/ColorProfileDto.cs +++ b/Shared/Contracts/DisplayInfo/ColorProfileDto.cs @@ -4,45 +4,50 @@ namespace ColorControl.Shared.Contracts.DisplayInfo; public class ColorProfileDto { - public string Name { get; set; } - public string Description { get; set; } - public bool HasExtraInfo { get; set; } - public bool IsHDRProfile { get; set; } - public RgbPrimariesDto DevicePrimaries { get; set; } = new RgbPrimariesDto(RgbPrimaries.sRGB); - public SDRTransferFunction SDRTransferFunction { get; set; } - public ColorGamut ColorGamut { get; set; } - public double MinCLL { get; set; } - public double MaxCLL { get; set; } - public double WhiteLuminance { get; set; } - public double BlackLuminance { get; set; } - public double SDRMinBrightness { get; set; } - public double SDRMaxBrightness { get; set; } - public double SDRBrightnessBoost { get; set; } - public double ShadowDetailBoost { get; set; } - public double Gamma { get; set; } = 2.2; + public string Name { get; set; } + public string Description { get; set; } + public bool HasExtraInfo { get; set; } + public bool IsHDRProfile { get; set; } + public RgbPrimariesDto DevicePrimaries { get; set; } = new RgbPrimariesDto(RgbPrimaries.sRGB); + public SDRTransferFunction SDRTransferFunction { get; set; } + public ColorGamut ColorGamut { get; set; } + public double MinCLL { get; set; } + public double MaxCLL { get; set; } + public double WhiteLuminance { get; set; } + public double BlackLuminance { get; set; } + public double SDRMinBrightness { get; set; } + public double SDRMaxBrightness { get; set; } + public double SDRBrightnessBoost { get; set; } + public double ShadowDetailBoost { get; set; } + public double Gamma { get; set; } = 2.2; - public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo) - { - BlackLuminance = displayColorInfo.BlackLuminance; - WhiteLuminance = displayColorInfo.WhiteLuminance; - MinCLL = displayColorInfo.MinCLL; - MaxCLL = displayColorInfo.MaxCLL; - DevicePrimaries = new RgbPrimariesDto(displayColorInfo.RgbPrimaries); - } + public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo) + { + BlackLuminance = displayColorInfo.BlackLuminance; + WhiteLuminance = displayColorInfo.WhiteLuminance; + MinCLL = displayColorInfo.MinCLL; + MaxCLL = displayColorInfo.MaxCLL; + DevicePrimaries = new RgbPrimariesDto(displayColorInfo.RgbPrimaries); + } - public static ColorProfileDto CreateDefault(bool isHdrProfile) - { - return new ColorProfileDto - { - IsHDRProfile = isHdrProfile, - HasExtraInfo = true, - MinCLL = 0, - MaxCLL = 1000, - BlackLuminance = 0, - WhiteLuminance = 1000, - SDRMinBrightness = 0, - SDRMaxBrightness = 100, - SDRTransferFunction = isHdrProfile ? SDRTransferFunction.Piecewise : SDRTransferFunction.PurePower - }; - } + public override string ToString() + { + return Name == "None" ? Name : $"{(IsHDRProfile ? "HDR" : "SDR")}: {Name}"; + } + + public static ColorProfileDto CreateDefault(bool isHdrProfile) + { + return new ColorProfileDto + { + IsHDRProfile = isHdrProfile, + HasExtraInfo = true, + MinCLL = 0, + MaxCLL = 1000, + BlackLuminance = 0, + WhiteLuminance = 1000, + SDRMinBrightness = 0, + SDRMaxBrightness = 100, + SDRTransferFunction = isHdrProfile ? SDRTransferFunction.Piecewise : SDRTransferFunction.PurePower + }; + } } diff --git a/Shared/Contracts/DisplayInfo/DisplayInfoDto.cs b/Shared/Contracts/DisplayInfo/DisplayInfoDto.cs index a120200..5adf55a 100644 --- a/Shared/Contracts/DisplayInfo/DisplayInfoDto.cs +++ b/Shared/Contracts/DisplayInfo/DisplayInfoDto.cs @@ -4,78 +4,79 @@ namespace ColorControl.Shared.Contracts.DisplayInfo; public class ColorPoint { - public double X { get; set; } - public double Y { get; set; } + public double X { get; set; } + public double Y { get; set; } } public class RgbPrimariesDto { - public RgbPrimariesDto(RgbPrimaries primaries) - { - Red = new ColorPoint { X = primaries.Red.x, Y = primaries.Red.y }; - Green = new ColorPoint { X = primaries.Green.x, Y = primaries.Green.y }; - Blue = new ColorPoint { X = primaries.Blue.x, Y = primaries.Blue.y }; - White = new ColorPoint { X = primaries.White.x, Y = primaries.White.y }; - } + public RgbPrimariesDto(RgbPrimaries primaries) + { + Red = new ColorPoint { X = primaries.Red.x, Y = primaries.Red.y }; + Green = new ColorPoint { X = primaries.Green.x, Y = primaries.Green.y }; + Blue = new ColorPoint { X = primaries.Blue.x, Y = primaries.Blue.y }; + White = new ColorPoint { X = primaries.White.x, Y = primaries.White.y }; + } - public RgbPrimariesDto(RgbPrimariesDto primaries) - { - Red = new ColorPoint { X = primaries.Red.X, Y = primaries.Red.Y }; - Green = new ColorPoint { X = primaries.Green.X, Y = primaries.Green.Y }; - Blue = new ColorPoint { X = primaries.Blue.X, Y = primaries.Blue.Y }; - White = new ColorPoint { X = primaries.White.X, Y = primaries.White.Y }; - } + public RgbPrimariesDto(RgbPrimariesDto primaries) + { + Red = new ColorPoint { X = primaries.Red.X, Y = primaries.Red.Y }; + Green = new ColorPoint { X = primaries.Green.X, Y = primaries.Green.Y }; + Blue = new ColorPoint { X = primaries.Blue.X, Y = primaries.Blue.Y }; + White = new ColorPoint { X = primaries.White.X, Y = primaries.White.Y }; + } - public RgbPrimariesDto() { } + public RgbPrimariesDto() { } - public ColorPoint Red { get; set; } - public ColorPoint Green { get; set; } - public ColorPoint Blue { get; set; } - public ColorPoint White { get; set; } + public ColorPoint Red { get; set; } + public ColorPoint Green { get; set; } + public ColorPoint Blue { get; set; } + public ColorPoint White { get; set; } - public RgbPrimaries ToInternal() - { - return new RgbPrimaries( - new CIExy { x = Red.X, y = Red.Y }, - new CIExy { x = Green.X, y = Green.Y }, - new CIExy { x = Blue.X, y = Blue.Y }, - new CIExy { x = White.X, y = White.Y } - ); - } + public RgbPrimaries ToInternal() + { + return new RgbPrimaries( + new CIExy { x = Red.X, y = Red.Y }, + new CIExy { x = Green.X, y = Green.Y }, + new CIExy { x = Blue.X, y = Blue.Y }, + new CIExy { x = White.X, y = White.Y } + ); + } } public class DisplayColorInfo { - public DisplayPrimariesSource DisplayPrimariesSource { get; set; } - public string CustomName { get; set; } - public RgbPrimariesDto RgbPrimaries { get; set; } - public double MinCLL { get; set; } - public double MaxCLL { get; set; } - public double BlackLuminance { get; set; } - public double WhiteLuminance { get; set; } + public DisplayPrimariesSource DisplayPrimariesSource { get; set; } + public string CustomName { get; set; } + public RgbPrimariesDto RgbPrimaries { get; set; } + public double MinCLL { get; set; } + public double MaxCLL { get; set; } + public double BlackLuminance { get; set; } + public double WhiteLuminance { get; set; } } public class DisplayInfoDto { - public bool IsActive { get; set; } - public string DisplayName { get; set; } - public string AdapterName { get; set; } - public string DeviceName { get; set; } - public string DevicePath { get; set; } - public string DisplayId { get; set; } + public bool IsActive { get; set; } + public string DisplayName { get; set; } + public string AdapterName { get; set; } + public string DeviceName { get; set; } + public string DevicePath { get; set; } + public string DisplayId { get; set; } - public bool IsAdvancedColorSupported { get; set; } - public bool IsAdvancedColorEnabled { get; set; } + public bool IsAdvancedColorSupported { get; set; } + public bool IsAdvancedColorEnabled { get; set; } - public List DisplayColors { get; set; } = new(); - public List ColorProfiles { get; set; } = new(); - public string DefaultColorProfile { get; set; } + public List DisplayColors { get; set; } = new(); + public List ColorProfiles { get; set; } = new(); + public string DefaultSdrColorProfile { get; set; } + public string DefaultHdrColorProfile { get; set; } - public bool IsHdrSupportedAndEnabled => IsAdvancedColorSupported && IsAdvancedColorEnabled; + public bool IsHdrSupportedAndEnabled => IsAdvancedColorSupported && IsAdvancedColorEnabled; - public override string ToString() - { - return $"{DisplayName} on {AdapterName}"; - } + public override string ToString() + { + return $"{DisplayName} on {AdapterName}"; + } } diff --git a/Shared/Contracts/LG/LgDeviceOptions.cs b/Shared/Contracts/LG/LgDeviceOptions.cs index 0895059..ebc27fb 100644 --- a/Shared/Contracts/LG/LgDeviceOptions.cs +++ b/Shared/Contracts/LG/LgDeviceOptions.cs @@ -2,22 +2,24 @@ public class LgDeviceOptions { - public bool PowerOnAfterStartup { get; set; } - public bool PowerOnAfterResume { get; set; } - public bool PowerOffOnShutdown { get; set; } - public bool PowerOffOnStandby { get; set; } - public bool PowerOffOnScreenSaver { get; set; } - public int ScreenSaverMinimalDuration { get; set; } - public bool TurnScreenOffOnScreenSaver { get; set; } - public bool HandleManualScreenSaver { get; set; } - public bool PowerOnAfterScreenSaver { get; set; } - public bool TurnScreenOnAfterScreenSaver { get; set; } - public bool PowerOnAfterManualPowerOff { get; set; } - public bool PowerOnByWindows { get; set; } - public bool PowerOffByWindows { get; set; } - public bool TriggersEnabled { get; set; } - public int HDMIPortNumber { get; set; } + public bool PowerOnAfterStartup { get; set; } + public bool PowerOnAfterResume { get; set; } + public bool PowerOffOnShutdown { get; set; } + public bool PowerOffOnStandby { get; set; } + public bool PowerOffOnScreenSaver { get; set; } + public int ScreenSaverMinimalDuration { get; set; } + public bool TurnScreenOffOnScreenSaver { get; set; } + public bool HandleManualScreenSaver { get; set; } + public bool PowerOnAfterScreenSaver { get; set; } + public bool TurnScreenOnAfterScreenSaver { get; set; } + public bool PowerOnAfterManualPowerOff { get; set; } + public bool PowerOnByWindows { get; set; } + public bool PowerOffByWindows { get; set; } + public bool PowerOnAfterSessionUnlock { get; set; } + public bool PowerOffAfterSessionLock { get; set; } + public bool TriggersEnabled { get; set; } + public int HDMIPortNumber { get; set; } - public bool UseSecureConnection { get; set; } = true; + public bool UseSecureConnection { get; set; } = true; } diff --git a/Shared/Contracts/NVIDIA/NvGpuInfoDto.cs b/Shared/Contracts/NVIDIA/NvGpuInfoDto.cs index 07d3b34..637925b 100644 --- a/Shared/Contracts/NVIDIA/NvGpuInfoDto.cs +++ b/Shared/Contracts/NVIDIA/NvGpuInfoDto.cs @@ -1,28 +1,52 @@ -using NvAPIWrapper.Native.GPU.Structures; +using Newtonsoft.Json; +using NvAPIWrapper.Native.GPU.Structures; using static NvAPIWrapper.Native.GPU.Structures.PrivateClockBoostLockV2; namespace ColorControl.Shared.Contracts.NVIDIA; public class NvGpuInfoDto { - public List ClockBoostLocks { get; set; } = new(); - public ClockDomainInfo StockGraphicsClock { get; set; } - public ClockDomainInfo StockMemoryClock { get; set; } - public PrivateVFPCurveV3 CurveV3 { get; set; } - public PrivateVFPCurveV1 CurveV1 { get; set; } - public int? DefaultPowerInMilliWatts { get; set; } - public int MinPowerInMilliWatts { get; set; } - public int MaxPowerInMilliWatts { get; set; } - public int MinCoreStepInMHz { get; set; } = 15; - public List SupportedOcTypes { get; set; } = new(); + public List ClockBoostLocks { get; set; } = new(); + public ClockDomainInfo StockGraphicsClock { get; set; } + public ClockDomainInfo StockMemoryClock { get; set; } + public PrivateVFPCurveV3 CurveV3 { get; set; } + public PrivateVFPCurveV1 CurveV1 { get; set; } + public int? DefaultPowerInMilliWatts { get; set; } + public uint MinimumPowerInPCM { get; set; } + public uint MaximumPowerInPCM { get; set; } + public int MinPowerInMilliWatts { get; set; } + public int MaxPowerInMilliWatts { get; set; } + public int MinCoreStepInMHz { get; set; } = 15; + public int MinCoreDeltaInMHz { get; set; } + [JsonIgnore] + public int CorrectedMinCoreDeltaInMHz + { + get => (MinCoreDeltaInMHz / MinCoreStepInMHz) * MinCoreStepInMHz; + } + public int MaxCoreDeltaInMHz { get; set; } + public int MinMemoryDeltaInMHz { get; set; } + public int MaxMemoryDeltaInMHz { get; set; } + public List CurveFrequenciesInMHz { get; set; } = new(); + public List CurveVoltagesInMv { get; set; } = new(); + [JsonIgnore] + public uint MinCurveFrequencyInMHz => CurveFrequenciesInMHz.Any() ? CurveFrequenciesInMHz.Min() : 0; + [JsonIgnore] + public uint MaxCurveFrequencyInMHz => CurveFrequenciesInMHz.Any() ? CurveFrequenciesInMHz.Max() : 0; + [JsonIgnore] + public uint MinCurveVoltageInMv => CurveVoltagesInMv.Any() ? CurveVoltagesInMv.Min() : 0; + [JsonIgnore] + public uint MaxCurveVoltageInMv => CurveVoltagesInMv.Any() ? CurveVoltagesInMv.Max() : 0; + public List SupportedOcTypes { get; set; } = new(); - public string PCIIdentifier { get; set; } - public string Name { get; set; } - public uint GpuFrequencyInKhz { get; set; } - public uint MemoryFrequencyInKhz { get; set; } - public uint VoltageInMv { get; set; } - public double PowerInWatts { get; set; } - public double? GpuTemperature { get; set; } - public double? HotSpotTemperature { get; set; } - public double? MemoryTemperature { get; set; } + public string PCIIdentifier { get; set; } + public string Name { get; set; } + public uint GpuFrequencyInKhz { get; set; } + public uint MemoryFrequencyInKhz { get; set; } + public uint VoltageInMv { get; set; } + public double PowerInWatts { get; set; } + public double? GpuTemperature { get; set; } + public double? HotSpotTemperature { get; set; } + public double? MemoryTemperature { get; set; } + + public NvGpuOcSettings OverclockSettings { get; set; } } diff --git a/Shared/Contracts/NVIDIA/NvGpuOcSettings.cs b/Shared/Contracts/NVIDIA/NvGpuOcSettings.cs index f2391dc..76cc68b 100644 --- a/Shared/Contracts/NVIDIA/NvGpuOcSettings.cs +++ b/Shared/Contracts/NVIDIA/NvGpuOcSettings.cs @@ -1,4 +1,5 @@ using ColorControl.Shared.Common; +using Newtonsoft.Json; namespace ColorControl.Shared.Contracts.NVIDIA { @@ -16,34 +17,75 @@ namespace ColorControl.Shared.Contracts.NVIDIA public string PCIIdentifier { get; set; } public int MemoryOffsetKHz { get; set; } + [JsonIgnore] + public int MemoryOffsetMHz + { + get => MemoryOffsetKHz / 1000; + set => MemoryOffsetKHz = value * 1000; + } public NvGpuOcType Type { get; set; } public int GraphicsOffsetKHz { get; set; } + [JsonIgnore] + public int GraphicsOffsetMHz + { + get => GraphicsOffsetKHz / 1000; + set => GraphicsOffsetKHz = value * 1000; + } public uint MaximumFrequencyKHz { get; set; } + [JsonIgnore] + public uint MaximumFrequencyMHz + { + get => MaximumFrequencyKHz / 1000; + set => MaximumFrequencyKHz = value * 1000; + } public uint MaximumVoltageUv { get; set; } + [JsonIgnore] + public uint MaximumVoltageMv + { + get => MaximumVoltageUv / 1000; + set => MaximumVoltageUv = value * 1000; + } public bool FrequencyPreferred { get; set; } public uint VoltageBoostPercent { get; set; } public uint PowerPCM { get; set; } = DefaultPowerPCM; + public NvGpuOcSettings() + { + } + + public NvGpuOcSettings(NvGpuOcSettings settings) + { + PCIIdentifier = settings.PCIIdentifier; + MemoryOffsetKHz = settings.MemoryOffsetKHz; + Type = settings.Type; + GraphicsOffsetKHz = settings.GraphicsOffsetKHz; + MaximumFrequencyKHz = settings.MaximumFrequencyKHz; + MaximumVoltageUv = settings.MaximumVoltageUv; + FrequencyPreferred = settings.FrequencyPreferred; + VoltageBoostPercent = settings.VoltageBoostPercent; + PowerPCM = settings.PowerPCM; + } + public override string ToString() { var value = ""; if (Type == NvGpuOcType.BoostLock) { - value += $"GPU: Locked: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToUnitString()} = {MaximumFrequencyKHz.ToUnitString()} @ {MaximumVoltageUv / 1000}mV"; + value += $"GPU: Locked: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToKiloUnitString()} = {MaximumFrequencyKHz.ToKiloUnitString()} @ {MaximumVoltageUv / 1000}mV"; } else if (Type == NvGpuOcType.Curve) { - value += $"GPU: Curve: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToUnitString()}"; + value += $"GPU: Curve: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToKiloUnitString()}"; if (MaximumVoltageUv > 0) { - value += $", UV: {MaximumFrequencyKHz.ToUnitString()} @ {MaximumVoltageUv / 1000}mV"; + value += $", UV: {MaximumFrequencyKHz.ToKiloUnitString()} @ {MaximumVoltageUv / 1000}mV"; } } else if (Type == NvGpuOcType.Offset) { - value += $"GPU: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToUnitString()}"; + value += $"GPU: {(GraphicsOffsetKHz >= 0 ? "+" : "-")}{GraphicsOffsetKHz.ToKiloUnitString()}"; } if (MemoryOffsetKHz != 0) @@ -53,7 +95,7 @@ namespace ColorControl.Shared.Contracts.NVIDIA value += ", "; } - value += $"Memory: {(MemoryOffsetKHz >= 0 ? "+" : "-")}{MemoryOffsetKHz.ToUnitString()}"; + value += $"Memory: {(MemoryOffsetKHz >= 0 ? "+" : "-")}{MemoryOffsetKHz.ToKiloUnitString()}"; } if (PowerPCM != 100000) diff --git a/Shared/Contracts/NVIDIA/NvPreset.cs b/Shared/Contracts/NVIDIA/NvPreset.cs index c596db0..2eab63d 100644 --- a/Shared/Contracts/NVIDIA/NvPreset.cs +++ b/Shared/Contracts/NVIDIA/NvPreset.cs @@ -150,6 +150,7 @@ namespace ColorControl.Shared.Contracts.NVIDIA applyDriverSettings = keepChanges && applyDriverSettings || driverSettings.Any(s => !currentSettings.driverSettings.Any(o => o.Key == s.Key && o.Value == s.Value)); applyHdmiSettings = keepChanges && applyHdmiSettings || HdmiInfoFrameSettings.IsDifferent(currentSettings.HdmiInfoFrameSettings); ApplyNovideoSettings = keepChanges && ApplyNovideoSettings || (NovideoSettings.ApplyClamp && !HDREnabled || applyHDR && !HDREnabled && !NovideoSettings.ApplyClamp); + applyOverclocking = keepChanges && applyOverclocking || ocSettings.Any(); ApplyColorEnhancements = keepChanges && ApplyColorEnhancements || ColorEnhancementSettings.DigitalVibranceLevel != currentSettings.ColorEnhancementSettings.DigitalVibranceLevel || ColorEnhancementSettings.HueAngle != currentSettings.ColorEnhancementSettings.HueAngle; @@ -196,6 +197,9 @@ namespace ColorControl.Shared.Contracts.NVIDIA NovideoSettings = new NovideoSettings(preset.NovideoSettings); ApplyNovideoSettings = preset.ApplyNovideoSettings; + applyOverclocking = preset.applyOverclocking; + ocSettings = preset.ocSettings.Select(s => new NvGpuOcSettings(s)).ToList(); + MonitorConnectionType = preset.MonitorConnectionType; } diff --git a/Shared/Contracts/PresetStep.cs b/Shared/Contracts/PresetStep.cs index 021eb21..b69cb71 100644 --- a/Shared/Contracts/PresetStep.cs +++ b/Shared/Contracts/PresetStep.cs @@ -6,7 +6,7 @@ public class CustomStepItem { public string Name { get; private set; } public List Items { get; private set; } - public string? SelectedItem { get; set; } + public string SelectedItem { get; set; } public CustomStepItem(string name, List items) { diff --git a/Shared/Contracts/Samsung/SamsungDeviceOptions.cs b/Shared/Contracts/Samsung/SamsungDeviceOptions.cs index e32f256..cc9b627 100644 --- a/Shared/Contracts/Samsung/SamsungDeviceOptions.cs +++ b/Shared/Contracts/Samsung/SamsungDeviceOptions.cs @@ -15,6 +15,8 @@ public class SamsungDeviceOptions public bool PowerOnAfterManualPowerOff { get; set; } public bool PowerOnByWindows { get; set; } public bool PowerOffByWindows { get; set; } + public bool PowerOnAfterSessionUnlock { get; set; } + public bool PowerOffAfterSessionLock { get; set; } public bool TriggersEnabled { get; set; } public int HDMIPortNumber { get; set; } diff --git a/Shared/Contracts/Samsung/SamsungEnums.cs b/Shared/Contracts/Samsung/SamsungEnums.cs index 6245689..adc0aef 100644 --- a/Shared/Contracts/Samsung/SamsungEnums.cs +++ b/Shared/Contracts/Samsung/SamsungEnums.cs @@ -10,4 +10,11 @@ public enum ExitServiceMenuType { Exit, Reboot -} \ No newline at end of file +} + +public enum AdvancedUnlockType +{ + TV, + Monitor +} + diff --git a/Shared/Contracts/Samsung/SamsungPresets.cs b/Shared/Contracts/Samsung/SamsungPresets.cs index 3bbfa2a..b6ddf43 100644 --- a/Shared/Contracts/Samsung/SamsungPresets.cs +++ b/Shared/Contracts/Samsung/SamsungPresets.cs @@ -5,7 +5,7 @@ public static class SamsungFactoryMenuPresets { public static SamsungPreset Preset1 = new("ServiceMenuStep1", null, ["KEY_INFO", "KEY_FACTORY:2000"]); public static SamsungPreset PresetAdvanced = new("ServiceMenuAdvanced", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_9", "KEY_8"]); - public static SamsungPreset PresetAdvancedAlt = new("ServiceMenuAdvancedAlt", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_3", "KEY_8"]); + public static SamsungPreset PresetAdvancedMonitor = new("ServiceMenuAdvancedMonitor", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_3", "KEY_8"]); public static SamsungPreset Exit = new("ServiceMenuStep3", null, ["KEY_FACTORY:1500", "KEY_FACTORY"]); public static SamsungPreset KeyFactory = new("ServiceMenuKeyFactory", null, ["KEY_FACTORY"]); } diff --git a/Shared/EventDispatcher/SessionSwitchDispatcher.cs b/Shared/EventDispatcher/SessionSwitchDispatcher.cs index 0beaf06..80721f4 100644 --- a/Shared/EventDispatcher/SessionSwitchDispatcher.cs +++ b/Shared/EventDispatcher/SessionSwitchDispatcher.cs @@ -18,16 +18,16 @@ public class SessionSwitchDispatcher : EventDispatcher public void SessionSwitchHandler(object sender, SessionSwitchEventArgs evt) { + Logger.Debug($"Session switch: {evt.Reason}"); + if (evt.Reason == SessionSwitchReason.ConsoleDisconnect) { - Logger.Debug("Detected a disconnect from the console"); UserLocalSession = false; DispatchEvent(Event_SessionSwitch, evt); } else if (evt.Reason == SessionSwitchReason.ConsoleConnect) { - Logger.Debug("Detected a connect to the console"); if (!UserLocalSession) { Logger.Debug("Session state switched to local"); @@ -35,6 +35,11 @@ public class SessionSwitchDispatcher : EventDispatcher DispatchEvent(Event_SessionSwitch, evt); } } + else if (evt.Reason is SessionSwitchReason.SessionUnlock or SessionSwitchReason.SessionLock) + { + Task.Run(async () => await DispatchEventAsync(Event_SessionSwitch, evt)); + } + LastSessionSwitchReason = evt.Reason; } } diff --git a/Shared/EventDispatcher/WindowMessageDispatcher.cs b/Shared/EventDispatcher/WindowMessageDispatcher.cs index 8af160c..10b6fcb 100644 --- a/Shared/EventDispatcher/WindowMessageDispatcher.cs +++ b/Shared/EventDispatcher/WindowMessageDispatcher.cs @@ -90,7 +90,7 @@ public class WindowMessageDispatcher : EventDispatcher Logger.Debug("WM_QUERYENDSESSION"); } - Logger.Debug(m); + //Logger.Debug(m); OnMessage?.Invoke(this, m); diff --git a/Shared/Forms/DarkModeUtils.cs b/Shared/Forms/DarkModeUtils.cs index bdb9ec6..0af11f2 100644 --- a/Shared/Forms/DarkModeUtils.cs +++ b/Shared/Forms/DarkModeUtils.cs @@ -1,4 +1,5 @@ -using ColorControl.Shared.Native; +using ColorControl.Shared.Common; +using ColorControl.Shared.Native; using NWin32; namespace ColorControl.Shared.Forms @@ -56,10 +57,7 @@ namespace ColorControl.Shared.Forms public static void InitWpfTheme() { - if (System.Windows.Application.Current == null) - { - new System.Windows.Application(); - } + Utils.EnsureApplication(); var resources = System.Windows.Application.Current.Resources; diff --git a/Shared/Forms/MessageForms.cs b/Shared/Forms/MessageForms.cs index 03e9123..0f05e7a 100644 --- a/Shared/Forms/MessageForms.cs +++ b/Shared/Forms/MessageForms.cs @@ -1,538 +1,566 @@ using ColorControl.Shared.Common; using ColorControl.Shared.Contracts; using ColorControl.Shared.EventDispatcher; +using NStandard; using System.Data; using System.Globalization; namespace ColorControl.Shared.Forms { - public class MessageForm : Form - { - private ToolTip _toolTip; - public Dictionary _controls = new(); - - public void edtShortcut_KeyDown(object sender, KeyEventArgs e) - { - ((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e); - } - - public void edtShortcut_KeyUp(object sender, KeyEventArgs e) - { - KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e); - } - - public void TrackBar_Scroll(object sender, EventArgs e) - { - var trackBar = sender as TrackBar; - var edit = (NumericUpDown)_controls[trackBar]; - - var indexDbl = (trackBar.Value * 1.0) / trackBar.TickFrequency; - var index = Convert.ToInt32(Math.Round(indexDbl)); - - var newValue = trackBar.Value < trackBar.Maximum ? trackBar.TickFrequency * index : trackBar.Value; - newValue = Math.Max(newValue, trackBar.Minimum); - newValue = Math.Min(newValue, trackBar.Maximum); - - trackBar.Value = newValue; - - edit.Value = trackBar.Value; - - _toolTip ??= new ToolTip(); - _toolTip.Show(trackBar.Value.ToString(), trackBar, 50, 10, 1500); - } - - public void TrackBarEdit_ValueChanged(object sender, EventArgs e) - { - var edit = (NumericUpDown)sender; - var trackBar = (TrackBar)_controls.First(c => c.Value == edit).Key; - - trackBar.Value = (int)edit.Value; - } - } - - public class MessageForms - { - public static Form MainForm; - public static KeyboardShortcutDispatcher KeyboardShortcutDispatcher; - public static string Title = null; - - public static void WarningOk(string text) - { - MessageBox.Show(text, Title ?? "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); - } - - public static void ErrorOk(string text) - { - MessageBox.Show(text, Title ?? "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - public static void InfoOk(string text, string title = null, string url = null) - { - if (url != null) - { - MessageBox.Show(text, title ?? Title ?? "Info", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0, url); - } - else - { - MessageBox.Show(text, title ?? Title ?? "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - } - - public static DialogResult QuestionYesNo(string text) - { - return MessageBox.Show(text, Title ?? "Question", MessageBoxButtons.YesNo, MessageBoxIcon.Information); - } - - public static List ShowDialog(string caption, IEnumerable labels, Func, string> validateFunc = null) - { - var fieldDefinitions = labels.Select(l => new FieldDefinition - { - Label = l - }).ToList(); - - return ShowDialog(caption, fieldDefinitions, validateFunc); - } - - public static List ShowDialog(string caption, IEnumerable fields, Func, string> validateFunc = null, string okButtonText = "OK") - { - var values = new List(); - - var prompt = new MessageForm() - { - Width = 460, - Height = 106, - FormBorderStyle = FormBorderStyle.FixedDialog, - Text = caption, - StartPosition = FormStartPosition.CenterScreen - }; - - var groupBox = new Panel(); - groupBox.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; - groupBox.Top = 0; - groupBox.Left = 0; - groupBox.Width = prompt.Width; - groupBox.Height = prompt.Height - 80; - groupBox.BackColor = SystemColors.Window; - //groupBox.Margin = new Padding(20); - - prompt.Controls.Add(groupBox); - - var top = 10; - var boxes = new List(); - var labels = new List