mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
fix issues with new UI, add color profiles, add sdr brightness for AMD
This commit is contained in:
1 parent
4d2ca8a552
commit
f42a46e53f
95 files changed
+12129
-10005
No files matched your search
@@ -1,5 +1,5 @@
|
||||
using ColorControl.Shared.Contracts;
|
||||
using ColorControl.Shared.Services;
|
||||
using ColorControl.UI.Services;
|
||||
|
||||
namespace ColorControl.UI;
|
||||
|
||||
@@ -15,6 +15,9 @@ public static class Blazor
|
||||
|
||||
builder.Services.AddSingleton<AppState>(new AppState { SelectedTheme = (config?.UseDarkMode ?? true) ? "dark" : "light" });
|
||||
builder.Services.AddTransient<RpcUiClientService>();
|
||||
builder.Services.AddTransient<JSHelper>();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddSingleton<NotificationService>();
|
||||
builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(config?.UiPort > 0 ? config.UiPort : 5000));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -13,9 +13,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
<ProjectReference Include="..\MHC2Gen\MHC2Gen.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Components\Pages\ColorProfile\ColorProfilePage.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="Components\Pages\Generic\FieldDefs.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
@@ -25,6 +29,9 @@
|
||||
<Content Update="Components\Pages\Game\GameSummary.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="Components\Pages\Generic\NotificationsComponent.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="Components\Pages\Generic\TriggerInput.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
@@ -52,6 +59,15 @@
|
||||
<Content Update="Components\Pages\Samsung\SamsungServiceMenu.razor">
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</Content>
|
||||
<Content Update="wwwroot\app.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\favicon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="wwwroot\helper.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,8 +1,15 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.UI.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@inject AppState AppState
|
||||
@inject RpcUiClientService ClientService;
|
||||
@inject NavigationManager _navigationManager;
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject NotificationService _notificationService
|
||||
@inject ProtectedSessionStorage _sessionStorage
|
||||
|
||||
@implements IDisposable
|
||||
@attribute [StreamRendering]
|
||||
|
||||
@@ -15,28 +22,33 @@
|
||||
<base href="/" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="ColorControl.UI.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<NotificationsComponent></NotificationsComponent>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@code {
|
||||
private Config? Config;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var config = await ClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
Config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
|
||||
var newTheme = config.UseDarkMode ? "dark" : "light";
|
||||
var newTheme = Config?.UseDarkMode == true ? "dark" : "light";
|
||||
|
||||
if (newTheme != AppState.SelectedTheme)
|
||||
{
|
||||
AppState.SelectedTheme = config.UseDarkMode ? "dark" : "light";
|
||||
AppState.SelectedTheme = Config?.UseDarkMode == true ? "dark" : "light";
|
||||
_navigationManager.Refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Services
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@@ -28,28 +29,34 @@
|
||||
@foreach (var module in modules.Where(m => m.IsActive))
|
||||
{
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link px-3" href="@module.Link">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> @module.DisplayName
|
||||
<NavLink class="nav-link px-3 align-items-baseline" href="@module.Link">
|
||||
<span class="bi bi-@module.GetIconClass()" aria-hidden="true"></span> @module.DisplayName
|
||||
</NavLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link px-3" href="options">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Options
|
||||
<NavLink class="nav-link px-3 align-items-baseline" href="options">
|
||||
<span class="bi bi-gear-fill" aria-hidden="true"></span> Options
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link px-3" href="logging">
|
||||
<span class="bi bi-list-logging-nav-menu" aria-hidden="true"></span> Logging
|
||||
<NavLink class="nav-link px-3 align-items-baseline" href="ColorProfile">
|
||||
<span class="bi bi-palette-fill" aria-hidden="true"></span> Color Profiles
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link px-3" href="info">
|
||||
<span class="bi bi-list-logging-nav-menu" aria-hidden="true"></span> Info
|
||||
<NavLink class="nav-link px-3 align-items-baseline" href="logging">
|
||||
<span class="bi bi-list-columns-reverse" aria-hidden="true"></span> Logging
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link px-3 align-items-baseline" href="info">
|
||||
<span class="bi bi-info-circle-fill" aria-hidden="true"></span> Info
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@inject AppState AppState
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Services
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.IsDisplayPreset == true ? $"Settings for display {Preset?.displayName}" : Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
@@ -316,7 +316,7 @@
|
||||
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
|
||||
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
|
||||
|
||||
if (Preset?.IsDisplayPreset != true)
|
||||
if (Preset?.IsDisplayPreset == false)
|
||||
{
|
||||
displayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">AMD settings</h5>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
@@ -92,6 +92,10 @@ else
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!Presets.Any())
|
||||
{
|
||||
<span>No presets found</span>
|
||||
}
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
@@ -106,11 +110,21 @@ else
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz @(preset.applyColorData ? preset.colorDepth : "")</p>
|
||||
<small class="text-muted">@preset.shortcut</small>
|
||||
@@ -124,7 +138,6 @@ else
|
||||
@code {
|
||||
private List<AmdPreset>? Presets;
|
||||
private List<AmdPreset>? DisplayPresets;
|
||||
private List<VirtualResolution>? Resolutions;
|
||||
|
||||
private AmdPreset? CurrentPreset;
|
||||
|
||||
@@ -134,7 +147,6 @@ else
|
||||
{
|
||||
await RefreshPresets();
|
||||
DisplayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
|
||||
Resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("AmdService", "GetAvailableResolutionsV2", DisplayPresets?.FirstOrDefault());
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Contracts.DisplayInfo
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Services
|
||||
@using MHC2Gen
|
||||
@using Microsoft.JSInterop;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject JSHelper jsHelper;
|
||||
|
||||
<div class="modal modal-lg" id="colorProfileModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(ColorProfile?.Name == null ? $"Settings for new color profile" : $"Settings of color profile {ColorProfile.Name}")</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (ColorProfile?.HasExtraInfo == false)
|
||||
{
|
||||
<div class="alert alert-warning" role="alert">
|
||||
This is not a profile created by ColorControl. Some fields are set to default.
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(_rpcClientService.LastErrorMessage))
|
||||
{
|
||||
<div class="alert alert-danger" role="alert">
|
||||
Error: @_rpcClientService.LastErrorMessage
|
||||
</div>
|
||||
}
|
||||
<form class="row g-1 needs-validation @WasValidated" id="profileForm" novalidate>
|
||||
@if (ColorProfile == null || DisplayInfo == null || CurrentColorInfo == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="display">Display</label>
|
||||
<input class="form-control" type="text" id="display" value="@DisplayInfo" disabled />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" type="text" id="name" @bind="ColorProfile.Name" @bind:after="AfterNameChanged" required />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="description">Description</label>
|
||||
<input class="form-control" type="text" id="description" @bind="ColorProfile.Description" />
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<span class="d-flex align-items-baseline">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
|
||||
Color gamut
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label class="form-label" for="primariesSource"><small>Show Display Primaries and Luminance values of</small></label>
|
||||
<select class="form-select" id="primariesSource" @bind="DisplayPrimariesSource" @bind:after="DisplayPrimariesSourceChanged">
|
||||
@foreach (var colorInfo in DisplayColors)
|
||||
{
|
||||
<option value="@colorInfo.DisplayPrimariesSource">@(colorInfo.CustomName ?? colorInfo.DisplayPrimariesSource.ToString())</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col align-content-end">
|
||||
<button class="btn btn-primary" type="button" disabled="@(DisplayPrimariesSource == DisplayPrimariesSource.ColorProfile)" @onclick="OverwritePrimariesOnClick">Overwrite color profile values</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-danger" for="redX">Red X-coordinate</label>
|
||||
<input class="form-control" id="redX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Red.X" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-danger" for="redY">Red Y-coordinate</label>
|
||||
<input class="form-control" id="redY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Red.Y" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-success" for="greenX">Green X-coordinate</label>
|
||||
<input class="form-control" id="greenX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Green.X" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-success" for="greenY">Green Y-coordinate</label>
|
||||
<input class="form-control" id="greenY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Green.Y" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-primary" for="blueX">Blue X-coordinate</label>
|
||||
<input class="form-control" id="blueX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Blue.X" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label text-primary" for="blueY">Blue Y-coordinate</label>
|
||||
<input class="form-control" id="blueY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Blue.Y" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="whiteX">White X-coordinate</label>
|
||||
<input class="form-control" id="whiteX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.White.X" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="whiteY">White Y-coordinate</label>
|
||||
<input class="form-control" id="whiteY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.White.Y" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="targetGamut">Target gamut</label>
|
||||
<select class="form-select" id="targetGamut" @bind="ColorProfile.ColorGamut">
|
||||
@foreach (var gamut in Enum.GetValues<ColorGamut>())
|
||||
{
|
||||
<option value="@gamut">@gamut.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<span class="d-flex align-items-baseline">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#luminanceSettings">
|
||||
Luminance settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="luminanceSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="minLuminance">Minimum luminance (nits)</label>
|
||||
<input class="form-control" id="minLuminance" type="number" min="0" max="10" @bind="CurrentColorInfo.BlackLuminance" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="maxLuminance">Max full frame luminance (nits)</label>
|
||||
<input class="form-control" id="maxLuminance" type="number" min="0" max="10000" @bind="CurrentColorInfo.WhiteLuminance" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="minCLL">Minimum brightness (MinCLL) (nits)</label>
|
||||
<input class="form-control" id="minCLL" type="number" min="0" max="10" @bind="CurrentColorInfo.MinCLL" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="maxCLL">Peak brightness (MaxCLL) (nits)</label>
|
||||
<input class="form-control" id="maxCLL" type="number" min="0" max="10000" @bind="CurrentColorInfo.MaxCLL" disabled="@PrimariesDisabled" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<span class="d-flex align-items-baseline">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#hdrSettings">
|
||||
Advanced EOTF settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="hdrSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="sdrTransfer">SDR Transfer Function</label>
|
||||
<select class="form-select" id="sdrTransfer" @bind="ColorProfile.SDRTransferFunction">
|
||||
@foreach (var function in Enum.GetValues<SDRTransferFunction>())
|
||||
{
|
||||
<option value="@function">@function.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="gamma">Gamma</label>
|
||||
<input class="form-control" id="gamma" type="number" min="0.1" max="10" step="0.01" @bind="ColorProfile.Gamma" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (ColorProfile.IsHDRProfile)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="minSDR">Minimum SDR brightness (nits)</label>
|
||||
<input class="form-control" id="minSDR" type="number" min="0" max="10" step="0.001" @bind="ColorProfile.SDRMinBrightness" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="maxSDR">Maximum SDR brightness (nits)</label>
|
||||
<input class="form-control" id="maxSDR" type="number" min="0" max="10000" @bind="ColorProfile.SDRMaxBrightness" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="brightnessBoost">Brightness boost (%)</label>
|
||||
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="shadowDetailBoost">Shadow detail boost (%)</label>
|
||||
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" @onclick="InstallOnClick" disabled="@(ColorProfile == null)">Install and set as default</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="submit" class="btn btn-primary" @onclick="ApplyClick" disabled="@(ColorProfile == null)">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private ColorProfileDto? ColorProfile { get; set; }
|
||||
private DisplayColorInfo? CurrentColorInfo { get; set; }
|
||||
private DisplayColorInfo? ColorProfileColorInfo { get; set; }
|
||||
private List<DisplayColorInfo> DisplayColors { get; set; } = new();
|
||||
private bool PrimariesDisabled = false;
|
||||
|
||||
[Parameter]
|
||||
public string? ColorProfileName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool UseAsTemplate { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public DisplayInfoDto? DisplayInfo { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<ColorProfileDto, Task>? AfterApply { get; set; }
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
private DisplayPrimariesSource DisplayPrimariesSource;
|
||||
private bool IsInstalled;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (ColorProfileName == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ColorProfileName == "")
|
||||
{
|
||||
ColorProfile = ColorProfileDto.CreateDefault(DisplayInfo?.IsHdrSupportedAndEnabled ?? false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorProfile = await _rpcClientService.CallAsync<ColorProfileDto>("ColorProfileService", "GetColorProfile", ColorProfileName);
|
||||
}
|
||||
|
||||
if (ColorProfile != null)
|
||||
{
|
||||
ColorProfileColorInfo = new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = DisplayPrimariesSource.ColorProfile,
|
||||
BlackLuminance = ColorProfile.BlackLuminance,
|
||||
WhiteLuminance = ColorProfile.WhiteLuminance,
|
||||
MinCLL = ColorProfile.MinCLL,
|
||||
MaxCLL = ColorProfile.MaxCLL,
|
||||
RgbPrimaries = new RgbPrimariesDto(ColorProfile.DevicePrimaries)
|
||||
};
|
||||
|
||||
CurrentColorInfo = ColorProfileColorInfo;
|
||||
|
||||
DisplayColors.Clear();
|
||||
DisplayColors.Add(CurrentColorInfo);
|
||||
|
||||
if (DisplayInfo != null)
|
||||
{
|
||||
DisplayColors.AddRange(DisplayInfo.DisplayColors);
|
||||
}
|
||||
|
||||
if (UseAsTemplate)
|
||||
{
|
||||
if (ColorProfile.Name != null)
|
||||
{
|
||||
ColorProfile.Name = Path.GetFileNameWithoutExtension(ColorProfile.Name) + " (clone).icm";
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorProfile.Name = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (ColorProfile == null || CurrentColorInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await IsValid()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ColorProfile.UpdatePrimariesAndLuminance(CurrentColorInfo);
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "UpdateColorProfile", ColorProfile, DisplayInfo?.DeviceName);
|
||||
|
||||
if (result)
|
||||
{
|
||||
await jsHelper.CloseModal("colorProfileModal");
|
||||
IsVisible = false;
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(ColorProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InstallOnClick()
|
||||
{
|
||||
if (ColorProfile == null || DisplayInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await IsValid()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ColorProfile.UpdatePrimariesAndLuminance(CurrentColorInfo);
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "InstallAndActivateColorProfile", ColorProfile, DisplayInfo.DeviceName);
|
||||
|
||||
IsInstalled = true;
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
DisplayPrimariesSource = DisplayPrimariesSource.ColorProfile;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsValid()
|
||||
{
|
||||
var valid = await jsHelper.IsFormValid("profileForm");
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
private void DisplayPrimariesSourceChanged()
|
||||
{
|
||||
if (DisplayInfo == null || ColorProfile == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentColorInfo = DisplayColors.First(dc => dc.DisplayPrimariesSource == DisplayPrimariesSource);
|
||||
PrimariesDisabled = CurrentColorInfo.DisplayPrimariesSource != DisplayPrimariesSource.ColorProfile;
|
||||
}
|
||||
|
||||
private void OverwritePrimariesOnClick(MouseEventArgs e)
|
||||
{
|
||||
if (ColorProfileColorInfo == null || CurrentColorInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ColorProfileColorInfo.BlackLuminance = CurrentColorInfo.BlackLuminance;
|
||||
ColorProfileColorInfo.WhiteLuminance = CurrentColorInfo.WhiteLuminance;
|
||||
ColorProfileColorInfo.MinCLL = CurrentColorInfo.MinCLL;
|
||||
ColorProfileColorInfo.MaxCLL = CurrentColorInfo.MaxCLL;
|
||||
ColorProfileColorInfo.RgbPrimaries = new RgbPrimariesDto(CurrentColorInfo.RgbPrimaries);
|
||||
}
|
||||
|
||||
private void AfterNameChanged()
|
||||
{
|
||||
if (ColorProfile?.Name == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ColorProfile.Name.EndsWith(".icm", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ColorProfile.Name += ".icm";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
@page "/ColorProfile"
|
||||
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.DisplayInfo
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using ColorControl.UI.Services
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject JSHelper jsHelper;
|
||||
|
||||
<PageTitle>Color profiles</PageTitle>
|
||||
|
||||
<ColorProfilePage ColorProfileName="@SelectedColorProfileName" UseAsTemplate="UseAsTemplate" DisplayInfo="SelectedDisplay" AfterApply="AfterColorProfileSaved"></ColorProfilePage>
|
||||
|
||||
<ConfirmModal Title="Delete color profile" Message="Are you sure you want to delete the color profile?" OnConfirm="ColorProfileOnDeleteConfirmed" />
|
||||
<ConfirmModal ModalId="disassociateConfirmModal" Title="Disassociate color profile" Message="Are you sure you want to disassociate the color profile from the selected display?" OnConfirm="ColorProfileOnDisassociateConfirmed" />
|
||||
<ConfirmModal ModalId="associateConfirmModal" Title="Associate color profile" Message="Are you sure you want to associate the color profile with the selected display?" OnConfirm="ColorProfileOnAssociateConfirmed" />
|
||||
|
||||
<h2>Displays</h2>
|
||||
|
||||
@if (Displays == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@if (!Displays.Any())
|
||||
{
|
||||
<span>No displays found</span>
|
||||
}
|
||||
@foreach (var display in Displays)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="device">Selected display</label>
|
||||
<select class="form-select" id="device" @bind="SelectedDisplayId" @bind:after="SelectedDisplayChanged">
|
||||
@foreach (var device in Displays)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.DisplayName @@ @display.AdapterName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
@* <div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-baseline">
|
||||
<span>
|
||||
@display.DisplayName @@ @display.AdapterName
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-body">@display.DevicePath</small>
|
||||
<small class="text-muted">EDID? @display.DisplayColors.Any(dc => dc.DisplayPrimariesSource == MHC2Gen.DisplayPrimariesSource.EDID)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
*@ }
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (SelectedDisplay == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Display Associated Color profiles (@(SelectedDisplay.IsHdrSupportedAndEnabled ? "HDR" : "SDR"))</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => AddColorProfileOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!SelectedDisplay.ColorProfiles.Any())
|
||||
{
|
||||
<span>No color profiles found that are associated with the display</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group">
|
||||
@foreach (var colorProfile in SelectedDisplay.ColorProfiles)
|
||||
{
|
||||
<a class="list-group-item list-group-item-action" @onclick="() => SetColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@colorProfile
|
||||
@if (colorProfile == SelectedDisplay?.DefaultColorProfile)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-warning" title="Active color profile">Active</span>
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#disassociateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile" @onclick:preventDefault @onclick:stopPropagation>Disassociate</button>
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#disassociateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile">Disassociate</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Other Color profiles</h2>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (ColorProfileNames == null)
|
||||
{
|
||||
<span>No profiles found</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group">
|
||||
@foreach (var colorProfile in ColorProfileNames)
|
||||
{
|
||||
<span class="list-group-item list-group-item-action" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@colorProfile
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#associateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile" @onclick:preventDefault @onclick:stopPropagation>Associate</button>
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#associateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile">Associate</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
private string? SelectedDisplayId;
|
||||
private DisplayInfoDto? SelectedDisplay;
|
||||
private List<DisplayInfoDto>? Displays;
|
||||
private string? SelectedColorProfileName;
|
||||
private bool UseAsTemplate;
|
||||
private List<string>? ColorProfileNames;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshDisplays();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
{
|
||||
}
|
||||
|
||||
private async Task AddColorProfileOnClick()
|
||||
{
|
||||
SelectedColorProfileName = "";
|
||||
}
|
||||
|
||||
private async Task EditColorProfileOnClick(string colorProfile)
|
||||
{
|
||||
UseAsTemplate = false;
|
||||
SelectedColorProfileName = colorProfile;
|
||||
await jsHelper.OpenModal("colorProfileModal");
|
||||
}
|
||||
|
||||
private async Task SetColorProfileOnClick(string colorProfile)
|
||||
{
|
||||
if (SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetDisplayDefaultColorProfile", SelectedDisplay.DeviceName, colorProfile);
|
||||
if (result)
|
||||
{
|
||||
SelectedDisplay.DefaultColorProfile = colorProfile;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteColorProfileOnClick(string colorProfile)
|
||||
{
|
||||
SelectedColorProfileName = colorProfile;
|
||||
}
|
||||
|
||||
private async Task CloneColorProfileOnClick(string colorProfile)
|
||||
{
|
||||
UseAsTemplate = true;
|
||||
SelectedColorProfileName = colorProfile;
|
||||
}
|
||||
|
||||
private async Task ColorProfileOnDeleteConfirmed()
|
||||
{
|
||||
if (SelectedColorProfileName == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "DeleteColorProfile", SelectedColorProfileName);
|
||||
|
||||
SelectedColorProfileName = null;
|
||||
|
||||
await RefreshDisplays();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ColorProfileOnDisassociateConfirmed()
|
||||
{
|
||||
if (SelectedColorProfileName == null || SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "RemoveDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName);
|
||||
|
||||
SelectedColorProfileName = null;
|
||||
|
||||
await RefreshDisplays();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ColorProfileOnAssociateConfirmed()
|
||||
{
|
||||
if (SelectedColorProfileName == null || SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "AddDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, false);
|
||||
|
||||
SelectedColorProfileName = null;
|
||||
|
||||
await RefreshDisplays();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void SelectedDisplayChanged()
|
||||
{
|
||||
SelectedDisplay = Displays?.FirstOrDefault(d => d.DisplayId == SelectedDisplayId);
|
||||
}
|
||||
|
||||
private async Task AfterColorProfileSaved(ColorProfileDto colorProfile)
|
||||
{
|
||||
await RefreshDisplays();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RefreshDisplays()
|
||||
{
|
||||
Displays = await _rpcClientService.CallAsync<List<DisplayInfoDto>>("ColorProfileService", "GetDisplayInfos");
|
||||
|
||||
ColorProfileNames = await _rpcClientService.CallAsync<List<string>>("ColorProfileService", "GetAllColorProfileNames");
|
||||
|
||||
if (Displays?.Any() == true)
|
||||
{
|
||||
SelectedDisplay = Displays.FirstOrDefault(d => d.DisplayId == SelectedDisplayId) ?? Displays.First();
|
||||
SelectedDisplayId = SelectedDisplay.DisplayId;
|
||||
|
||||
ColorProfileNames = ColorProfileNames?.Except(SelectedDisplay.ColorProfiles).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Game
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
@@ -13,7 +13,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Game
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">NVIDIA settings</h5>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@using ColorControl.Shared.Contracts;
|
||||
@using ColorControl.Shared.Contracts.Game;
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Generics
|
||||
@@ -52,6 +52,10 @@ else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!Presets.Any())
|
||||
{
|
||||
<span>No presets found</span>
|
||||
}
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
@@ -66,11 +70,21 @@ else
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<FieldsModal ModalId="confirmModal" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
|
||||
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
@@ -16,6 +16,9 @@
|
||||
[Parameter]
|
||||
public Func<Task>? OnConfirm { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ModalId { get; set; } = "confirmModal";
|
||||
|
||||
public List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@@ -7,7 +7,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="@ModalId" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Title</h5>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
<div class="row mx-0 mb-2">
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Services
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject JSHelper jsHelper;
|
||||
@inject NotificationService notificationService;
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
@if (Notifications?.Any() == true)
|
||||
{
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative">
|
||||
<div class="toast-container bottom-0 end-0 p-3">
|
||||
|
||||
@foreach (var notification in Notifications)
|
||||
{
|
||||
<div class="toast" id="toast_@notification.Id" role="alert" aria-live="assertive" aria-atomic="true" @onclick="() => NotificationOnClick(notification)">
|
||||
<div class="toast-header" style="background-color: var(--bs-@notification.Level-bg-subtle)">
|
||||
<span class="w-100 d-flex">
|
||||
<div class="alert alert-@notification.Level mb-0 py-1">
|
||||
<strong class="me-auto">@notification.Category</strong>
|
||||
</div>
|
||||
</span>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
@if (notification.InternalUrl != null)
|
||||
{
|
||||
<ul class="list-group-flush p-0 mb-0">
|
||||
<li class="list-group-item">@notification.Message</li>
|
||||
</ul>
|
||||
}
|
||||
else
|
||||
{
|
||||
@notification.Message
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<NotificationDto> Notifications { get; } = [];
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
notificationService.OnNotification += AddNotification;
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task AddNotification(NotificationDto notification)
|
||||
{
|
||||
RemoveOldNotifications();
|
||||
|
||||
var minShownAt = DateTime.UtcNow.AddSeconds(-5);
|
||||
|
||||
// Suppress duplicate noficiations
|
||||
if (Notifications.Any(n => n.Message == notification.Message && n.Level == notification.Level && n.ShownAt >= minShownAt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Notifications.Add(notification);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
await jsHelper.ShowToast($"toast_{notification.Id}");
|
||||
notification.ShownAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private async Task NotificationOnClick(NotificationDto notification)
|
||||
{
|
||||
if (notification.InternalUrl != null)
|
||||
{
|
||||
_navigationManager.NavigateTo(notification.InternalUrl);
|
||||
}
|
||||
|
||||
Notifications.Remove(notification);
|
||||
}
|
||||
|
||||
private void RemoveOldNotifications()
|
||||
{
|
||||
var minDateTime = DateTime.UtcNow.AddMinutes(-1);
|
||||
|
||||
Notifications.RemoveAll(n => n.ShownAt.HasValue && n.ShownAt <= minDateTime);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@typeparam TPreset where TPreset : PresetBase
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
@page "/"
|
||||
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
@inject NotificationService _notificationService
|
||||
@inject ProtectedSessionStorage _sessionStorage
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
@@ -24,10 +29,10 @@ else
|
||||
<div class="row">
|
||||
@foreach (var module in Modules.Where(m => m.IsActive))
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card h-100 mb-3">
|
||||
<div class="col-lg-4 col-xl mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
@module.DisplayName
|
||||
<span class="me-2 bi bi-@module.GetIconClass()" aria-hidden="true"></span>@module.DisplayName
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="@module.Link" class="btn btn-primary stretched-link mb-2">Open module</a>
|
||||
@@ -47,6 +52,8 @@ else
|
||||
@code {
|
||||
private Config? Config;
|
||||
private List<ModuleDto>? Modules;
|
||||
private InfoDto? Info;
|
||||
private bool IsUpdateAvailable;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
@@ -57,4 +64,28 @@ else
|
||||
Modules.ForEach(m => m.Link = m.DisplayName.Split(' ')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender || Info != null || Config?.CheckForUpdates != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var checkedForUpdates = await _sessionStorage.TryGet<bool>("checkedForUpdates");
|
||||
|
||||
if (!checkedForUpdates)
|
||||
{
|
||||
Info = await _rpcClientService.CallAsync<InfoDto>("OptionsService", "GetInfo");
|
||||
|
||||
await _sessionStorage.SetAsync("checkedForUpdates", true);
|
||||
|
||||
if (!IsUpdateAvailable && Info?.UpdateInfoDto?.UpdateAvailable == true)
|
||||
{
|
||||
IsUpdateAvailable = true;
|
||||
var message = $"Update available: version {Info.UpdateInfoDto.NewVersionNumber}";
|
||||
_notificationService.SendNotification(new NotificationDto(message) { InternalUrl = "/info", ScheduledAt = DateTime.UtcNow.AddSeconds(5) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
@page "/info"
|
||||
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
@inject IHttpContextAccessor _httpContextAccessor
|
||||
|
||||
<PageTitle>Logging</PageTitle>
|
||||
|
||||
@@ -20,7 +21,41 @@
|
||||
else
|
||||
{
|
||||
<h3 class="mb-2">@info.ApplicationTitle - @info.LegalCopyright</h3>
|
||||
|
||||
<div class="d-flex align-items-baseline">
|
||||
@if (IsUpdateInstalled)
|
||||
{
|
||||
if (IsEmbeddedBrowser)
|
||||
{
|
||||
<button type="button" class="btn btn-primary me-2" @onclick="RestartOnClick">Restart</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary me-2" @onclick="ReloadOnClick">Reload</button>
|
||||
}
|
||||
<span>New version @info.UpdateInfoDto.NewVersionNumber is installed. Click the button to restart the application.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary me-2" disabled="@(!info.UpdateInfoDto.UpdateAvailable || IsInstallingUpdate)" @onclick="InstallUpdateOnClick">
|
||||
@if (IsInstallingUpdate)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">Install update</span>
|
||||
</button>
|
||||
@if (info.UpdateInfoDto.UpdateAvailable)
|
||||
{
|
||||
<span>New version @info.UpdateInfoDto.NewVersionNumber available. Click the button to install the update.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>You have the latest version</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<h5>This app contains the following 3rd party plugins:</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">lgtv.net by gr4b4z</li>
|
||||
@@ -41,10 +76,46 @@ else
|
||||
|
||||
@code {
|
||||
private InfoDto? info;
|
||||
private bool IsInstallingUpdate;
|
||||
private bool IsUpdateInstalled;
|
||||
private bool IsEmbeddedBrowser;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
info = await _rpcClientService.CallAsync<InfoDto>("OptionsService", "GetInfo");
|
||||
|
||||
}
|
||||
|
||||
private async Task InstallUpdateOnClick(MouseEventArgs e)
|
||||
{
|
||||
IsInstallingUpdate = true;
|
||||
try
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "InstallUpdate");
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsUpdateInstalled = true;
|
||||
IsEmbeddedBrowser = _httpContextAccessor.HttpContext?.Request.Headers["user-agent"].ToString().Contains("ColorControlEmbedded") == true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsInstallingUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RestartOnClick(MouseEventArgs e)
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "RestartAfterUpdate");
|
||||
}
|
||||
|
||||
private async Task ReloadOnClick(MouseEventArgs e)
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "RestartAfterUpdate");
|
||||
|
||||
_navigationManager.Refresh();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="deviceModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Device?.Name == null ? $"Settings for new device" : $"Settings of device {Device.Name}")</h5>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
@@ -13,7 +13,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
@@ -155,6 +155,10 @@ else
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!Presets.Any())
|
||||
{
|
||||
<span>No presets found</span>
|
||||
}
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
@@ -168,11 +172,21 @@ else
|
||||
}
|
||||
<div><small class="text-muted">@preset.shortcut</small></div>
|
||||
</div>
|
||||
<span>
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
@@ -215,7 +229,7 @@ else
|
||||
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
|
||||
InvokableActions = await _rpcClientService.CallAsync<List<InvokableActionDto<LgPreset>>>("LgService", "GetInvokableActions");
|
||||
|
||||
ActionGroups = InvokableActions.GroupBy(a => a.Category).Select(g => new ActionGroup(g.Key, g.ToList())).ToList();
|
||||
ActionGroups = InvokableActions?.GroupBy(a => a.Category).Select(g => new ActionGroup(g.Key, g.ToList())).ToList() ?? new List<ActionGroup>();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/logging"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@@ -154,10 +154,13 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
logLevel = config.LogLevel;
|
||||
|
||||
await GetLog();
|
||||
if (allLines == null)
|
||||
{
|
||||
await GetLog();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetLog()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal modal-lg" id="driverSettingsModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" @onfocus="OnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset != null ? Preset.IsDisplayPreset ? $"Driver settings for display {Preset.displayName}" : $"Driver settings for {Preset.IdOrName}" : "Driver settings")</h5>
|
||||
@@ -38,6 +38,12 @@
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="OnlyFoundPrograms" id="onlyFoundPrograms" @bind:after="OnlyFoundProgramsOnChange">
|
||||
<label class="form-check-label" for="onlyFoundPrograms">
|
||||
Only show found programs
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="accordion accordion-flush" id="driverSettingsAccordion">
|
||||
@@ -137,6 +143,8 @@
|
||||
|
||||
private bool IsVisible { get; set; }
|
||||
|
||||
private bool OnlyFoundPrograms = true;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_preset != Preset)
|
||||
@@ -232,12 +240,17 @@
|
||||
await LoadDriverSettings();
|
||||
}
|
||||
|
||||
private async Task OnlyFoundProgramsOnChange()
|
||||
{
|
||||
ProfileNames = await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
|
||||
}
|
||||
|
||||
private async Task OnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
IsVisible = true;
|
||||
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames");
|
||||
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
|
||||
await LoadDriverSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
@@ -12,7 +12,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.IsDisplayPreset == true ? $"Settings for display {Preset?.displayName}" : Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
@@ -448,6 +448,7 @@
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="extendedColorimetry">Extended colorimetry</label>
|
||||
<select class="form-select" id="extendedColorimetry" @bind="Preset.HdmiInfoFrameSettings.ExtendedColorimetry" @oninput="HdmiSettingsOnInput">
|
||||
<option value="">Not selected</option>
|
||||
@foreach (var extendedColorimetry in Enum.GetValues<InfoFrameVideoExtendedColorimetry>())
|
||||
{
|
||||
<option value="@extendedColorimetry.ToString()">@extendedColorimetry.GetDescription()</option>
|
||||
@@ -458,6 +459,48 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (ColorProfileNames?.Any() == true)
|
||||
{
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<span class="d-flex align-items-baseline">
|
||||
@if (!Preset.IsDisplayPreset)
|
||||
{
|
||||
<input class="form-check-input me-1" style="font-size: 1rem" title="Apply these settings" type="checkbox" @bind="Preset.applyOther" id="applyOther">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyOther ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#otherSettings">
|
||||
Other settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="otherSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="colorProfile">Color profile</label>
|
||||
<select class="form-select" id="colorProfile" @bind="Preset.ColorProfileSettings.ProfileName">
|
||||
<option value="">Not selected</option>
|
||||
@foreach (var colorProfile in ColorProfileNames)
|
||||
{
|
||||
<option value="@colorProfile">@colorProfile</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="nvScaling">Scaling</label>
|
||||
<select class="form-select" id="nvScaling" @bind="Preset.scaling">
|
||||
<option value="">Not selected</option>
|
||||
@foreach (var scaling in Enum.GetValues<Scaling>())
|
||||
{
|
||||
<option value="@scaling">@scaling.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
@@ -489,6 +532,7 @@
|
||||
private List<VirtualResolution>? resolutions;
|
||||
private List<Rational>? refreshRates;
|
||||
private List<NvPreset>? displayPresets;
|
||||
private List<string>? ColorProfileNames;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
@@ -511,10 +555,17 @@
|
||||
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
|
||||
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
|
||||
|
||||
if (Preset?.IsDisplayPreset != true)
|
||||
if (Preset?.IsDisplayPreset == false)
|
||||
{
|
||||
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
}
|
||||
|
||||
var deviceName = Preset?.IsDisplayPreset == true ? Preset.DisplayId : Preset?.DisplayId ?? displayPresets?.FirstOrDefault()?.DisplayId;
|
||||
|
||||
if (Preset != null)
|
||||
{
|
||||
ColorProfileNames = await _rpcClientService.CallAsync<List<string>>("ColorProfileService", "GetDisplayColorProfiles", deviceName);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">NVIDIA settings</h5>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Generics
|
||||
@@ -74,6 +74,21 @@ else
|
||||
<div class="card-body">
|
||||
<small class="text-body">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</small>
|
||||
<small class="text-muted">@preset.colorData</small>
|
||||
@if (preset.DitherState != NvDitherState.Auto)
|
||||
{
|
||||
<br />
|
||||
<small class="text-body">Dithering: @preset.DitherState.GetDescription() @preset.NvDitherBits.GetDescription() @preset.NvDitherMode.GetDescription()</small>
|
||||
}
|
||||
@if (preset.HDREnabled)
|
||||
{
|
||||
<br />
|
||||
<small class="text-body">HDR enabled @(preset.HdrSettings.OutputMode.HasValue ? $"({preset.HdrSettings.DisplayOutputMode.GetDescription()})" : ""), SDR brightness: @preset.SDRBrightness%</small>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(preset.ColorProfileSettings.ProfileName))
|
||||
{
|
||||
<br />
|
||||
<small class="text-body">Color profile: @preset.ColorProfileSettings.ProfileName</small>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,10 +118,14 @@ else
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!Presets.Any())
|
||||
{
|
||||
<span>No presets found</span>
|
||||
}
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
<a class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<span class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@@ -117,34 +136,68 @@ else
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1">
|
||||
@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz
|
||||
@(preset.applyColorData ? " " + preset.colorData : "")
|
||||
@if (preset.ApplyColorEnhancements)
|
||||
{
|
||||
<span>, @preset.ColorEnhancementSettings</span>
|
||||
}
|
||||
@if (preset.applyHDR)
|
||||
{
|
||||
<span>, HDR: @preset.GetHdrDescription()</span>
|
||||
}
|
||||
@if (preset.applyDriverSettings)
|
||||
{
|
||||
<span>, @preset.driverSettings.Count changed driver settings</span>
|
||||
}
|
||||
@if (preset.ApplyNovideoSettings)
|
||||
{
|
||||
<span>, @preset.NovideoSettings</span>
|
||||
}
|
||||
</p>
|
||||
<small>
|
||||
<ul class="list-group list-group-horizontal-md mb-1">
|
||||
@if (preset.DisplayConfig.ApplyResolution || preset.DisplayConfig.ApplyRefreshRate)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</li>
|
||||
}
|
||||
@if (preset.applyColorData)
|
||||
{
|
||||
<li class="list-group-item p-1">Color settings: @preset.colorData</li>
|
||||
}
|
||||
@if (preset.applyDithering)
|
||||
{
|
||||
<li class="list-group-item p-1">Dithering: @preset.GetDitheringDescription()</li>
|
||||
}
|
||||
@if (preset.ApplyColorEnhancements)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.ColorEnhancementSettings</li>
|
||||
}
|
||||
@if (preset.applyHDR)
|
||||
{
|
||||
<li class="list-group-item p-1">HDR: @preset.GetHdrDescription()</li>
|
||||
}
|
||||
@if (preset.applyDriverSettings)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.driverSettings.Count changed driver settings</li>
|
||||
}
|
||||
@if (preset.ApplyNovideoSettings)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.NovideoSettings</li>
|
||||
}
|
||||
@if (preset.applyHdmiSettings)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.GetHdmiSettingsDescription()</li>
|
||||
}
|
||||
@if (preset.applyOverclocking)
|
||||
{
|
||||
<li class="list-group-item p-1">@preset.GetOverclockingSettingsDescription()</li>
|
||||
}
|
||||
@if (preset.applyOther && !string.IsNullOrEmpty(preset.ColorProfileSettings.ProfileName))
|
||||
{
|
||||
<li class="list-group-item p-1">Color profile: @preset.ColorProfileSettings.ProfileName</li>
|
||||
}
|
||||
</ul>
|
||||
</small>
|
||||
<small class="text-muted">@preset.shortcut</small>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@page "/options"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
@inject NavigationManager _navigationManager
|
||||
@inject NotificationService _notificationService
|
||||
|
||||
<PageTitle>Options</PageTitle>
|
||||
|
||||
@@ -250,6 +251,11 @@ else
|
||||
{
|
||||
_navigationManager.NavigateTo("/options", true);
|
||||
}
|
||||
else if (module.IsActive)
|
||||
{
|
||||
_notificationService.SendNotification(new NotificationDto("Cannot activate the module: check the log for more info", Constants.Danger) { InternalUrl = "/logging" });
|
||||
module.IsActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PollingIntervalOnInput()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="deviceModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Device?.Name == null ? $"Add new device" : $"Settings of device {Device.Name}")</h5>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
@@ -13,7 +13,7 @@
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
@@ -153,6 +153,10 @@ else
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!Presets.Any())
|
||||
{
|
||||
<span>No presets found</span>
|
||||
}
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
@@ -166,11 +170,21 @@ else
|
||||
}
|
||||
<div><small class="text-muted">@preset.shortcut</small></div>
|
||||
</div>
|
||||
<span>
|
||||
<span class="d-none d-lg-block">
|
||||
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace ColorControl.UI.Generics;
|
||||
|
||||
public static class GeneralExtensions
|
||||
{
|
||||
public static async ValueTask<TValue?> TryGet<TValue>(this ProtectedBrowserStorage storage, string key, TValue? def = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await storage.GetAsync<TValue>(key);
|
||||
var value = result.Success ? result.Value : def;
|
||||
|
||||
return value;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
await storage.DeleteAsync(key);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,25 @@
|
||||
using ColorControl.Shared.Contracts;
|
||||
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace ColorControl.UI.Generics;
|
||||
|
||||
public static class PresetExtensions
|
||||
{
|
||||
public static List<T>? OrderPresetsBy<T>(this List<T> presets, PresetOrder order) where T : PresetBase
|
||||
{
|
||||
if (presets == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public static List<T>? OrderPresetsBy<T>(this List<T> presets, PresetOrder order) where T : PresetBase
|
||||
{
|
||||
if (presets == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (order == PresetOrder.ByName)
|
||||
{
|
||||
presets = presets.OrderBy(p => p.name).ToList();
|
||||
}
|
||||
else if (order == PresetOrder.ByLastUsed)
|
||||
{
|
||||
presets = presets.OrderByDescending(p => p.LastUsed).ToList();
|
||||
}
|
||||
if (order == PresetOrder.ByName)
|
||||
{
|
||||
presets = presets.OrderBy(p => p.name).ToList();
|
||||
}
|
||||
else if (order == PresetOrder.ByLastUsed)
|
||||
{
|
||||
presets = presets.OrderByDescending(p => p.LastUsed).ToList();
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
public static async ValueTask<TValue?> TryGet<TValue>(this ProtectedBrowserStorage storage, string key, TValue? def = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await storage.GetAsync<TValue>(key);
|
||||
var value = result.Success ? result.Value : def;
|
||||
|
||||
return value;
|
||||
}
|
||||
catch (CryptographicException)
|
||||
{
|
||||
await storage.DeleteAsync(key);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
return presets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using NStandard;
|
||||
|
||||
namespace ColorControl.UI;
|
||||
|
||||
public class NotificationDto
|
||||
{
|
||||
public long Id { get; }
|
||||
public string Message { get; }
|
||||
public string Level { get; } = Constants.Info;
|
||||
public DateTime? ScheduledAt { get; set; }
|
||||
public string? InternalUrl { get; set; }
|
||||
public DateTime? ShownAt { get; set; }
|
||||
|
||||
public NotificationDto(string message, string level = Constants.Info)
|
||||
{
|
||||
Message = message;
|
||||
Level = level;
|
||||
Id = Random.Shared.NextInt64();
|
||||
}
|
||||
|
||||
public string Category => Level switch
|
||||
{
|
||||
Constants.Danger => "Error",
|
||||
_ => Level[0].ToUpper() + Level.Substring(1)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace ColorControl.UI.Services;
|
||||
|
||||
public class JSHelper(IJSRuntime jsRuntime)
|
||||
{
|
||||
private IJSObjectReference? jsModule;
|
||||
|
||||
public async Task<bool> IsFormValid(string id)
|
||||
{
|
||||
return await InvokeAsync<bool>("FormValid", id);
|
||||
}
|
||||
|
||||
public async Task OpenModal(string id)
|
||||
{
|
||||
await InvokeVoidAsync("OpenModal", id);
|
||||
}
|
||||
|
||||
public async Task CloseModal(string id)
|
||||
{
|
||||
await InvokeVoidAsync("CloseModal", id);
|
||||
}
|
||||
|
||||
public async Task ShowToast(string id)
|
||||
{
|
||||
await InvokeVoidAsync("ShowToast", id);
|
||||
}
|
||||
|
||||
private async ValueTask<T> InvokeAsync<T>(string identifier, params object?[]? names) where T : struct
|
||||
{
|
||||
jsModule = await LoadModule();
|
||||
|
||||
return await jsModule.InvokeAsync<T>(identifier, names);
|
||||
}
|
||||
|
||||
private async ValueTask InvokeVoidAsync(string identifier, params object?[]? names)
|
||||
{
|
||||
jsModule = await LoadModule();
|
||||
|
||||
await jsModule.InvokeVoidAsync(identifier, names);
|
||||
}
|
||||
|
||||
private async Task<IJSObjectReference> LoadModule()
|
||||
{
|
||||
return jsModule ??= await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./helper.js");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace ColorControl.UI.Services;
|
||||
|
||||
public class NotificationService
|
||||
{
|
||||
public event Func<NotificationDto, Task>? OnNotification;
|
||||
|
||||
private List<NotificationDto> _scheduledNotifications = new();
|
||||
|
||||
public void SendNotification(NotificationDto notification)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if (notification.ScheduledAt == null || notification.ScheduledAt <= now)
|
||||
{
|
||||
OnNotification?.Invoke(notification);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(notification.ScheduledAt.Value - now);
|
||||
|
||||
OnNotification?.Invoke(notification);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Contracts;
|
||||
using Newtonsoft.Json;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ColorControl.UI.Services;
|
||||
|
||||
public class RpcUiClientOptions
|
||||
{
|
||||
public int Timeout { get; set; } = PipeUtils.DefaultTimeout;
|
||||
}
|
||||
|
||||
public class RpcUiClientService(NotificationService notificationService)
|
||||
{
|
||||
public string? LastErrorMessage { get; private set; }
|
||||
|
||||
public async Task<T> CallAsync<T>(string serviceName, string method, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
var timeout = Debugger.IsAttached ? 60000 : PipeUtils.DefaultTimeout;
|
||||
|
||||
return await SendRpcMessageAsync<T>(rpcMessage, timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
|
||||
public async Task<T> CallWithOptionsAsync<T>(string serviceName, string method, RpcUiClientOptions options, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
return await SendRpcMessageAsync<T>(rpcMessage, options.Timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
|
||||
public T Call<T>(string serviceName, string method, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
var timeout = Debugger.IsAttached ? 60000 : 2000;
|
||||
|
||||
return PipeUtils.SendRpcMessage<T>(rpcMessage, timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
|
||||
private async Task<T> SendRpcMessageAsync<T>(SvcRpcMessage message, int timeout = PipeUtils.DefaultTimeout, string pipeName = PipeUtils.ServicePipe)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
|
||||
var resultJson = await PipeUtils.SendMessageAsync(messageJson, timeout, pipeName);
|
||||
|
||||
if (resultJson == null)
|
||||
{
|
||||
notificationService.SendNotification(new NotificationDto("Unknown communication error", Constants.Danger));
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
|
||||
if (resultMessage?.Data == null)
|
||||
{
|
||||
LastErrorMessage = resultMessage?.ErrorMessage ?? "Unknown communication error";
|
||||
|
||||
notificationService.SendNotification(new NotificationDto(LastErrorMessage, Constants.Danger));
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
LastErrorMessage = null;
|
||||
|
||||
var result = JsonConvert.DeserializeObject<T>(resultMessage.Data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -42,9 +42,9 @@ h1:focus {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
@charset "UTF-8";/*!
|
||||
* Bootstrap v5.1.0 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2021 The Bootstrap Authors
|
||||
* Copyright 2011-2021 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){leLine truncated
|
||||
/*# sourceMappingURL=bootstrap.min.css.map */
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"iBAAA;;;;;ACAA,MAQI,UAAA,QAAA,YAAA,QAAA,YAAA,QAAA,UAAA,QAAA,SAAA,QAAA,YAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAAA,UAAA,QAAA,WAAA,KAAA,UAAA,QAAA,eAAA,QAIA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAAA,cAAA,QAIA,aAAA,QAAA,eAAA,QAAA,aAAA,QAAA,UAAA,QAAA,aAAA,QAAA,YAAA,QAAA,WAAA,QAAA,UAAA,QAIA,iBAAA,EAAA,CAAA,GAAA,CAAA,IAAA,mBAAA,GAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,EAAA,CAAA,GAAA,CAAA,GAAA,cAAA,EAAA,CAAA,GAAA,CAAA,IAAA,iBAAA,GAAA,CAAA,GAAA,CAAA,EAAA,gBAAA,GAAA,CAAA,EAAA,CAAA,GAAA,eAAA,GAAA,CAAA,GAAA,CAAA,IAAA,cAAA,EAAA,CAAA,EAAA,CAAA,GAGF,eAAA,GAAA,CAAA,GAAA,CAAA,IACA,eAAA,CAAA,CAAA,CAAA,CAAA,EACA,cAAA,EAAA,CAAA,EAAA,CAAA,GAMA,qBAAA,SAAA,CAAA,aAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,iBAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBACA,oBAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UACA,cAAA,2EAQA,sBAAA,0BACA,oBAAA,KACA,sBAAA,IACA,sBAAA,IACA,gBAAA,QAIA,aAAA,KClCF,EC+CA,QADA,SD3CE,WAAA,WAeE,8CANJ,MAOM,gBAAA,QAcN,KACE,OAAA,EACA,YAAA,2BEmPI,UAAA,yBFjPJ,YAAA,2BACA,YAAA,2BACA,MAAA,qBACA,WAAA,0BACA,iBAAA,kBACA,yBAAA,KACA,4BAAA,YAUF,GACE,OAAA,KAAA,EACA,MAAA,QACA,iBAAA,aACA,OAAA,EACA,QAAA,IAGF,eACE,OAAA,IAUF,IAAA,IAAA,IAAA,IAAA,IAAA,IAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GACE,WAAA,EACA,cAAA,MAGA,YAAA,IACA,YAAA,IAIF,IAAA,GEwMQ,UAAA,uBAlKJ,0BFtCJ,IAAA,GE+MQ,UAAA,QF1MR,IAAA,GEmMQ,UAAA,sBAlKJ,0BFjCJ,IAAA,GE0MQ,UAAA,MFrMR,IAAA,GE8LQ,UAAA,oBAlKJ,0BF5BJ,IAAA,GEqMQ,UAAA,SFhMR,IAAA,GEyLQ,UAAA,sBAlKJ,0BFvBJ,IAAA,GEgMQ,UAAA,QF3LR,IAAA,GEgLM,UAAA,QF3KN,IAAA,GE2KM,UAAA,KFhKN,EACE,WAAA,EACA,cAAA,KCmBF,6BDRA,YAEE,wBAAA,UAAA,OAAA,gBAAA,UAAA,OACA,OAAA,KACA,iCAAA,KAAA,yBAAA,KAMF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QAMF,GCIA,GDFE,aAAA,KCQF,GDLA,GCIA,GDDE,WAAA,EACA,cAAA,KAGF,MCKA,MACA,MAFA,MDAE,cAAA,EAGF,GACE,YAAA,IAKF,GACE,cAAA,MACA,YAAA,EAMF,WACE,OAAA,EAAA,EAAA,KAQF,ECNA,ODQE,YAAA,OAQF,OAAA,ME4EM,UAAA,OFrEN,MAAA,KACE,QAAA,KACA,iBAAA,QASF,ICpBA,IDsBE,SAAA,SEwDI,UAAA,MFtDJ,YAAA,EACA,eAAA,SAGF,IAAM,OAAA,OACN,IAAM,IAAA,MAKN,EACE,MAAA,QACA,gBAAA,UAEA,QACE,MAAA,QAWF,2BAAA,iCAEE,MAAA,QACA,gBAAA,KCxBJ,KACA,ID8BA,IC7BA,KDiCE,YAAA,yBEcI,UAAA,IFZJ,UAAA,IACA,aAAA,cAOF,IACE,QAAA,MACA,WAAA,EACA,cAAA,KACA,SAAA,KEAI,UAAA,OFKJ,SELI,UAAA,QFOF,MAAA,QACA,WAAA,OAIJ,KEZM,UAAA,OFcJ,MAAA,QACA,UAAA,WAGA,OACE,MAAA,QAIJ,IACE,QAAA,MAAA,MExBI,UAAA,OF0BJ,MAAA,KACA,iBAAA,QG7SE,cAAA,MHgTF,QACE,QAAA,EE/BE,UAAA,IFiCF,YAAA,IASJ,OACE,OAAA,EAAA,EAAA,KAMF,ICjDA,IDmDE,eAAA,OAQF,MACE,aAAA,OACA,gBAAA,SAGF,QACE,YAAA,MACA,eAAA,MACA,MAAA,QACA,WAAA,KAOF,GAEE,WAAA,QACA,WAAA,qBCxDF,MAGA,GAFA,MAGA,GDuDA,MCzDA,GD+DE,aAAA,QACA,aAAA,MACA,aAAA,EAQF,MACE,QAAA,aAMF,OAEE,cAAA,EAQF,iCACE,QAAA,ECtEF,OD2EA,MCzEA,SADA,OAEA,SD6EE,OAAA,EACA,YAAA,QE9HI,UAAA,QFgIJLine truncated
|
||||
@@ -0,0 +1,17 @@
|
||||
export function FormValid(formId) {
|
||||
let form = document.getElementById(formId);
|
||||
return form.checkValidity();
|
||||
}
|
||||
|
||||
export function CloseModal(modalId) {
|
||||
bootstrap.Modal.getOrCreateInstance('#' + modalId)?.hide();
|
||||
}
|
||||
|
||||
export function OpenModal(modalId) {
|
||||
bootstrap.Modal.getOrCreateInstance('#' + modalId)?.show();
|
||||
}
|
||||
|
||||
export function ShowToast(toastId) {
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance('#' + toastId);
|
||||
toastBootstrap?.show();
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
<PublisherName>Maassoft</PublisherName>
|
||||
<Company>Maassoft</Company>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>10.0.0.1</ApplicationVersion>
|
||||
<Version>10.0.0.1</Version>
|
||||
<ApplicationVersion>10.0.1.0</ApplicationVersion>
|
||||
<Version>10.0.1.0</Version>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
|
||||
+329
-340
@@ -21,351 +21,340 @@ using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private static bool SystemShutdown = false;
|
||||
private static bool EndSession = false;
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private static bool SystemShutdown = false;
|
||||
private static bool EndSession = false;
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ElevationService _elevationService;
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ElevationService _elevationService;
|
||||
|
||||
private NvPanel _nvPanel;
|
||||
private NvPanel _nvPanel;
|
||||
|
||||
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
private readonly UpdateManager _updateManager;
|
||||
private readonly WindowMessageDispatcher _windowMessageDispatcher;
|
||||
private Config _config;
|
||||
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
private readonly UpdateManager _updateManager;
|
||||
private readonly WindowMessageDispatcher _windowMessageDispatcher;
|
||||
private Config _config;
|
||||
|
||||
private LgPanel _lgPanel;
|
||||
private SamsungPanel _samsungPanel;
|
||||
private LgPanel _lgPanel;
|
||||
private SamsungPanel _samsungPanel;
|
||||
|
||||
private AmdPanel _amdPanel;
|
||||
private bool _skipResize;
|
||||
private AmdPanel _amdPanel;
|
||||
private bool _skipResize;
|
||||
|
||||
private GamePanel _gamePanel;
|
||||
|
||||
public MainForm(GlobalContext globalContext, ServiceManager serviceManager, IServiceProvider serviceProvider, ElevationService elevationService,
|
||||
KeyboardShortcutDispatcher keyboardShortcutDispatcher, UpdateManager updateManager, WindowMessageDispatcher windowMessageDispatcher)
|
||||
{
|
||||
InitializeComponent();
|
||||
private GamePanel _gamePanel;
|
||||
|
||||
public MainForm(GlobalContext globalContext, ServiceManager serviceManager, IServiceProvider serviceProvider, ElevationService elevationService,
|
||||
KeyboardShortcutDispatcher keyboardShortcutDispatcher, UpdateManager updateManager, WindowMessageDispatcher windowMessageDispatcher)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Icon = Resources.AppIcon;
|
||||
|
||||
_serviceManager = serviceManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_elevationService = elevationService;
|
||||
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
|
||||
_updateManager = updateManager;
|
||||
_windowMessageDispatcher = windowMessageDispatcher;
|
||||
_config = Program.Config;
|
||||
|
||||
Text = globalContext.ApplicationTitleAdmin;
|
||||
|
||||
LoadConfig();
|
||||
|
||||
MessageForms.MainForm = this;
|
||||
|
||||
InitModules();
|
||||
|
||||
Task.Run(AfterInitialized);
|
||||
|
||||
if (_config.UseDarkMode)
|
||||
{
|
||||
SetTheme(_config.UseDarkMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitModules()
|
||||
{
|
||||
var _ = tcMain.Handle;
|
||||
|
||||
_nvPanel = InitPanel<NvPanel>("NVIDIA controller");
|
||||
_amdPanel = InitPanel<AmdPanel>("AMD controller");
|
||||
_lgPanel = InitPanel<LgPanel>("LG controller");
|
||||
_samsungPanel = InitPanel<SamsungPanel>("Samsung controller");
|
||||
_gamePanel = InitPanel<GamePanel>("Game launcher");
|
||||
|
||||
tcMain.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private T InitPanel<T>(string displayName) where T : UserControl, IModulePanel
|
||||
{
|
||||
var module = _config.Modules.FirstOrDefault(m => m.IsActive && m.DisplayName == displayName);
|
||||
if (module == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var panel = _serviceProvider.GetRequiredService<T>();
|
||||
panel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
panel.Init();
|
||||
|
||||
var tabPage = new TabPage(module.DisplayName);
|
||||
tcMain.TabPages.Insert(tcMain.TabPages.Count - 2, tabPage);
|
||||
|
||||
tabPage.Controls.Add(panel);
|
||||
|
||||
panel.Size = tabPage.ClientSize;
|
||||
panel.BackColor = SystemColors.Window;
|
||||
|
||||
//Logger.Debug($"Panel {panel.GetType().Name}, size: {panel.Size.Width}x{panel.Size.Height}");
|
||||
|
||||
return panel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"Error creating panel {typeof(T).Name}: {ex.ToLogString()}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenForm()
|
||||
{
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (Program.UserExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(SystemShutdown || EndSession) && _config.MinimizeOnClose)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = FormWindowState.Minimized;
|
||||
Program.UserExit = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalSave();
|
||||
|
||||
if (SystemShutdown)
|
||||
{
|
||||
Logger.Debug($"MainForm_FormClosing: SystemShutdown");
|
||||
}
|
||||
|
||||
Program.Exit();
|
||||
}
|
||||
|
||||
private void GlobalSave()
|
||||
{
|
||||
_serviceManager.Save();
|
||||
|
||||
_nvPanel?.Save();
|
||||
_amdPanel?.Save();
|
||||
_lgPanel?.Save();
|
||||
_gamePanel?.Save();
|
||||
_samsungPanel?.Save();
|
||||
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == NativeConstants.WM_QUERYENDSESSION)
|
||||
{
|
||||
SystemShutdown = true;
|
||||
EndSession = true;
|
||||
_windowMessageDispatcher.DispatchEvent(WindowMessageDispatcher.Event_WindowMessageQueryEndSession, new WindowMessageEventArgs(m));
|
||||
}
|
||||
else if (m.Msg == Utils.WM_BRINGTOFRONT)
|
||||
{
|
||||
Logger.Debug("WM_BRINGTOFRONT message received, opening form");
|
||||
OpenForm();
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private void MainForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (_skipResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
if (_config.MinimizeToTray)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
else if (WindowState == FormWindowState.Normal && _config != null)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_skipResize = true;
|
||||
try
|
||||
{
|
||||
Width = _config.FormWidth;
|
||||
Height = _config.FormHeight;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
if (WindowState != FormWindowState.Minimized)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
|
||||
Utils.WriteObject(Program.ConfigFilename, _config);
|
||||
}
|
||||
|
||||
private void MainForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
|
||||
_elevationService.CheckElevationMethod();
|
||||
}
|
||||
|
||||
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private void InitSelectedTab()
|
||||
{
|
||||
if (tcMain.SelectedTab == tabInfo)
|
||||
{
|
||||
InitInfoTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab == tabOptions)
|
||||
{
|
||||
InitOptionsTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOptionsTab()
|
||||
{
|
||||
if (tabOptions.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _serviceProvider.GetRequiredService<OptionsPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabOptions.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
}
|
||||
|
||||
private void InitInfoTab()
|
||||
{
|
||||
if (tabInfo.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var control = _serviceProvider.GetRequiredService<InfoPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabInfo.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
//control.BackColor = SystemColors.Window;
|
||||
}
|
||||
|
||||
private void MainForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AfterInitialized()
|
||||
{
|
||||
if (_nvPanel != null)
|
||||
{
|
||||
await _nvPanel.AfterInitialized();
|
||||
}
|
||||
if (_amdPanel != null)
|
||||
{
|
||||
await _amdPanel.AfterInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Deactivate(object sender, EventArgs e)
|
||||
{
|
||||
GlobalSave();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeBegin(object sender, EventArgs e)
|
||||
{
|
||||
SuspendLayout();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeEnd(object sender, EventArgs e)
|
||||
{
|
||||
ResumeLayout(true);
|
||||
}
|
||||
|
||||
private async void MainForm_Click(object sender, EventArgs e)
|
||||
{
|
||||
_serviceManager.NvService?.TestResolution();
|
||||
}
|
||||
|
||||
public void SetTheme(bool toDark)
|
||||
{
|
||||
this.UpdateTheme(toDark);
|
||||
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private async void btnUpdate_Click(object sender, EventArgs e)
|
||||
{
|
||||
await _updateManager.InstallUpdate();
|
||||
|
||||
Program.Restart();
|
||||
}
|
||||
|
||||
internal void CloseForRestart()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if ((keyData == (Keys.Control | Keys.PageUp) || keyData == (Keys.Control | Keys.PageDown)) && KeyboardShortcutDispatcher.IsShortcutControlFocused())
|
||||
{
|
||||
var control = this.FindFocusedControl() as TextBox;
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
var keyEvent = new KeyEventArgs(keyData);
|
||||
|
||||
control.Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(keyEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
}
|
||||
Icon = Resources.AppIcon;
|
||||
|
||||
_serviceManager = serviceManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_elevationService = elevationService;
|
||||
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
|
||||
_updateManager = updateManager;
|
||||
_windowMessageDispatcher = windowMessageDispatcher;
|
||||
_config = Program.Config;
|
||||
|
||||
Text = globalContext.ApplicationTitleAdmin;
|
||||
|
||||
LoadConfig();
|
||||
|
||||
MessageForms.MainForm = this;
|
||||
|
||||
InitModules();
|
||||
|
||||
Task.Run(AfterInitialized);
|
||||
|
||||
if (_config.UseDarkMode)
|
||||
{
|
||||
SetTheme(_config.UseDarkMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitModules()
|
||||
{
|
||||
var _ = tcMain.Handle;
|
||||
|
||||
_nvPanel = InitPanel<NvPanel>("NVIDIA controller");
|
||||
_amdPanel = InitPanel<AmdPanel>("AMD controller");
|
||||
_lgPanel = InitPanel<LgPanel>("LG controller");
|
||||
_samsungPanel = InitPanel<SamsungPanel>("Samsung controller");
|
||||
_gamePanel = InitPanel<GamePanel>("Game launcher");
|
||||
|
||||
tcMain.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private T InitPanel<T>(string displayName) where T : UserControl, IModulePanel
|
||||
{
|
||||
var module = _config.Modules.FirstOrDefault(m => m.IsActive && m.DisplayName == displayName);
|
||||
if (module == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var panel = _serviceProvider.GetRequiredService<T>();
|
||||
panel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
panel.Init();
|
||||
|
||||
var tabPage = new TabPage(module.DisplayName);
|
||||
tcMain.TabPages.Insert(tcMain.TabPages.Count - 2, tabPage);
|
||||
|
||||
tabPage.Controls.Add(panel);
|
||||
|
||||
panel.Size = tabPage.ClientSize;
|
||||
panel.BackColor = SystemColors.Window;
|
||||
|
||||
//Logger.Debug($"Panel {panel.GetType().Name}, size: {panel.Size.Width}x{panel.Size.Height}");
|
||||
|
||||
return panel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"Error creating panel {typeof(T).Name}: {ex.ToLogString()}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenForm()
|
||||
{
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (Program.UserExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(SystemShutdown || EndSession) && _config.MinimizeOnClose)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = FormWindowState.Minimized;
|
||||
Program.UserExit = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalSave();
|
||||
|
||||
if (SystemShutdown)
|
||||
{
|
||||
Logger.Debug($"MainForm_FormClosing: SystemShutdown");
|
||||
}
|
||||
|
||||
Program.Exit();
|
||||
}
|
||||
|
||||
private void GlobalSave()
|
||||
{
|
||||
_nvPanel?.Save();
|
||||
_amdPanel?.Save();
|
||||
_lgPanel?.Save();
|
||||
_gamePanel?.Save();
|
||||
_samsungPanel?.Save();
|
||||
|
||||
if (WindowState != FormWindowState.Minimized)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == NativeConstants.WM_QUERYENDSESSION)
|
||||
{
|
||||
SystemShutdown = true;
|
||||
EndSession = true;
|
||||
_windowMessageDispatcher.DispatchEvent(WindowMessageDispatcher.Event_WindowMessageQueryEndSession, new WindowMessageEventArgs(m));
|
||||
}
|
||||
else if (m.Msg == Utils.WM_BRINGTOFRONT)
|
||||
{
|
||||
Logger.Debug("WM_BRINGTOFRONT message received, opening form");
|
||||
OpenForm();
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private void MainForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (_skipResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
if (_config.MinimizeToTray)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
else if (WindowState == FormWindowState.Normal && _config != null)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_skipResize = true;
|
||||
try
|
||||
{
|
||||
Width = _config.FormWidth;
|
||||
Height = _config.FormHeight;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
|
||||
_elevationService.CheckElevationMethod();
|
||||
}
|
||||
|
||||
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private void InitSelectedTab()
|
||||
{
|
||||
if (tcMain.SelectedTab == tabInfo)
|
||||
{
|
||||
InitInfoTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab == tabOptions)
|
||||
{
|
||||
InitOptionsTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOptionsTab()
|
||||
{
|
||||
if (tabOptions.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _serviceProvider.GetRequiredService<OptionsPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabOptions.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
}
|
||||
|
||||
private void InitInfoTab()
|
||||
{
|
||||
if (tabInfo.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var control = _serviceProvider.GetRequiredService<InfoPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabInfo.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
//control.BackColor = SystemColors.Window;
|
||||
}
|
||||
|
||||
private void MainForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AfterInitialized()
|
||||
{
|
||||
if (_nvPanel != null)
|
||||
{
|
||||
await _nvPanel.AfterInitialized();
|
||||
}
|
||||
if (_amdPanel != null)
|
||||
{
|
||||
await _amdPanel.AfterInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Deactivate(object sender, EventArgs e)
|
||||
{
|
||||
GlobalSave();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeBegin(object sender, EventArgs e)
|
||||
{
|
||||
SuspendLayout();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeEnd(object sender, EventArgs e)
|
||||
{
|
||||
ResumeLayout(true);
|
||||
}
|
||||
|
||||
private async void MainForm_Click(object sender, EventArgs e)
|
||||
{
|
||||
_serviceManager.NvService?.TestResolution();
|
||||
}
|
||||
|
||||
public void SetTheme(bool toDark)
|
||||
{
|
||||
this.UpdateTheme(toDark);
|
||||
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private async void btnUpdate_Click(object sender, EventArgs e)
|
||||
{
|
||||
await _updateManager.InstallUpdate();
|
||||
}
|
||||
|
||||
internal void CloseForRestart()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if ((keyData == (Keys.Control | Keys.PageUp) || keyData == (Keys.Control | Keys.PageDown)) && KeyboardShortcutDispatcher.IsShortcutControlFocused())
|
||||
{
|
||||
var control = this.FindFocusedControl() as TextBox;
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
var keyEvent = new KeyEventArgs(keyData);
|
||||
|
||||
control.Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(keyEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,10 @@ internal class MainWorker
|
||||
|
||||
_ = _powerEventDispatcher.SendEventAsync(PowerEventDispatcher.Event_Startup);
|
||||
|
||||
_ = _updateManager.CheckForUpdates();
|
||||
if (_config.CheckForUpdates)
|
||||
{
|
||||
_ = _updateManager.CheckForUpdatesAndInstall();
|
||||
}
|
||||
|
||||
if (!_config.StartMinimized /*|| Debugger.IsAttached*/)
|
||||
{
|
||||
@@ -159,6 +162,8 @@ internal class MainWorker
|
||||
Logger.Debug($"MainWorker: QueryEndSession");
|
||||
Logger.Debug($"MainWorker: SystemShutdown");
|
||||
|
||||
_serviceManager.Save();
|
||||
|
||||
_powerEventDispatcher.SendEvent(PowerEventDispatcher.Event_Shutdown);
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,11 @@ namespace ColorControl
|
||||
_mainForm.OpenForm();
|
||||
}
|
||||
|
||||
public static bool IsMainFormOpened()
|
||||
{
|
||||
return _mainForm != null;
|
||||
}
|
||||
|
||||
public static void Exit()
|
||||
{
|
||||
UserExit = true;
|
||||
@@ -397,6 +402,7 @@ namespace ColorControl
|
||||
services.AddSingleton<WolService>();
|
||||
services.AddSingleton<ColorControlBackgroundService>();
|
||||
services.AddSingleton<ServiceManager>();
|
||||
services.AddSingleton<ColorProfileService>();
|
||||
|
||||
RpcServerService.ServiceTypes.Add(typeof(NvService));
|
||||
RpcServerService.ServiceTypes.Add(typeof(GameService));
|
||||
@@ -405,6 +411,7 @@ namespace ColorControl
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,343 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Contracts.DisplayInfo;
|
||||
using ColorControl.Shared.Native;
|
||||
using ColorControl.Shared.Services;
|
||||
using EDIDParser;
|
||||
using MHC2Gen;
|
||||
using Microsoft.Win32;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using WindowsDisplayAPI;
|
||||
using static ColorControl.Shared.Native.CCD;
|
||||
|
||||
namespace ColorControl.Services.Common;
|
||||
|
||||
public class ColorProfileService
|
||||
{
|
||||
private readonly GlobalContext _globalContext;
|
||||
private readonly RpcClientService _rpcClientService;
|
||||
private readonly WinApiService _winApiService;
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
|
||||
public ColorProfileService(GlobalContext globalContext, RpcClientService rpcClientService, WinApiService winApiService, ServiceManager serviceManager, WinApiAdminService winApiAdminService)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_rpcClientService = rpcClientService;
|
||||
_winApiService = winApiService;
|
||||
_serviceManager = serviceManager;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
|
||||
_rpcClientService.Name = nameof(ColorProfileService);
|
||||
}
|
||||
|
||||
public bool InstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.InstallColorProfile(profileName);
|
||||
}
|
||||
|
||||
public bool UninstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.UninstallColorProfile(profileName);
|
||||
}
|
||||
|
||||
public bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true)
|
||||
{
|
||||
return CCD.AddDisplayColorProfile(displayName, profileName, setAsDefault);
|
||||
}
|
||||
|
||||
|
||||
public bool RemoveDisplayColorProfile(string displayName, string profileName)
|
||||
{
|
||||
return CCD.RemoveDisplayColorProfile(displayName, profileName);
|
||||
}
|
||||
|
||||
public bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true)
|
||||
{
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, fixMaxTML);
|
||||
}
|
||||
|
||||
public string GetDisplayDefaultColorProfile(string displayName, COLORPROFILESUBTYPE profileSubType = COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE)
|
||||
{
|
||||
return CCD.GetDisplayDefaultColorProfile(displayName, profileSubType);
|
||||
}
|
||||
|
||||
public List<string> GetDisplayColorProfiles(string displayName)
|
||||
{
|
||||
if (displayName != null && !displayName.StartsWith("\\\\"))
|
||||
{
|
||||
displayName = GetDisplayNameByDisplayId(displayName);
|
||||
}
|
||||
|
||||
return CCD.GetDisplayColorProfiles(displayName);
|
||||
}
|
||||
|
||||
public List<string> GetAllColorProfileNames()
|
||||
{
|
||||
if (!_winApiService.IsAdministrator())
|
||||
{
|
||||
return _rpcClientService.Call<List<string>>("GetAllColorProfileNames");
|
||||
}
|
||||
|
||||
return CCD.GetAllColorProfileNames();
|
||||
}
|
||||
|
||||
private string GetDisplayNameByDisplayId(string displayName)
|
||||
{
|
||||
var deviceName = default(string);
|
||||
|
||||
Display.GetDisplays().FirstOrDefault(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
|
||||
if (ccdDisplayInfo.DisplayId == displayName)
|
||||
{
|
||||
deviceName = ccdDisplayInfo.DisplayName;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public bool GetUsePerUserDisplayProfiles(string displayName)
|
||||
{
|
||||
return CCD.GetUsePerUserDisplayProfiles(displayName);
|
||||
}
|
||||
|
||||
public bool SetUsePerUserDisplayProfiles(string displayName, bool usePerUserProfiles)
|
||||
{
|
||||
return CCD.SetUsePerUserDisplayProfiles(displayName, usePerUserProfiles);
|
||||
}
|
||||
|
||||
public bool DeleteColorProfile(string name)
|
||||
{
|
||||
return CCD.UninstallColorProfile(name);
|
||||
}
|
||||
|
||||
public ColorProfileDto GetColorProfile(string name)
|
||||
{
|
||||
var data = MHC2Wrapper.LoadProfile(name, true);
|
||||
|
||||
return new ColorProfileDto
|
||||
{
|
||||
Name = name,
|
||||
HasExtraInfo = data.HasExtraInfo,
|
||||
BlackLuminance = data.BlackLuminance,
|
||||
ColorGamut = data.ColorGamut,
|
||||
Description = data.Description,
|
||||
DevicePrimaries = new RgbPrimariesDto(data.DevicePrimaries),
|
||||
Gamma = data.Gamma,
|
||||
IsHDRProfile = data.IsHDRProfile,
|
||||
MaxCLL = data.MaxCLL,
|
||||
MinCLL = data.MinCLL,
|
||||
SDRBrightnessBoost = data.SDRBrightnessBoost,
|
||||
SDRMaxBrightness = data.SDRMaxBrightness,
|
||||
SDRMinBrightness = data.SDRMinBrightness,
|
||||
SDRTransferFunction = data.SDRTransferFunction,
|
||||
ShadowDetailBoost = data.ShadowDetailBoost,
|
||||
WhiteLuminance = data.WhiteLuminance,
|
||||
};
|
||||
}
|
||||
|
||||
public bool UpdateColorProfile(ColorProfileDto colorProfile, string displayName = null)
|
||||
{
|
||||
var command = new GenerateProfileCommand
|
||||
{
|
||||
Description = colorProfile.Description,
|
||||
IsHDRProfile = colorProfile.IsHDRProfile,
|
||||
MinCLL = colorProfile.MinCLL,
|
||||
MaxCLL = colorProfile.MaxCLL,
|
||||
BlackLuminance = colorProfile.BlackLuminance,
|
||||
WhiteLuminance = colorProfile.WhiteLuminance,
|
||||
SDRMinBrightness = colorProfile.SDRMinBrightness,
|
||||
SDRMaxBrightness = colorProfile.SDRMaxBrightness,
|
||||
SDRTransferFunction = colorProfile.SDRTransferFunction,
|
||||
SDRBrightnessBoost = colorProfile.SDRBrightnessBoost,
|
||||
ShadowDetailBoost = colorProfile.ShadowDetailBoost,
|
||||
ColorGamut = colorProfile.ColorGamut,
|
||||
Gamma = colorProfile.Gamma,
|
||||
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
|
||||
};
|
||||
|
||||
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
|
||||
|
||||
var tempFilename = Path.GetFullPath(colorProfile.Name, Path.GetTempPath());
|
||||
|
||||
if (File.Exists(tempFilename))
|
||||
{
|
||||
File.Delete(tempFilename);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(tempFilename, bytes);
|
||||
|
||||
_winApiAdminService.UninstallColorProfile(tempFilename);
|
||||
|
||||
var result = CCD.InstallColorProfile(tempFilename);
|
||||
|
||||
if (result && displayName != null)
|
||||
{
|
||||
CCD.AddDisplayColorProfile(displayName, colorProfile.Name, false);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool InstallAndActivateColorProfile(ColorProfileDto colorProfile, string displayName)
|
||||
{
|
||||
var installed = UpdateColorProfile(colorProfile);
|
||||
|
||||
if (!installed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var profileName = colorProfile.Name;
|
||||
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, true);
|
||||
}
|
||||
|
||||
public List<DisplayInfoDto> GetDisplayInfos(bool? onlyHdrEnabled = null)
|
||||
{
|
||||
return Display.GetDisplays().Select(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(d.DisplayName);
|
||||
var colorParams = CCD.GetColorParams(d.DisplayName);
|
||||
|
||||
var displayColors = new List<DisplayColorInfo>
|
||||
{
|
||||
new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Custom,
|
||||
CustomName = "sRGB",
|
||||
RgbPrimaries = new RgbPrimariesDto(RgbPrimaries.sRGB),
|
||||
BlackLuminance = 0,
|
||||
WhiteLuminance = 100,
|
||||
MinCLL = 0,
|
||||
MaxCLL = 100
|
||||
}
|
||||
};
|
||||
|
||||
if (!colorParams.Equals(default(ColorParams)))
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Windows,
|
||||
RgbPrimaries = ColorParamsToRgbPrimaries(colorParams),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
|
||||
var edid = GetEDIDInternal(d.DevicePath);
|
||||
if (edid != null)
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.EDID,
|
||||
RgbPrimaries = EdidToRgbPrimaries(edid),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
|
||||
return new DisplayInfoDto
|
||||
{
|
||||
DisplayName = ccdDisplayInfo.ExtendedName,
|
||||
DevicePath = ccdDisplayInfo.DevicePath,
|
||||
DisplayId = ccdDisplayInfo.DisplayId,
|
||||
IsActive = true,
|
||||
DeviceName = d.DisplayName,
|
||||
AdapterName = d.Adapter.DeviceName,
|
||||
IsAdvancedColorEnabled = advancedColor.isEnabled,
|
||||
IsAdvancedColorSupported = advancedColor.isSupported,
|
||||
DisplayColors = displayColors,
|
||||
ColorProfiles = GetDisplayColorProfiles(d.DisplayName),
|
||||
DefaultColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, advancedColor.isEnabled ? COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE : COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE)
|
||||
};
|
||||
})
|
||||
.Where(d => !onlyHdrEnabled.HasValue || onlyHdrEnabled == d.IsHdrSupportedAndEnabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public RgbPrimariesDto ColorParamsToRgbPrimaries(ColorParams colorParams)
|
||||
{
|
||||
var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
|
||||
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.RedPointX / divider,
|
||||
Y = (double)colorParams.RedPointY / divider
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.GreenPointX / divider,
|
||||
Y = (double)colorParams.GreenPointY / divider
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.BluePointX / divider,
|
||||
Y = (double)colorParams.BluePointY / divider
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.WhitePointX / divider,
|
||||
Y = (double)colorParams.WhitePointY / divider
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public RgbPrimariesDto EdidToRgbPrimaries(EDID edid)
|
||||
{
|
||||
var coords = edid.DisplayParameters.ChromaticityCoordinates;
|
||||
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = coords.RedX,
|
||||
Y = coords.RedY
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = coords.GreenX,
|
||||
Y = coords.GreenY
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = coords.BlueX,
|
||||
Y = coords.BlueY
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = coords.WhiteX,
|
||||
Y = coords.WhiteY
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static EDID GetEDIDInternal(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryPath = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\";
|
||||
registryPath += string.Join("\\", path.Split('#').Skip(1).Take(2));
|
||||
return new EDID((byte[])Registry.GetValue(registryPath + "\\Device Parameters", "EDID", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ColorControl.Services.Common;
|
||||
|
||||
@@ -17,8 +18,9 @@ public class OptionsService
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly UpdateManager _updateManager;
|
||||
|
||||
public OptionsService(GlobalContext globalContext, ElevationService elevationService, WinApiService winApiService, ServiceManager serviceManager, KeyboardShortcutDispatcher keyboardShortcutDispatcher, WinApiAdminService winApiAdminService)
|
||||
public OptionsService(GlobalContext globalContext, ElevationService elevationService, WinApiService winApiService, ServiceManager serviceManager, KeyboardShortcutDispatcher keyboardShortcutDispatcher, WinApiAdminService winApiAdminService, UpdateManager updateManager)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_elevationService = elevationService;
|
||||
@@ -26,6 +28,7 @@ public class OptionsService
|
||||
_serviceManager = serviceManager;
|
||||
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
_updateManager = updateManager;
|
||||
}
|
||||
|
||||
public Config GetConfig()
|
||||
@@ -46,24 +49,42 @@ public class OptionsService
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SaveSettings()
|
||||
{
|
||||
_serviceManager.Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<ModuleDto> GetModules()
|
||||
{
|
||||
return _globalContext.Config.Modules.Select(m => new ModuleDto { DisplayName = m.DisplayName, IsActive = m.IsActive, Info = _serviceManager.GetServiceInfo(m.DisplayName) }).ToList();
|
||||
}
|
||||
|
||||
public InfoDto GetInfo()
|
||||
public async Task<InfoDto> GetInfo()
|
||||
{
|
||||
var updateInfo = await _updateManager.CheckForUpdates();
|
||||
|
||||
return new InfoDto
|
||||
{
|
||||
ApplicationTitle = _globalContext.ApplicationTitle,
|
||||
ApplicationTitleAdmin = _globalContext.ApplicationTitleAdmin,
|
||||
DataPath = _globalContext.DataPath,
|
||||
StartTime = _globalContext.StartTime,
|
||||
UpdateAvailable = _globalContext.UpdateAvailable,
|
||||
UpdateInfoDto = updateInfo,
|
||||
LegalCopyright = _globalContext.LegalCopyright,
|
||||
};
|
||||
}
|
||||
|
||||
public Task<bool> InstallUpdate()
|
||||
{
|
||||
return _updateManager.InstallUpdate();
|
||||
}
|
||||
|
||||
public Task<bool> RestartAfterUpdate()
|
||||
{
|
||||
return _updateManager.RestartAfterUpdate();
|
||||
}
|
||||
|
||||
public bool SetStartAfterLogin(bool value)
|
||||
{
|
||||
_elevationService.RegisterScheduledTask(value);
|
||||
@@ -216,9 +237,14 @@ public class OptionsService
|
||||
|
||||
module.IsActive = updateModule.IsActive;
|
||||
|
||||
_serviceManager.ActivateModule(module);
|
||||
var result = _serviceManager.ActivateModule(module);
|
||||
|
||||
return true;
|
||||
if (module.IsActive && !result)
|
||||
{
|
||||
module.IsActive = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,344 +15,351 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ColorControl.Services.Common
|
||||
{
|
||||
abstract class ServiceBase<T> : IServiceBase where T : PresetBase, new()
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public event EventHandler<T> AfterApplyPreset;
|
||||
|
||||
public abstract string ServiceName
|
||||
{
|
||||
get;
|
||||
}
|
||||
protected abstract string PresetsBaseFilename { get; }
|
||||
|
||||
protected string _dataPath;
|
||||
protected string _presetsFilename;
|
||||
protected string _presetsBackupFilename;
|
||||
protected bool _initialized = false;
|
||||
protected List<T> _presets;
|
||||
protected T _lastAppliedPreset;
|
||||
protected string _loadPresetsError;
|
||||
protected T _lastTriggeredPreset;
|
||||
protected int _quickAccessShortcutId;
|
||||
|
||||
protected GlobalContext _globalContext;
|
||||
|
||||
private List<JsonConverter> _jsonConverters;
|
||||
|
||||
public ServiceBase(GlobalContext globalContext)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_dataPath = globalContext.DataPath;
|
||||
_jsonConverters = new List<JsonConverter>();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
~ServiceBase()
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
public abstract List<string> GetInfo();
|
||||
|
||||
public List<T> GetPresets()
|
||||
{
|
||||
return _presets;
|
||||
}
|
||||
|
||||
public T GetPresetByIdOrName(string idOrName)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.name != null && p.name.Equals(idOrName, StringComparison.OrdinalIgnoreCase));
|
||||
if (preset == null && int.TryParse(idOrName, out var id))
|
||||
{
|
||||
preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
public bool DeletePreset(int id)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
_presets.Remove(preset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> ApplyPresetWithId(int id)
|
||||
{
|
||||
return ApplyPreset(id.ToString());
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyPreset(string idOrName)
|
||||
{
|
||||
var preset = GetPresetByIdOrName(idOrName);
|
||||
if (preset != null)
|
||||
{
|
||||
return await ApplyPreset(preset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task<bool> ApplyPreset(T preset);
|
||||
|
||||
public async Task<bool> ApplyPresetUi(T preset)
|
||||
{
|
||||
if (preset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var result = await ApplyPreset(preset);
|
||||
if (!result)
|
||||
{
|
||||
throw new Exception($"Error while applying {ServiceName}-preset. At least one setting could not be applied. Check the log for details.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e);
|
||||
|
||||
if (!_globalContext.Config.DisableErrorPopupWhenApplyingPreset)
|
||||
{
|
||||
MessageForms.ErrorOk($"Error applying {ServiceName}-preset ({preset.IdOrName}): {e.Message}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyPresetById(int id)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
|
||||
if (preset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await ApplyPresetUi(preset);
|
||||
}
|
||||
|
||||
protected void SetShortcuts(int quickAccessShortcutId = 0, string shortcut = null)
|
||||
{
|
||||
_quickAccessShortcutId = quickAccessShortcutId;
|
||||
|
||||
var keyboardShortcutDispatcher = _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
|
||||
|
||||
if (keyboardShortcutDispatcher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (quickAccessShortcutId != 0)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(quickAccessShortcutId, shortcut);
|
||||
}
|
||||
|
||||
foreach (var preset in _presets)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut);
|
||||
}
|
||||
|
||||
keyboardShortcutDispatcher.RegisterAsyncEventHandler(KeyboardShortcutDispatcher.Event_HotKey, HotKeyPressed);
|
||||
}
|
||||
|
||||
public virtual void InstallEventHandlers()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public T GetLastAppliedPreset()
|
||||
{
|
||||
return _lastAppliedPreset;
|
||||
}
|
||||
|
||||
public virtual T CreateNewPreset()
|
||||
{
|
||||
var preset = new T();
|
||||
preset.name = CreateNewPresetName();
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
public string CreateNewPresetName()
|
||||
{
|
||||
var name = "New preset";
|
||||
string fullname;
|
||||
var number = 1;
|
||||
do
|
||||
{
|
||||
fullname = $"{name} ({number})";
|
||||
number++;
|
||||
} while (_presets.Any(x => x.name?.Equals(fullname) == true));
|
||||
|
||||
return fullname;
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Uninitialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void PresetApplied()
|
||||
{
|
||||
if (_lastAppliedPreset != null)
|
||||
{
|
||||
_lastAppliedPreset.LastUsed = DateTime.Now;
|
||||
}
|
||||
|
||||
AfterApplyPreset?.Invoke(this, _lastAppliedPreset);
|
||||
}
|
||||
|
||||
protected void LoadPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, PresetsBaseFilename);
|
||||
_presetsBackupFilename = Path.Combine(_dataPath, $"{PresetsBaseFilename}.backup");
|
||||
|
||||
try
|
||||
{
|
||||
var presetsExists = File.Exists(_presetsFilename);
|
||||
if (presetsExists)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
// Hack to convert incorrect triggers
|
||||
json = json.Replace(@"""Triggers"":0", @"""Triggers"":[]");
|
||||
|
||||
_presets = JsonConvert.DeserializeObject<List<T>>(json, _jsonConverters.ToArray());
|
||||
|
||||
if (_presets != null)
|
||||
{
|
||||
if (File.Exists(_presetsBackupFilename))
|
||||
{
|
||||
File.Delete(_presetsBackupFilename);
|
||||
}
|
||||
File.Copy(_presetsFilename, _presetsBackupFilename);
|
||||
}
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
Logger.Error($"Error while loading presets, reverting to default presets: {ex1.Message}");
|
||||
_loadPresetsError = ex1.Message;
|
||||
}
|
||||
}
|
||||
if (!presetsExists || _presets == null)
|
||||
{
|
||||
Logger.Debug("Reverting to default presets");
|
||||
try
|
||||
{
|
||||
_presets = GetDefaultPresets();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error loading default presets, reverting to empty list: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"General error while loading presets, reverting to empty list: {ex.Message}");
|
||||
_loadPresetsError = ex.Message;
|
||||
}
|
||||
if (_presets == null)
|
||||
{
|
||||
_presets = new List<T>();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<T> GetDefaultPresets();
|
||||
|
||||
protected void AddJsonConverter(JsonConverter jsonConverter)
|
||||
{
|
||||
_jsonConverters.Add(jsonConverter);
|
||||
}
|
||||
|
||||
public void ToggleQuickAccessForm()
|
||||
{
|
||||
QuickAccessForm<T>.ToggleQuickAccessForm(this);
|
||||
}
|
||||
|
||||
protected async Task<PresetTriggerContext> CreateTriggerContext(ServiceManager serviceManager, ProcessChangedEventArgs context = null, bool? isHDRActive = null, IList<PresetTriggerType> triggerTypes = null)
|
||||
{
|
||||
triggerTypes ??= new[] { PresetTriggerType.ProcessSwitch };
|
||||
|
||||
var changedProcesses = new List<Process>();
|
||||
if (context?.ForegroundProcess != null)
|
||||
{
|
||||
changedProcesses.Add(context.ForegroundProcess);
|
||||
}
|
||||
|
||||
var isGsyncActive = await serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" });
|
||||
|
||||
var triggerContext = new PresetTriggerContext
|
||||
{
|
||||
Triggers = triggerTypes,
|
||||
IsHDRActive = isHDRActive ?? CCD.IsHDREnabled(),
|
||||
IsGsyncActive = isGsyncActive,
|
||||
ForegroundProcess = context?.ForegroundProcess,
|
||||
ForegroundProcessIsFullScreen = context?.ForegroundProcessIsFullScreen ?? false,
|
||||
IsNotificationDisabled = context?.IsNotificationDisabled ?? false,
|
||||
ChangedProcesses = changedProcesses,
|
||||
ScreenSaverTransitionState = context?.ScreenSaverTransitionState ?? ScreenSaverTransitionState.None
|
||||
};
|
||||
|
||||
return triggerContext;
|
||||
}
|
||||
|
||||
protected async Task ExecuteScreenSaverPresets(ServiceManager serviceManager, ProcessChangedEventArgs context, bool? isHDRActive = null)
|
||||
{
|
||||
await ExecuteEventPresets(serviceManager, new[] { PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop }, context, isHDRActive);
|
||||
}
|
||||
|
||||
public async Task ExecuteEventPresets(ServiceManager serviceManager, IList<PresetTriggerType> triggerTypes, ProcessChangedEventArgs context = null, bool? isHDRActive = null)
|
||||
{
|
||||
var triggerContext = await CreateTriggerContext(serviceManager, context, isHDRActive, triggerTypes);
|
||||
|
||||
var triggerPresets = _presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList();
|
||||
|
||||
if (!triggerPresets.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug($"Executing event: {string.Join(',', triggerTypes)}, presets count: {triggerPresets.Count}");
|
||||
|
||||
foreach (var preset in triggerPresets)
|
||||
{
|
||||
Logger.Debug($"Executing event preset: {preset.name}");
|
||||
|
||||
await ApplyPreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task HotKeyPressed(object sender, KeyboardShortcutEventArgs e, CancellationToken _)
|
||||
{
|
||||
if (e.HotKeyId == _quickAccessShortcutId)
|
||||
{
|
||||
ToggleQuickAccessForm();
|
||||
return;
|
||||
}
|
||||
|
||||
await ApplyPresetById(e.HotKeyId);
|
||||
}
|
||||
}
|
||||
abstract class ServiceBase<T> : IServiceBase where T : PresetBase, new()
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public event EventHandler<T> AfterApplyPreset;
|
||||
|
||||
public abstract string ServiceName
|
||||
{
|
||||
get;
|
||||
}
|
||||
protected abstract string PresetsBaseFilename { get; }
|
||||
|
||||
protected string _dataPath;
|
||||
protected string _presetsFilename;
|
||||
protected string _presetsBackupFilename;
|
||||
protected bool _initialized = false;
|
||||
protected List<T> _presets;
|
||||
protected T _lastAppliedPreset;
|
||||
protected string _loadPresetsError;
|
||||
protected T _lastTriggeredPreset;
|
||||
protected int _quickAccessShortcutId;
|
||||
|
||||
protected GlobalContext _globalContext;
|
||||
|
||||
private List<JsonConverter> _jsonConverters;
|
||||
|
||||
public ServiceBase(GlobalContext globalContext)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_dataPath = globalContext.DataPath;
|
||||
_jsonConverters = new List<JsonConverter>();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
~ServiceBase()
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
public abstract List<string> GetInfo();
|
||||
|
||||
public List<T> GetPresets()
|
||||
{
|
||||
return _presets;
|
||||
}
|
||||
|
||||
public T GetPresetByIdOrName(string idOrName)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.name != null && p.name.Equals(idOrName, StringComparison.OrdinalIgnoreCase));
|
||||
if (preset == null && int.TryParse(idOrName, out var id))
|
||||
{
|
||||
preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
public bool DeletePreset(int id)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
_presets.Remove(preset);
|
||||
|
||||
SavePresets();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> ApplyPresetWithId(int id)
|
||||
{
|
||||
return ApplyPreset(id.ToString());
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyPreset(string idOrName)
|
||||
{
|
||||
var preset = GetPresetByIdOrName(idOrName);
|
||||
if (preset != null)
|
||||
{
|
||||
return await ApplyPreset(preset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Task<bool> ApplyPreset(T preset);
|
||||
|
||||
public async Task<bool> ApplyPresetUi(T preset)
|
||||
{
|
||||
if (preset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var result = await ApplyPreset(preset);
|
||||
if (!result)
|
||||
{
|
||||
throw new Exception($"Error while applying {ServiceName}-preset. At least one setting could not be applied. Check the log for details.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e);
|
||||
|
||||
if (!_globalContext.Config.DisableErrorPopupWhenApplyingPreset)
|
||||
{
|
||||
MessageForms.ErrorOk($"Error applying {ServiceName}-preset ({preset.IdOrName}): {e.Message}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyPresetById(int id)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(p => p.id == id);
|
||||
|
||||
if (preset == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return await ApplyPresetUi(preset);
|
||||
}
|
||||
|
||||
protected void SetShortcuts(int quickAccessShortcutId = 0, string shortcut = null)
|
||||
{
|
||||
_quickAccessShortcutId = quickAccessShortcutId;
|
||||
|
||||
var keyboardShortcutDispatcher = _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
|
||||
|
||||
if (keyboardShortcutDispatcher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (quickAccessShortcutId != 0)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(quickAccessShortcutId, shortcut);
|
||||
}
|
||||
|
||||
foreach (var preset in _presets)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut);
|
||||
}
|
||||
|
||||
keyboardShortcutDispatcher.RegisterAsyncEventHandler(KeyboardShortcutDispatcher.Event_HotKey, HotKeyPressed);
|
||||
}
|
||||
|
||||
public virtual void InstallEventHandlers()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public T GetLastAppliedPreset()
|
||||
{
|
||||
return _lastAppliedPreset;
|
||||
}
|
||||
|
||||
public virtual T CreateNewPreset()
|
||||
{
|
||||
var preset = new T();
|
||||
preset.name = CreateNewPresetName();
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
public string CreateNewPresetName()
|
||||
{
|
||||
var name = "New preset";
|
||||
string fullname;
|
||||
var number = 1;
|
||||
do
|
||||
{
|
||||
fullname = $"{name} ({number})";
|
||||
number++;
|
||||
} while (_presets.Any(x => x.name?.Equals(fullname) == true));
|
||||
|
||||
return fullname;
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Uninitialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void PresetApplied()
|
||||
{
|
||||
if (_lastAppliedPreset != null)
|
||||
{
|
||||
_lastAppliedPreset.LastUsed = DateTime.Now;
|
||||
}
|
||||
|
||||
AfterApplyPreset?.Invoke(this, _lastAppliedPreset);
|
||||
}
|
||||
|
||||
protected void LoadPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, PresetsBaseFilename);
|
||||
_presetsBackupFilename = Path.Combine(_dataPath, $"{PresetsBaseFilename}.backup");
|
||||
|
||||
try
|
||||
{
|
||||
var presetsExists = File.Exists(_presetsFilename);
|
||||
if (presetsExists)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
// Hack to convert incorrect triggers
|
||||
json = json.Replace(@"""Triggers"":0", @"""Triggers"":[]");
|
||||
|
||||
_presets = JsonConvert.DeserializeObject<List<T>>(json, _jsonConverters.ToArray());
|
||||
|
||||
if (_presets != null)
|
||||
{
|
||||
if (File.Exists(_presetsBackupFilename))
|
||||
{
|
||||
File.Delete(_presetsBackupFilename);
|
||||
}
|
||||
File.Copy(_presetsFilename, _presetsBackupFilename);
|
||||
}
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
Logger.Error($"Error while loading presets, reverting to default presets: {ex1.Message}");
|
||||
_loadPresetsError = ex1.Message;
|
||||
}
|
||||
}
|
||||
if (!presetsExists || _presets == null)
|
||||
{
|
||||
Logger.Debug("Reverting to default presets");
|
||||
try
|
||||
{
|
||||
_presets = GetDefaultPresets();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error loading default presets, reverting to empty list: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"General error while loading presets, reverting to empty list: {ex.Message}");
|
||||
_loadPresetsError = ex.Message;
|
||||
}
|
||||
if (_presets == null)
|
||||
{
|
||||
_presets = new List<T>();
|
||||
}
|
||||
}
|
||||
|
||||
protected void SavePresets()
|
||||
{
|
||||
Utils.WriteObject(_presetsFilename, _presets);
|
||||
}
|
||||
|
||||
protected abstract List<T> GetDefaultPresets();
|
||||
|
||||
protected void AddJsonConverter(JsonConverter jsonConverter)
|
||||
{
|
||||
_jsonConverters.Add(jsonConverter);
|
||||
}
|
||||
|
||||
public void ToggleQuickAccessForm()
|
||||
{
|
||||
QuickAccessForm<T>.ToggleQuickAccessForm(this);
|
||||
}
|
||||
|
||||
protected async Task<PresetTriggerContext> CreateTriggerContext(ServiceManager serviceManager, ProcessChangedEventArgs context = null, bool? isHDRActive = null, IList<PresetTriggerType> triggerTypes = null)
|
||||
{
|
||||
triggerTypes ??= new[] { PresetTriggerType.ProcessSwitch };
|
||||
|
||||
var changedProcesses = new List<Process>();
|
||||
if (context?.ForegroundProcess != null)
|
||||
{
|
||||
changedProcesses.Add(context.ForegroundProcess);
|
||||
}
|
||||
|
||||
var isGsyncActive = await serviceManager.HandleExternalServiceAsync("GsyncEnabled", new[] { "" });
|
||||
|
||||
var triggerContext = new PresetTriggerContext
|
||||
{
|
||||
Triggers = triggerTypes,
|
||||
IsHDRActive = isHDRActive ?? CCD.IsHDREnabled(),
|
||||
IsGsyncActive = isGsyncActive,
|
||||
ForegroundProcess = context?.ForegroundProcess,
|
||||
ForegroundProcessIsFullScreen = context?.ForegroundProcessIsFullScreen ?? false,
|
||||
IsNotificationDisabled = context?.IsNotificationDisabled ?? false,
|
||||
ChangedProcesses = changedProcesses,
|
||||
ScreenSaverTransitionState = context?.ScreenSaverTransitionState ?? ScreenSaverTransitionState.None
|
||||
};
|
||||
|
||||
return triggerContext;
|
||||
}
|
||||
|
||||
protected async Task ExecuteScreenSaverPresets(ServiceManager serviceManager, ProcessChangedEventArgs context, bool? isHDRActive = null)
|
||||
{
|
||||
await ExecuteEventPresets(serviceManager, new[] { PresetTriggerType.ScreensaverStart, PresetTriggerType.ScreensaverStop }, context, isHDRActive);
|
||||
}
|
||||
|
||||
public async Task ExecuteEventPresets(ServiceManager serviceManager, IList<PresetTriggerType> triggerTypes, ProcessChangedEventArgs context = null, bool? isHDRActive = null)
|
||||
{
|
||||
var triggerContext = await CreateTriggerContext(serviceManager, context, isHDRActive, triggerTypes);
|
||||
|
||||
var triggerPresets = _presets.Where(p => p.Triggers.Any(t => t.TriggerActive(triggerContext))).ToList();
|
||||
|
||||
if (!triggerPresets.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Debug($"Executing event: {string.Join(',', triggerTypes)}, presets count: {triggerPresets.Count}");
|
||||
|
||||
foreach (var preset in triggerPresets)
|
||||
{
|
||||
Logger.Debug($"Executing event preset: {preset.name}");
|
||||
|
||||
await ApplyPreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task HotKeyPressed(object sender, KeyboardShortcutEventArgs e, CancellationToken _)
|
||||
{
|
||||
if (e.HotKeyId == _quickAccessShortcutId)
|
||||
{
|
||||
ToggleQuickAccessForm();
|
||||
return;
|
||||
}
|
||||
|
||||
await ApplyPresetById(e.HotKeyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,14 +109,16 @@ public class ServiceManager
|
||||
return default;
|
||||
}
|
||||
|
||||
public void ActivateModule(Module module)
|
||||
public bool ActivateModule(Module module)
|
||||
{
|
||||
if (!module.IsActive)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadModules(module.DisplayName);
|
||||
|
||||
return Services.ContainsKey(module.DisplayName);
|
||||
}
|
||||
|
||||
public async Task<bool> HandleExternalServiceAsync(string serviceName, string[] parameters)
|
||||
|
||||
@@ -19,8 +19,6 @@ public class UpdateManager
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly Config _config;
|
||||
|
||||
private bool _checkedForUpdates;
|
||||
private string _updateHtmlUrl;
|
||||
private string _downloadUrl;
|
||||
|
||||
public UpdateManager(GlobalContext globalContext, ServiceManager serviceManager, NotifyIconManager notifyIconManager, WinApiService winApiService, WinApiAdminService winApiAdminService)
|
||||
@@ -35,40 +33,102 @@ public class UpdateManager
|
||||
|
||||
private void trayIconBalloonTip_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (_updateHtmlUrl != null)
|
||||
if (_globalContext.UpdateInfo?.HtmlUrl != null)
|
||||
{
|
||||
_winApiAdminService.StartProcess(_updateHtmlUrl);
|
||||
_winApiAdminService.StartProcess(_globalContext.UpdateInfo?.HtmlUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckForUpdates()
|
||||
public async Task CheckForUpdatesAndInstall()
|
||||
{
|
||||
if (!_config.CheckForUpdates || _checkedForUpdates || Debugger.IsAttached)
|
||||
var updateInfo = await CheckForUpdates();
|
||||
|
||||
if (!updateInfo.UpdateAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_checkedForUpdates = true;
|
||||
_notifyIconManager.NotifyIcon.BalloonTipClicked += trayIconBalloonTip_Clicked;
|
||||
var newVersion = updateInfo.NewVersionNumber;
|
||||
|
||||
await Utils.GetRestJsonAsync("https://api.github.com/repos/maassoft/colorcontrol/releases/latest", InitHandleCheckForUpdates);
|
||||
}
|
||||
|
||||
private void InitHandleCheckForUpdates(dynamic latest)
|
||||
{
|
||||
HandleCheckForUpdates(latest);
|
||||
}
|
||||
|
||||
private async void HandleCheckForUpdates(dynamic latest)
|
||||
{
|
||||
if (latest?.tag_name == null)
|
||||
if (!string.IsNullOrEmpty(updateInfo.DownloadUrl) && _winApiService.IsServiceRunning())
|
||||
{
|
||||
if (_config.AutoInstallUpdates)
|
||||
{
|
||||
await InstallUpdate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click on the Update-button to update", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Update-button to update", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click to open the GitHub page", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Help-button to open the GitHub page.", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UpdateInfoDto> CheckForUpdates()
|
||||
{
|
||||
if (_globalContext.UpdateInfo != null)
|
||||
{
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
|
||||
//if (Debugger.IsAttached)
|
||||
//{
|
||||
// return _globalContext.UpdateInfo = new UpdateInfoDto
|
||||
// {
|
||||
// UpdateAvailable = true,
|
||||
// NewVersionNumber = "10.0.0.2",
|
||||
// DownloadUrl = "http://testurl"
|
||||
// };
|
||||
//}
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
|
||||
var json = await Utils.GetRestJsonAsync("https://api.github.com/repos/maassoft/colorcontrol/releases/latest");
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
|
||||
_globalContext.UpdateInfo = GetUpdateInfo(json);
|
||||
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
|
||||
private static UpdateInfoDto GetUpdateInfo(dynamic json)
|
||||
{
|
||||
var updateInfo = new UpdateInfoDto();
|
||||
|
||||
if (json?.tag_name == null)
|
||||
{
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
var currentVersion = Application.ProductVersion;
|
||||
var cvParts = currentVersion.Split(".");
|
||||
|
||||
var newVersion = (string)latest.tag_name.Value.Substring(1);
|
||||
var newVersion = (string)json.tag_name.Value.Substring(1);
|
||||
var nvParts = newVersion.Split(".");
|
||||
|
||||
bool CompareVersions()
|
||||
@@ -99,56 +159,36 @@ public class UpdateManager
|
||||
return result && aNumberIsLarger;
|
||||
}
|
||||
|
||||
if (nvParts.Length != cvParts.Length || CompareVersions())
|
||||
updateInfo.NewVersionNumber = newVersion;
|
||||
updateInfo.UpdateAvailable = nvParts.Length != cvParts.Length || CompareVersions();
|
||||
|
||||
updateInfo.HtmlUrl = json.html_url.Value;
|
||||
|
||||
if (json.assets != null)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.BalloonTipClicked += trayIconBalloonTip_Clicked;
|
||||
|
||||
_updateHtmlUrl = latest.html_url.Value;
|
||||
|
||||
if (latest.assets != null && _winApiService.IsServiceRunning())
|
||||
{
|
||||
var asset = latest.assets[0];
|
||||
_downloadUrl = asset.browser_download_url.Value;
|
||||
|
||||
if (_config.AutoInstallUpdates)
|
||||
{
|
||||
await InstallUpdate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_globalContext.UpdateAvailable = true;
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click on the Update-button to update", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Update-button to update", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click to open the GitHub page", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Help-button to open the GitHub page.", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
var asset = json.assets[0];
|
||||
updateInfo.DownloadUrl = asset.browser_download_url.Value;
|
||||
}
|
||||
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
public async Task InstallUpdate()
|
||||
public async Task<bool> InstallUpdate()
|
||||
{
|
||||
if (_downloadUrl == null) { return; }
|
||||
if (_globalContext.UpdateInfo?.DownloadUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
return true;
|
||||
}
|
||||
|
||||
var message = new SvcInstallUpdateMessage
|
||||
{
|
||||
DownloadUrl = _downloadUrl,
|
||||
DownloadUrl = _globalContext.UpdateInfo?.DownloadUrl,
|
||||
ClientPath = new FileInfo(Application.ExecutablePath).Directory.FullName
|
||||
};
|
||||
|
||||
@@ -156,21 +196,28 @@ public class UpdateManager
|
||||
|
||||
if (result != null && !result.Result)
|
||||
{
|
||||
MessageForms.ErrorOk($"Error while updating: {result.ErrorMessage}");
|
||||
if (Program.IsMainFormOpened())
|
||||
{
|
||||
MessageForms.ErrorOk($"Error while updating: {result.ErrorMessage}");
|
||||
}
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
await PipeUtils.SendMessageAsync(SvcMessageType.RestartAfterUpdate);
|
||||
|
||||
if (MessageForms.QuestionYesNo("Update installed successfully. Do you want to restart the application?") == DialogResult.Yes)
|
||||
if (Program.IsMainFormOpened() && MessageForms.QuestionYesNo("Update installed successfully. Do you want to restart the application?") == DialogResult.Yes)
|
||||
{
|
||||
Program.Restart();
|
||||
}
|
||||
else
|
||||
{
|
||||
_downloadUrl = null;
|
||||
_globalContext.UpdateAvailable = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> RestartAfterUpdate()
|
||||
{
|
||||
Program.Restart();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -144,11 +144,6 @@ namespace ColorControl.Services.GameLauncher
|
||||
return new List<GamePreset>();
|
||||
}
|
||||
|
||||
private void SavePresets()
|
||||
{
|
||||
Utils.WriteObject(_presetsFilename, _presets);
|
||||
}
|
||||
|
||||
public void GlobalSave()
|
||||
{
|
||||
SavePresets();
|
||||
@@ -300,6 +295,8 @@ namespace ColorControl.Services.GameLauncher
|
||||
|
||||
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
|
||||
|
||||
SaveConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -317,6 +314,8 @@ namespace ColorControl.Services.GameLauncher
|
||||
_presets.Add(preset);
|
||||
}
|
||||
|
||||
SavePresets();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1213
-1203
File diff suppressed because it is too large.
Load diff
@@ -797,7 +797,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
label += " (clear to set to unchanged)";
|
||||
}
|
||||
|
||||
var value = SelectValue(0, 100, preset.SDRBrightness, label);
|
||||
var value = MessageForms.SelectIntValue(0, 100, preset.SDRBrightness, label);
|
||||
|
||||
if (value == -1)
|
||||
{
|
||||
@@ -831,7 +831,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
var display = _nvService.ResolveDisplay(preset);
|
||||
var dvControl = display.DigitalVibranceControl;
|
||||
|
||||
var value = SelectValue(dvControl.MinimumLevel, dvControl.MaximumLevel, preset.ColorEnhancementSettings.DigitalVibranceLevel, label);
|
||||
var value = MessageForms.SelectIntValue(dvControl.MinimumLevel, dvControl.MaximumLevel, preset.ColorEnhancementSettings.DigitalVibranceLevel, label);
|
||||
|
||||
if (value == -1)
|
||||
{
|
||||
@@ -862,7 +862,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
var display = _nvService.ResolveDisplay(preset);
|
||||
var hueControl = display.HUEControl;
|
||||
|
||||
var value = SelectValue(hueControl.DefaultAngle, 359, preset.ColorEnhancementSettings.HueAngle, label);
|
||||
var value = MessageForms.SelectIntValue(hueControl.DefaultAngle, 359, preset.ColorEnhancementSettings.HueAngle, label);
|
||||
|
||||
if (value == -1)
|
||||
{
|
||||
@@ -1439,36 +1439,6 @@ namespace ColorControl.Services.NVIDIA
|
||||
return settingValue;
|
||||
}
|
||||
|
||||
private int? SelectValue(int min, int max, int? currentValue, string label)
|
||||
{
|
||||
var field = new FieldDefinition
|
||||
{
|
||||
MinValue = min,
|
||||
MaxValue = max,
|
||||
Label = label,
|
||||
FieldType = FieldType.Numeric,
|
||||
Value = currentValue
|
||||
};
|
||||
|
||||
var resultFields = MessageForms.ShowDialog(field.Label, new[] { field });
|
||||
|
||||
if (!resultFields.Any())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var strValue = (string)resultFields.First().Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var settingValue = Utils.ParseInt(strValue);
|
||||
|
||||
return settingValue;
|
||||
}
|
||||
|
||||
private string SelectStringValue(string currentValue, string label, IEnumerable<string> values)
|
||||
{
|
||||
var field = new FieldDefinition
|
||||
|
||||
+1744
-1659
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,6 @@
|
||||
d:DesignHeight="720" d:DesignWidth="1280" Loaded="Window_Loaded">
|
||||
<DockPanel>
|
||||
<wv2:WebView2 Name="webView" DefaultBackgroundColor="#212529"
|
||||
Source="{Binding Url}"/>
|
||||
Source="{Binding Url}" NavigationStarting="webView_NavigationStarting"/>
|
||||
</DockPanel>
|
||||
</src:BaseWindow>
|
||||
@@ -63,7 +63,14 @@ public partial class BrowserWindow : BaseWindow
|
||||
_globalContext.Config.XFormWidth = Width;
|
||||
_globalContext.Config.XFormHeight = Height;
|
||||
|
||||
Hide();
|
||||
if (_globalContext.Config.MinimizeOnClose)
|
||||
{
|
||||
Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Program.Exit();
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(EventArgs e)
|
||||
@@ -84,4 +91,15 @@ public partial class BrowserWindow : BaseWindow
|
||||
private async void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
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)";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Services.Common;
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Forms;
|
||||
using ColorControl.Shared.Native;
|
||||
using ColorControl.Shared.XForms;
|
||||
using EDIDParser;
|
||||
using MHC2Gen;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
@@ -17,294 +15,263 @@ using WindowsDisplayAPI;
|
||||
|
||||
namespace ColorControl.XForms;
|
||||
|
||||
public enum SaveOption
|
||||
{
|
||||
Install,
|
||||
[Description("Install and set as default")]
|
||||
InstallAndSetAsDefault,
|
||||
[Description("Save to file")]
|
||||
SaveToFile
|
||||
}
|
||||
|
||||
public enum DisplayPrimariesSource
|
||||
{
|
||||
Custom,
|
||||
EDID,
|
||||
Windows
|
||||
}
|
||||
|
||||
public class CustomDisplay
|
||||
{
|
||||
public Display Display { get; set; }
|
||||
public Display Display { get; set; }
|
||||
|
||||
public string DisplayName => Display.DisplayName;
|
||||
public string DevicePath => Display.DevicePath;
|
||||
public bool IsHDRSupportedAndEnabled()
|
||||
{
|
||||
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(DisplayName);
|
||||
public string DisplayName => Display.DisplayName;
|
||||
public string DevicePath => Display.DevicePath;
|
||||
public bool IsHDRSupportedAndEnabled()
|
||||
{
|
||||
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(DisplayName);
|
||||
|
||||
return advancedColor.isSupported && advancedColor.isEnabled;
|
||||
}
|
||||
return advancedColor.isSupported && advancedColor.isEnabled;
|
||||
}
|
||||
|
||||
public CustomDisplay(Display display)
|
||||
{
|
||||
Display = display;
|
||||
}
|
||||
public CustomDisplay(Display display)
|
||||
{
|
||||
Display = display;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{CCD.GetDisplayInfo(Display.DisplayName)} on {Display.Adapter.DeviceName}";
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{CCD.GetDisplayInfo(Display.DisplayName)} on {Display.Adapter.DeviceName}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorPointModel : BaseViewModel
|
||||
{
|
||||
private double _x;
|
||||
public double X { get => _x; set { _x = value; OnPropertyChanged(); } }
|
||||
private double _x;
|
||||
public double X { get => _x; set { _x = value; OnPropertyChanged(); } }
|
||||
|
||||
private double _y;
|
||||
public double Y { get => _y; set { _y = value; OnPropertyChanged(); } }
|
||||
private double _y;
|
||||
public double Y { get => _y; set { _y = value; OnPropertyChanged(); } }
|
||||
|
||||
public ColorPointModel()
|
||||
{
|
||||
public ColorPointModel()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public ColorPointModel(CIExy coord)
|
||||
{
|
||||
X = coord.x;
|
||||
Y = coord.y;
|
||||
}
|
||||
public ColorPointModel(CIExy coord)
|
||||
{
|
||||
X = coord.x;
|
||||
Y = coord.y;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ColorProfileViewModel : BaseViewModel
|
||||
{
|
||||
[StringLength(30)]
|
||||
public string Description { get; set; }
|
||||
[StringLength(30)]
|
||||
public string Description { get; set; }
|
||||
|
||||
public ColorPointModel RedPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Red);
|
||||
public ColorPointModel GreenPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Green);
|
||||
public ColorPointModel BluePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Blue);
|
||||
public ColorPointModel WhitePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.White);
|
||||
public Dictionary<DisplayPrimariesSource, string> PrimariesSources { get; } = Utils.EnumToDictionary<DisplayPrimariesSource>();
|
||||
public DisplayPrimariesSource PrimariesSource { get; set; } = DisplayPrimariesSource.EDID;
|
||||
public Dictionary<ColorGamut, string> ColorGamuts { get; } = Utils.EnumToDictionary<ColorGamut>();
|
||||
public ColorGamut ColorGamut { get; set; } = ColorGamut.Native;
|
||||
public Dictionary<SDRTransferFunction, string> SDRTransferFunctions { get; } = Utils.EnumToDictionary<SDRTransferFunction>();
|
||||
public SDRTransferFunction SDRTransferFunction { get; set; } = SDRTransferFunction.PurePower;
|
||||
[Range(0.1, 10)]
|
||||
public double CustomGamma { get; set; } = 2.20;
|
||||
[Range(0, 10)]
|
||||
public double MinCLL { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double MaxCLL { get; set; } = 1000;
|
||||
[Range(0, 10)]
|
||||
public double BlackLuminance { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double WhiteLuminance { get; set; } = 1000;
|
||||
[Range(0, 10)]
|
||||
public double SDRMinBrightness { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double SDRMaxBrightness { get; set; } = 100;
|
||||
[Range(-50, 50)]
|
||||
public double SDRBrightnessBoost { get; set; }
|
||||
public ColorPointModel RedPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Red);
|
||||
public ColorPointModel GreenPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Green);
|
||||
public ColorPointModel BluePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Blue);
|
||||
public ColorPointModel WhitePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.White);
|
||||
public Dictionary<DisplayPrimariesSource, string> PrimariesSources { get; } = Utils.EnumToDictionary<DisplayPrimariesSource>();
|
||||
public DisplayPrimariesSource PrimariesSource { get; set; } = DisplayPrimariesSource.EDID;
|
||||
public Dictionary<ColorGamut, string> ColorGamuts { get; } = Utils.EnumToDictionary<ColorGamut>();
|
||||
public ColorGamut ColorGamut { get; set; } = ColorGamut.Native;
|
||||
public Dictionary<SDRTransferFunction, string> SDRTransferFunctions { get; } = Utils.EnumToDictionary<SDRTransferFunction>();
|
||||
public SDRTransferFunction SDRTransferFunction { get; set; } = SDRTransferFunction.PurePower;
|
||||
[Range(0.1, 10)]
|
||||
public double CustomGamma { get; set; } = 2.20;
|
||||
[Range(0, 10)]
|
||||
public double MinCLL { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double MaxCLL { get; set; } = 1000;
|
||||
[Range(0, 10)]
|
||||
public double BlackLuminance { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double WhiteLuminance { get; set; } = 1000;
|
||||
[Range(0, 10)]
|
||||
public double SDRMinBrightness { get; set; } = 0;
|
||||
[Range(11, 10000)]
|
||||
public double SDRMaxBrightness { get; set; } = 100;
|
||||
[Range(-50, 50)]
|
||||
public double SDRBrightnessBoost { get; set; }
|
||||
|
||||
public CustomDisplay SelectedDisplay { get; set; }
|
||||
public IEnumerable<CustomDisplay> Displays { get; set; }
|
||||
public CustomDisplay SelectedDisplay { get; set; }
|
||||
public IEnumerable<CustomDisplay> Displays { get; set; }
|
||||
|
||||
public string SelectedDisplayName => SelectedDisplay?.DisplayName;
|
||||
public string SelectedDisplayName => SelectedDisplay?.DisplayName;
|
||||
|
||||
public ObservableCollection<string> ExistingProfiles { get; set; }
|
||||
public string SelectedExistingProfile { get; set; } = CreateANewProfile;
|
||||
public string NewProfileName { get; set; }
|
||||
public string ProfileName => SelectedExistingProfile == CreateANewProfile ? NewProfileName : SelectedExistingProfile;
|
||||
public bool IsHDR { get; set; } = true;
|
||||
public Dictionary<SaveOption, string> SaveOptions { get; } = Utils.EnumToDictionary<SaveOption>();
|
||||
public SaveOption SaveOption { get; set; } = SaveOption.InstallAndSetAsDefault;
|
||||
public ObservableCollection<string> ExistingProfiles { get; set; }
|
||||
public string SelectedExistingProfile { get; set; } = CreateANewProfile;
|
||||
public string NewProfileName { get; set; }
|
||||
public string ProfileName => SelectedExistingProfile == CreateANewProfile ? NewProfileName : SelectedExistingProfile;
|
||||
public bool IsHDR { get; set; } = true;
|
||||
public Dictionary<SaveOption, string> SaveOptions { get; } = Utils.EnumToDictionary<SaveOption>();
|
||||
public SaveOption SaveOption { get; set; } = SaveOption.InstallAndSetAsDefault;
|
||||
|
||||
public bool SDRSettingsEnabled { get; set; }
|
||||
public Visibility VisibilityHDRSettings => IsHDR ? Visibility.Visible : Visibility.Collapsed;
|
||||
public bool IsLoadEnabled { get; set; }
|
||||
public bool SetMinMaxTml { get; set; } = true;
|
||||
public bool PrimariesEnabled { get; set; } = true;
|
||||
public bool SDRSettingsEnabled { get; set; }
|
||||
public Visibility VisibilityHDRSettings => IsHDR ? Visibility.Visible : Visibility.Collapsed;
|
||||
public bool IsLoadEnabled { get; set; }
|
||||
public bool SetMinMaxTml { get; set; } = true;
|
||||
public bool PrimariesEnabled { get; set; } = true;
|
||||
|
||||
public override string this[string columnName]
|
||||
{
|
||||
get
|
||||
{
|
||||
var baseResult = base[columnName];
|
||||
public override string this[string columnName]
|
||||
{
|
||||
get
|
||||
{
|
||||
var baseResult = base[columnName];
|
||||
|
||||
if (columnName == nameof(SelectedDisplay))
|
||||
{
|
||||
UpdateDisplay();
|
||||
}
|
||||
else if (columnName == nameof(SDRTransferFunction))
|
||||
{
|
||||
SDRSettingsEnabled = SDRTransferFunction == SDRTransferFunction.PurePower;
|
||||
OnPropertyChanged(nameof(SDRSettingsEnabled));
|
||||
}
|
||||
else if (columnName == nameof(SelectedExistingProfile))
|
||||
{
|
||||
IsLoadEnabled = SelectedExistingProfile != CreateANewProfile;
|
||||
OnPropertyChanged(nameof(IsLoadEnabled));
|
||||
}
|
||||
else if (columnName == nameof(PrimariesSource))
|
||||
{
|
||||
PrimariesEnabled = PrimariesSource == DisplayPrimariesSource.Custom;
|
||||
OnPropertyChanged(nameof(PrimariesEnabled));
|
||||
UpdateDisplay();
|
||||
}
|
||||
if (columnName == nameof(SelectedDisplay))
|
||||
{
|
||||
UpdateDisplay();
|
||||
}
|
||||
else if (columnName == nameof(SDRTransferFunction))
|
||||
{
|
||||
SDRSettingsEnabled = SDRTransferFunction == SDRTransferFunction.PurePower;
|
||||
OnPropertyChanged(nameof(SDRSettingsEnabled));
|
||||
}
|
||||
else if (columnName == nameof(SelectedExistingProfile))
|
||||
{
|
||||
IsLoadEnabled = SelectedExistingProfile != CreateANewProfile;
|
||||
OnPropertyChanged(nameof(IsLoadEnabled));
|
||||
}
|
||||
else if (columnName == nameof(PrimariesSource))
|
||||
{
|
||||
PrimariesEnabled = PrimariesSource == DisplayPrimariesSource.Custom;
|
||||
OnPropertyChanged(nameof(PrimariesEnabled));
|
||||
UpdateDisplay();
|
||||
}
|
||||
|
||||
return baseResult;
|
||||
}
|
||||
}
|
||||
return baseResult;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool PreValidate()
|
||||
{
|
||||
return base.PreValidate() && Displays.Any();
|
||||
}
|
||||
public override bool PreValidate()
|
||||
{
|
||||
return base.PreValidate() && Displays.Any();
|
||||
}
|
||||
|
||||
public const string CreateANewProfile = "<create a new profile>";
|
||||
public const string CreateANewProfile = "<create a new profile>";
|
||||
|
||||
public ColorProfileViewModel(bool isHDR = true)
|
||||
{
|
||||
IsHDR = isHDR;
|
||||
public ColorProfileViewModel(bool isHDR = true)
|
||||
{
|
||||
IsHDR = isHDR;
|
||||
|
||||
RefreshDisplays();
|
||||
RefreshDisplays();
|
||||
|
||||
ExistingProfiles = new ObservableCollection<string> { CreateANewProfile };
|
||||
NewProfileName = $"CC_{(isHDR ? "HDR" : "SDR")}_profile_{DateTime.Now:yyyyMMddHHmmss}.icm";
|
||||
Description = $"CC_{(isHDR ? "HDR" : "SDR")}";
|
||||
ExistingProfiles = new ObservableCollection<string> { CreateANewProfile };
|
||||
NewProfileName = $"CC_{(isHDR ? "HDR" : "SDR")}_profile_{DateTime.Now:yyyyMMddHHmmss}.icm";
|
||||
Description = $"CC_{(isHDR ? "HDR" : "SDR")}";
|
||||
|
||||
if (Displays.Any())
|
||||
{
|
||||
SelectedDisplay = Displays.First();
|
||||
if (Displays.Any())
|
||||
{
|
||||
SelectedDisplay = Displays.First();
|
||||
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshDisplays()
|
||||
{
|
||||
Displays = Display.GetDisplays().Select(d => new CustomDisplay(d))
|
||||
.Where(d => IsHDR == d.IsHDRSupportedAndEnabled())
|
||||
.ToList();
|
||||
}
|
||||
public void RefreshDisplays()
|
||||
{
|
||||
Displays = Display.GetDisplays().Select(d => new CustomDisplay(d))
|
||||
.Where(d => IsHDR == d.IsHDRSupportedAndEnabled())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var property in GetType().GetProperties().Where(p => !new[] { "Display", "PrimariesSource" }.Any(s => p.Name.Contains(s))))
|
||||
{
|
||||
OnPropertyChanged(property.Name);
|
||||
}
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
foreach (var property in GetType().GetProperties().Where(p => !new[] { "Display", "PrimariesSource" }.Any(s => p.Name.Contains(s))))
|
||||
{
|
||||
OnPropertyChanged(property.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
if (SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
if (SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var colorParams = CCD.GetColorParams(SelectedDisplay.DisplayName);
|
||||
var colorParams = CCD.GetColorParams(SelectedDisplay.DisplayName);
|
||||
|
||||
if (colorParams.MaxLuminance > 0)
|
||||
{
|
||||
if (PrimariesSource == DisplayPrimariesSource.Windows)
|
||||
{
|
||||
var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
|
||||
if (colorParams.MaxLuminance > 0)
|
||||
{
|
||||
if (PrimariesSource == DisplayPrimariesSource.Windows)
|
||||
{
|
||||
var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
|
||||
|
||||
RedPoint.X = (double)colorParams.RedPointX / divider;
|
||||
RedPoint.Y = (double)colorParams.RedPointY / divider;
|
||||
GreenPoint.X = (double)colorParams.GreenPointX / divider;
|
||||
GreenPoint.Y = (double)colorParams.GreenPointY / divider;
|
||||
BluePoint.X = (double)colorParams.BluePointX / divider;
|
||||
BluePoint.Y = (double)colorParams.BluePointY / divider;
|
||||
WhitePoint.X = (double)colorParams.WhitePointX / divider;
|
||||
WhitePoint.Y = (double)colorParams.WhitePointY / divider;
|
||||
}
|
||||
RedPoint.X = (double)colorParams.RedPointX / divider;
|
||||
RedPoint.Y = (double)colorParams.RedPointY / divider;
|
||||
GreenPoint.X = (double)colorParams.GreenPointX / divider;
|
||||
GreenPoint.Y = (double)colorParams.GreenPointY / divider;
|
||||
BluePoint.X = (double)colorParams.BluePointX / divider;
|
||||
BluePoint.Y = (double)colorParams.BluePointY / divider;
|
||||
WhitePoint.X = (double)colorParams.WhitePointX / divider;
|
||||
WhitePoint.Y = (double)colorParams.WhitePointY / divider;
|
||||
}
|
||||
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000;
|
||||
OnPropertyChanged(nameof(WhiteLuminance));
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000;
|
||||
OnPropertyChanged(nameof(BlackLuminance));
|
||||
MinCLL = BlackLuminance;
|
||||
OnPropertyChanged(nameof(MinCLL));
|
||||
MaxCLL = WhiteLuminance;
|
||||
OnPropertyChanged(nameof(MaxCLL));
|
||||
}
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000;
|
||||
OnPropertyChanged(nameof(WhiteLuminance));
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000;
|
||||
OnPropertyChanged(nameof(BlackLuminance));
|
||||
MinCLL = BlackLuminance;
|
||||
OnPropertyChanged(nameof(MinCLL));
|
||||
MaxCLL = WhiteLuminance;
|
||||
OnPropertyChanged(nameof(MaxCLL));
|
||||
}
|
||||
|
||||
if (PrimariesSource == DisplayPrimariesSource.EDID)
|
||||
{
|
||||
GetEDID();
|
||||
}
|
||||
if (PrimariesSource == DisplayPrimariesSource.EDID)
|
||||
{
|
||||
GetEDID();
|
||||
}
|
||||
|
||||
if (!CCD.GetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName))
|
||||
{
|
||||
if (MessageForms.QuestionYesNo("To be able to select/activate a color profile, user specific settings have to be enabled. Do you want to enable this?") == DialogResult.Yes)
|
||||
{
|
||||
CCD.SetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName, true);
|
||||
}
|
||||
}
|
||||
if (!CCD.GetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName))
|
||||
{
|
||||
if (MessageForms.QuestionYesNo("To be able to select/activate a color profile, user specific settings have to be enabled. Do you want to enable this?") == DialogResult.Yes)
|
||||
{
|
||||
CCD.SetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName, true);
|
||||
}
|
||||
}
|
||||
|
||||
ExistingProfiles = new ObservableCollection<string> { CreateANewProfile };
|
||||
ExistingProfiles = new ObservableCollection<string> { CreateANewProfile };
|
||||
|
||||
var profiles = CCD.GetDisplayColorProfiles(SelectedDisplay.DisplayName);
|
||||
var profiles = CCD.GetDisplayColorProfiles(SelectedDisplay.DisplayName);
|
||||
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
ExistingProfiles.Add(profile);
|
||||
}
|
||||
}
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
ExistingProfiles.Add(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetEDID()
|
||||
{
|
||||
if (SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
private void GetEDID()
|
||||
{
|
||||
if (SelectedDisplay == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var path = SelectedDisplay.DevicePath;
|
||||
var path = SelectedDisplay.DevicePath;
|
||||
|
||||
var edid = GetEDIDInternal(path);
|
||||
var primaries = edid?.DisplayParameters?.ChromaticityCoordinates;
|
||||
var edid = ColorProfileService.GetEDIDInternal(path);
|
||||
var primaries = edid?.DisplayParameters?.ChromaticityCoordinates;
|
||||
|
||||
if (primaries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (primaries == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RedPoint.X = primaries.RedX;
|
||||
RedPoint.Y = primaries.RedY;
|
||||
GreenPoint.X = primaries.GreenX;
|
||||
GreenPoint.Y = primaries.GreenY;
|
||||
BluePoint.X = primaries.BlueX;
|
||||
BluePoint.Y = primaries.BlueY;
|
||||
WhitePoint.X = primaries.WhiteX;
|
||||
WhitePoint.Y = primaries.WhiteY;
|
||||
}
|
||||
|
||||
internal RgbPrimaries GetDevicePrimaries()
|
||||
{
|
||||
return new RgbPrimaries(
|
||||
new CIExy { x = RedPoint.X, y = RedPoint.Y },
|
||||
new CIExy { x = GreenPoint.X, y = GreenPoint.Y },
|
||||
new CIExy { x = BluePoint.X, y = BluePoint.Y },
|
||||
new CIExy { x = WhitePoint.X, y = WhitePoint.Y });
|
||||
}
|
||||
|
||||
private static EDID GetEDIDInternal(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryPath = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\";
|
||||
registryPath += string.Join("\\", path.Split('#').Skip(1).Take(2));
|
||||
return new EDID((byte[])Registry.GetValue(registryPath + "\\Device Parameters", "EDID", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
RedPoint.X = primaries.RedX;
|
||||
RedPoint.Y = primaries.RedY;
|
||||
GreenPoint.X = primaries.GreenX;
|
||||
GreenPoint.Y = primaries.GreenY;
|
||||
BluePoint.X = primaries.BlueX;
|
||||
BluePoint.Y = primaries.BlueY;
|
||||
WhitePoint.X = primaries.WhiteX;
|
||||
WhitePoint.Y = primaries.WhiteY;
|
||||
}
|
||||
|
||||
internal RgbPrimaries GetDevicePrimaries()
|
||||
{
|
||||
return new RgbPrimaries(
|
||||
new CIExy { x = RedPoint.X, y = RedPoint.Y },
|
||||
new CIExy { x = GreenPoint.X, y = GreenPoint.Y },
|
||||
new CIExy { x = BluePoint.X, y = BluePoint.Y },
|
||||
new CIExy { x = WhitePoint.X, y = WhitePoint.Y });
|
||||
}
|
||||
}
|
||||
@@ -15,224 +15,224 @@ 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)
|
||||
{
|
||||
if (Application.Current == null)
|
||||
{
|
||||
new Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
|
||||
}
|
||||
|
||||
var winApiAdminService = Program.ServiceProvider.GetRequiredService<WinApiAdminService>();
|
||||
var globalContext = Program.ServiceProvider.GetRequiredService<GlobalContext>();
|
||||
var window = new ColorProfileWindow(winApiAdminService, globalContext, isHDR);
|
||||
var winApiAdminService = Program.ServiceProvider.GetRequiredService<WinApiAdminService>();
|
||||
var globalContext = Program.ServiceProvider.GetRequiredService<GlobalContext>();
|
||||
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))
|
||||
{
|
||||
if (!_viewModel.ExistingProfiles.Contains(profileName))
|
||||
{
|
||||
_viewModel.ExistingProfiles.Add(profileName);
|
||||
}
|
||||
_viewModel.SelectedExistingProfile = profileName;
|
||||
_viewModel.Update();
|
||||
}
|
||||
}
|
||||
if (CCD.SetDisplayDefaultColorProfile(displayName, profileName, _viewModel.SetMinMaxTml))
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MHC2Gen;
|
||||
|
||||
public enum SaveOption
|
||||
{
|
||||
Install,
|
||||
[Description("Install and associate to display")]
|
||||
InstallAndAssociate,
|
||||
[Description("Install and set as display default")]
|
||||
InstallAndSetAsDefault,
|
||||
[Description("Save to file")]
|
||||
SaveToFile
|
||||
}
|
||||
|
||||
public enum DisplayPrimariesSource
|
||||
{
|
||||
Custom,
|
||||
EDID,
|
||||
Windows,
|
||||
ColorProfile,
|
||||
}
|
||||
|
||||
public enum SDRTransferFunction
|
||||
{
|
||||
[Description("BT.1886")]
|
||||
BT_1886 = 0,
|
||||
[Description("Pure Power")]
|
||||
PurePower = 1,
|
||||
[Description("Piecewise")]
|
||||
Piecewise = 2
|
||||
}
|
||||
|
||||
public enum ColorGamut
|
||||
{
|
||||
Native = 0,
|
||||
sRGB = 1,
|
||||
P3 = 2,
|
||||
Rec2020 = 3,
|
||||
AdobeRGB = 4
|
||||
}
|
||||
@@ -1,29 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
namespace MHC2Gen;
|
||||
|
||||
namespace MHC2Gen
|
||||
public class GenerateProfileCommand
|
||||
{
|
||||
internal class ST2084
|
||||
{
|
||||
private const double m1 = 2610.0 / 16384.0;
|
||||
private const double m2 = 128.0 * 2523.0 / 4096.0;
|
||||
private const double c1 = 3424.0 / 4096.0;
|
||||
private const double c2 = 32.0 * 2413.0 / 4096.0;
|
||||
private const double c3 = 32.0 * 2392.0 / 4096.0;
|
||||
|
||||
public static double NitsToSignal(double nits)
|
||||
{
|
||||
var Ypowm1 = Math.Pow(nits / 10000.0, m1);
|
||||
return Math.Pow((c1 + c2 * Ypowm1) / (1 + c3 * Ypowm1), m2);
|
||||
}
|
||||
|
||||
public static double SignalToNits(double signal)
|
||||
{
|
||||
var Epow1divm2 = Math.Pow(signal, 1.0 / m2);
|
||||
return 10000 * Math.Pow(Math.Max(Epow1divm2 - c1, 0) / (c2 - c3 * Epow1divm2), 1 / m1);
|
||||
}
|
||||
}
|
||||
public string Description { get; set; } = "";
|
||||
public bool HasExtraInfo { get; set; }
|
||||
public bool IsHDRProfile { get; set; }
|
||||
public RgbPrimaries DevicePrimaries { get; set; } = new RgbPrimaries(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; } = 2000;
|
||||
public double BlackLuminance { get; set; }
|
||||
public double SDRMinBrightness { get; set; }
|
||||
public double SDRMaxBrightness { get; set; } = 100;
|
||||
public double SDRBrightnessBoost { get; set; }
|
||||
public double ShadowDetailBoost { get; set; }
|
||||
public double Gamma { get; set; } = 2.2;
|
||||
}
|
||||
+1178
-1164
File diff suppressed because it is too large.
Load diff
@@ -54,6 +54,8 @@ public class MHC2Wrapper
|
||||
|
||||
return new GenerateProfileCommand
|
||||
{
|
||||
Description = deviceContext.GetDescription(),
|
||||
HasExtraInfo = deviceContext.ExtraInfoTag != null,
|
||||
IsHDRProfile = isHDRProfile,
|
||||
BlackLuminance = deviceContext.min_nits,
|
||||
WhiteLuminance = deviceContext.max_nits,
|
||||
@@ -65,6 +67,7 @@ public class MHC2Wrapper
|
||||
SDRMaxBrightness = deviceContext.ExtraInfoTag?.SDRMaxBrightness ?? 100,
|
||||
SDRTransferFunction = deviceContext.ExtraInfoTag?.SDRTransferFunction ?? SDRTransferFunction.PurePower,
|
||||
SDRBrightnessBoost = deviceContext.ExtraInfoTag?.SDRBrightnessBoost ?? 0,
|
||||
ShadowDetailBoost = deviceContext.ExtraInfoTag?.ShadowDetailBoost ?? 0,
|
||||
Gamma = deviceContext.ExtraInfoTag?.Gamma ?? 2.2
|
||||
};
|
||||
}
|
||||
|
||||
+46
-40
@@ -1,52 +1,58 @@
|
||||
using LittleCms;
|
||||
using MathNet.Numerics;
|
||||
|
||||
namespace MHC2Gen
|
||||
{
|
||||
public class CIExy
|
||||
{
|
||||
public double x { get; set; }
|
||||
public double y { get; set; }
|
||||
public class CIExy
|
||||
{
|
||||
public double x { get; set; }
|
||||
public double y { get; set; }
|
||||
|
||||
public CIExy() { }
|
||||
public CIExy() { }
|
||||
|
||||
public CIExy(CIExy source)
|
||||
{
|
||||
x = source.x;
|
||||
y = source.y;
|
||||
}
|
||||
public CIExy(CIExy source)
|
||||
{
|
||||
x = source.x;
|
||||
y = source.y;
|
||||
}
|
||||
|
||||
public CIEXYZ ToXYZ(double Y = 1.0)
|
||||
{
|
||||
return new() { X = x * Y / y, Y = Y, Z = (1 - x - y) * Y / y };
|
||||
}
|
||||
}
|
||||
public class RgbPrimaries
|
||||
{
|
||||
public CIExy Red { get; }
|
||||
public CIExy Green { get; }
|
||||
public CIExy Blue { get; }
|
||||
public CIExy White { get; }
|
||||
public CIEXYZ ToXYZ(double Y = 1.0)
|
||||
{
|
||||
return new() { X = x * Y / y, Y = Y, Z = (1 - x - y) * Y / y };
|
||||
}
|
||||
|
||||
public RgbPrimaries(RgbPrimaries primaries)
|
||||
{
|
||||
Red = new CIExy(primaries.Red);
|
||||
Green = new CIExy(primaries.Green);
|
||||
Blue = new CIExy(primaries.Blue);
|
||||
White = new CIExy(primaries.White);
|
||||
public CIExy ToRounded()
|
||||
{
|
||||
return new CIExy { x = x.Round(15), y = y.Round(15) };
|
||||
}
|
||||
}
|
||||
public class RgbPrimaries
|
||||
{
|
||||
public CIExy Red { get; }
|
||||
public CIExy Green { get; }
|
||||
public CIExy Blue { get; }
|
||||
public CIExy White { get; }
|
||||
|
||||
}
|
||||
public RgbPrimaries(CIExy red, CIExy green, CIExy blue, CIExy white)
|
||||
{
|
||||
Red = red;
|
||||
Green = green;
|
||||
Blue = blue;
|
||||
White = white;
|
||||
}
|
||||
public RgbPrimaries(RgbPrimaries primaries)
|
||||
{
|
||||
Red = new CIExy(primaries.Red);
|
||||
Green = new CIExy(primaries.Green);
|
||||
Blue = new CIExy(primaries.Blue);
|
||||
White = new CIExy(primaries.White);
|
||||
|
||||
public static RgbPrimaries sRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.30, y = 0.60 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries AdobeRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.21, y = 0.71 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries P3D65 { get; } = new RgbPrimaries(new() { x = 0.68, y = 0.32 }, new() { x = 0.265, y = 0.690 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries Rec2020 { get; } = new RgbPrimaries(new() { x = 0.708, y = 0.292 }, new() { x = 0.170, y = 0.797 }, new() { x = 0.131, y = 0.046 }, new() { x = 0.3127, y = 0.3290 });
|
||||
}
|
||||
public RgbPrimaries(CIExy red, CIExy green, CIExy blue, CIExy white)
|
||||
{
|
||||
Red = red.ToRounded();
|
||||
Green = green.ToRounded();
|
||||
Blue = blue.ToRounded();
|
||||
White = white.ToRounded();
|
||||
}
|
||||
|
||||
}
|
||||
public static RgbPrimaries sRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.30, y = 0.60 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries AdobeRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.21, y = 0.71 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries P3D65 { get; } = new RgbPrimaries(new() { x = 0.68, y = 0.32 }, new() { x = 0.265, y = 0.690 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
|
||||
public static RgbPrimaries Rec2020 { get; } = new RgbPrimaries(new() { x = 0.708, y = 0.292 }, new() { x = 0.170, y = 0.797 }, new() { x = 0.131, y = 0.046 }, new() { x = 0.3127, y = 0.3290 });
|
||||
|
||||
}
|
||||
}
|
||||
+24
-34
@@ -1,39 +1,29 @@
|
||||
using System.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MHC2Gen;
|
||||
|
||||
public enum SDRTransferFunction
|
||||
namespace MHC2Gen
|
||||
{
|
||||
[Description("BT.1886")]
|
||||
BT_1886 = 0,
|
||||
[Description("Pure Power")]
|
||||
PurePower = 1,
|
||||
[Description("Piecewise")]
|
||||
Piecewise = 2
|
||||
}
|
||||
internal class ST2084
|
||||
{
|
||||
private const double m1 = 2610.0 / 16384.0;
|
||||
private const double m2 = 128.0 * 2523.0 / 4096.0;
|
||||
private const double c1 = 3424.0 / 4096.0;
|
||||
private const double c2 = 32.0 * 2413.0 / 4096.0;
|
||||
private const double c3 = 32.0 * 2392.0 / 4096.0;
|
||||
|
||||
public enum ColorGamut
|
||||
{
|
||||
Native = 0,
|
||||
sRGB = 1,
|
||||
P3 = 2,
|
||||
Rec2020 = 3,
|
||||
AdobeRGB = 4
|
||||
}
|
||||
public static double NitsToSignal(double nits)
|
||||
{
|
||||
var Ypowm1 = Math.Pow(nits / 10000.0, m1);
|
||||
return Math.Pow((c1 + c2 * Ypowm1) / (1 + c3 * Ypowm1), m2);
|
||||
}
|
||||
|
||||
public class GenerateProfileCommand
|
||||
{
|
||||
public string Description { get; set; }
|
||||
public bool IsHDRProfile { get; set; }
|
||||
public RgbPrimaries DevicePrimaries { get; set; } = new RgbPrimaries(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; } = 2000;
|
||||
public double BlackLuminance { get; set; } = 0;
|
||||
public double SDRMinBrightness { get; set; } = 0;
|
||||
public double SDRMaxBrightness { get; set; } = 100;
|
||||
public double SDRBrightnessBoost { get; set; } = 0;
|
||||
public double Gamma { get; set; } = 2.2;
|
||||
public static double SignalToNits(double signal)
|
||||
{
|
||||
var Epow1divm2 = Math.Pow(signal, 1.0 / m2);
|
||||
return 10000 * Math.Pow(Math.Max(Epow1divm2 - c1, 0) / (c2 - c3 * Epow1divm2), 1 / m1);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using NvAPIWrapper.Native.Attributes;
|
||||
using NvAPIWrapper.Native.Attributes;
|
||||
using NvAPIWrapper.Native.Exceptions;
|
||||
using NvAPIWrapper.Native.General.Structures;
|
||||
using NvAPIWrapper.Native.GPU;
|
||||
using NvAPIWrapper.Native.Helpers;
|
||||
using NvAPIWrapper.Native.Interfaces;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace NvAPIWrapper.Native.Display.Structures
|
||||
{
|
||||
@@ -18,7 +18,7 @@ namespace NvAPIWrapper.Native.Display.Structures
|
||||
{
|
||||
internal StructureVersion _Version;
|
||||
internal readonly Rotate _Rotation;
|
||||
internal readonly Scaling _Scaling;
|
||||
internal Scaling _Scaling;
|
||||
internal readonly uint _RefreshRateInMillihertz;
|
||||
internal uint _RawReserved;
|
||||
internal readonly ConnectorType _ConnectorType;
|
||||
@@ -139,14 +139,14 @@ namespace NvAPIWrapper.Native.Display.Structures
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int) _Rotation;
|
||||
hashCode = (hashCode * 397) ^ (int) _Scaling;
|
||||
hashCode = (hashCode * 397) ^ (int) _RefreshRateInMillihertz;
|
||||
var hashCode = (int)_Rotation;
|
||||
hashCode = (hashCode * 397) ^ (int)_Scaling;
|
||||
hashCode = (hashCode * 397) ^ (int)_RefreshRateInMillihertz;
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
hashCode = (hashCode * 397) ^ (int) _RawReserved;
|
||||
hashCode = (hashCode * 397) ^ (int) _ConnectorType;
|
||||
hashCode = (hashCode * 397) ^ (int) _TVFormat;
|
||||
hashCode = (hashCode * 397) ^ (int) _TimingOverride;
|
||||
hashCode = (hashCode * 397) ^ (int)_RawReserved;
|
||||
hashCode = (hashCode * 397) ^ (int)_ConnectorType;
|
||||
hashCode = (hashCode * 397) ^ (int)_TVFormat;
|
||||
hashCode = (hashCode * 397) ^ (int)_TimingOverride;
|
||||
hashCode = (hashCode * 397) ^ _Timing.GetHashCode();
|
||||
|
||||
return hashCode;
|
||||
@@ -167,6 +167,7 @@ namespace NvAPIWrapper.Native.Display.Structures
|
||||
public Scaling Scaling
|
||||
{
|
||||
get => _Scaling;
|
||||
set => _Scaling = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# ColorControl
|
||||
Easily change NVIDIA and AMD display settings and/or control LG TV's
|
||||
Easily change NVIDIA and AMD display settings, control LG and Samsung tv's, Game launcher and color profile management
|
||||
|
||||
## Installation
|
||||
|
||||
Just extract the .zip file in a folder of your preference. Run ColorControl.exe to start the application.
|
||||
Requires .NET 8 with the following runtimes:
|
||||
* .NET Desktop Runtime 8
|
||||
* .NET ASP.NET Core Runtime 8
|
||||
|
||||
ColorControl stores its settings in the following folder:
|
||||
* C:\Users\username\AppData\Roaming\Maassoft\ColorControl
|
||||
|
||||
If you choose to install the Windows Service then some settings will also be placed here:
|
||||
* C:\Windows\System32\config\systemprofile\AppData\Roaming\Maassoft\ColorControl
|
||||
|
||||
## NVIDIA/AMD controller
|
||||
|
||||
@@ -9,6 +22,8 @@ The NVIDIA controller even has some more options:
|
||||
* Dynamic range: VESA (Full RGB) or CEA (Limited RGB/YUV)
|
||||
* Color space: to change the color space, but most may not be supported by your tv
|
||||
* Dithering: you can define the dithering mode (Temporal or Spatial) and the dithering bit depth
|
||||
* HDR: toggle HDR and control the SDR brightness setting
|
||||
* NVIDIA: change driver settings
|
||||
|
||||
Notes:
|
||||
* For a specific setting to be applied, you must include it within the preset. Just click the "Include" menu item and once it is checked, you're good to go.
|
||||
@@ -18,7 +33,7 @@ Screenshot:
|
||||
|
||||
## LG controller
|
||||
|
||||
If you own a recent LG TV (2018 or newer, older might work), you can control your TV through the app (no NVIDIA or AMD graphics card needed).
|
||||
If you own a recent LG tv or monitor that uses WebOS as its operating system (2018 or newer, older might work), you can control your tv through the app (no NVIDIA or AMD graphics card needed).
|
||||
At startup of the application it will automatically detect your tv's (see below) if they are on the same network as your pc. If a tv is powered on, it will show a popup by which you can allow ColorControl to send commands to you tv. This will only happen the first time or whenever there's a change in the required permissions. A new version of ColorControl might need this.
|
||||
It is also possible to add a tv manually by using the "Add" button. A name and ip address are required, the MAC address is only necessary for Wake-On-Lan.
|
||||
You can configure as well when to automatically power your tv on or off:
|
||||
@@ -28,16 +43,37 @@ You can configure as well when to automatically power your tv on or off:
|
||||
* Power off on standby
|
||||
* Power off on screensaver and on when screensaver deactivates
|
||||
|
||||
Besides powering on and off a lot of the settings can be directly changed via ColorControl.
|
||||
|
||||
For experienced users:
|
||||
* Open service menu (InStart/EzAdjust)
|
||||
* Change TPC/GSR setting directly
|
||||
|
||||
## Samsung controller
|
||||
|
||||
If you own a recent Samsung tv or monitor that uses Tizen as its operating system (2018 or newer, older might work), you can control your tv through the app (no NVIDIA or AMD graphics card needed).
|
||||
At startup of the application it will automatically detect your tv's (see below) if they are on the same network as your pc. If a tv is powered on, it will show a popup by which you can allow ColorControl to send commands to you tv. This will only happen the first time or whenever there's a change in the required permissions. A new version of ColorControl might need this.
|
||||
It is also possible to add a tv manually by using the "Add" button. A name and ip address are required, the MAC address is only necessary for Wake-On-Lan.
|
||||
You can configure as well when to automatically power your tv on or off:
|
||||
* Power on after startup of pc
|
||||
* Power on after resume
|
||||
* Power off on shutdown
|
||||
* Power off on standby
|
||||
* Power off on screensaver and on when screensaver deactivates
|
||||
|
||||
For experienced users:
|
||||
* Open service menu
|
||||
|
||||
### Presets
|
||||
|
||||
With the presets you can peform actions on your tv you would normally do via the remote control. Properties of a preset:
|
||||
* Name: fill in your own name/description
|
||||
* Device: select the tv to perform the action on. Defaults to "Globally selected device", which is the selected device in the top devices drop down.
|
||||
* App: select the app to launch that is installed on your tv (optional)
|
||||
* App (LG only): select the app to launch that is installed on your tv (optional)
|
||||
* Shortcut: enter the global shortcut to execute this preset
|
||||
* Steps: steps to execute sequentially. These steps can be:
|
||||
* Remote control buttons: like RIGHT, LEFT, ENTER, etc.
|
||||
* Actions: directly change picture settings like backlight, contrast, pictureMode, etc. In a dialog you have to specify the value.
|
||||
* Actions: LG only: directly change picture settings like backlight, contrast, pictureMode, etc. In a dialog you have to specify the value.
|
||||
* NVIDIA/AMD presets: add NVIDIA or AMD presets here that have to execute as well
|
||||
|
||||
Furthermore, you can add a trigger to a preset which means it will execute automatically when a process on your pc is running. See for more information: https://github.com/Maassoft/ColorControl/releases/tag/v4.0.0.0
|
||||
@@ -59,14 +95,16 @@ On the Options-tabpage you can finetune some parameters and/or enable some setti
|
||||
It is possible to execute presets from the command line. It doesn't matter whether the user interface of Color Control is already running or not.
|
||||
This is the syntax:
|
||||
```
|
||||
Syntax : ColorControl.exe command options
|
||||
Syntax : ColorControl command options
|
||||
Commands:
|
||||
--nvpreset <preset name or id>: execute NVIDIA-preset
|
||||
--amdpreset <preset name or id>: execute AMD-preset
|
||||
--lgpreset <preset name> : execute LG-preset
|
||||
--sampreset <preset name> : execute Samsung-preset
|
||||
--help : displays this help info
|
||||
Options:
|
||||
--nogui: starts command from the command line and will not open GUI (is forced when GUI is already running)
|
||||
--nogui : starts command from the command line and will not open GUI (is forced when GUI is already running)
|
||||
--no-refresh: when using LG or Samsung-preset: skip refreshing devices (speeds up executing preset)
|
||||
```
|
||||
Note: use double quotes if your preset has spaces in it, like this:
|
||||
`ColorControl.exe --nvpreset "HDR GSYNC"`
|
||||
|
||||
@@ -8,58 +8,58 @@ using System.Diagnostics;
|
||||
namespace ColorControl.Shared.Common
|
||||
|
||||
{
|
||||
public class GlobalContext
|
||||
{
|
||||
public static GlobalContext CurrentContext { get; private set; }
|
||||
public class GlobalContext
|
||||
{
|
||||
public static GlobalContext CurrentContext { get; private set; }
|
||||
|
||||
public Config Config { get; private set; }
|
||||
public Config Config { get; private set; }
|
||||
|
||||
public StartUpParams StartUpParams { get; private set; }
|
||||
public string DataPath { get; private set; }
|
||||
public SynchronizationContext SynchronizationContext { get; set; } = AsyncOperationManager.SynchronizationContext;
|
||||
public LoggingRule LoggingRule { get; private set; }
|
||||
public DateTime StartTime { get; private set; }
|
||||
public string MutexId { get; private set; }
|
||||
public nint MainHandle { get; set; }
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
public StartUpParams StartUpParams { get; private set; }
|
||||
public string DataPath { get; private set; }
|
||||
public SynchronizationContext SynchronizationContext { get; set; } = AsyncOperationManager.SynchronizationContext;
|
||||
public LoggingRule LoggingRule { get; private set; }
|
||||
public DateTime StartTime { get; private set; }
|
||||
public string MutexId { get; private set; }
|
||||
public nint MainHandle { get; set; }
|
||||
public IServiceProvider ServiceProvider { get; set; }
|
||||
|
||||
public string ApplicationTitle { get; private set; }
|
||||
public string ApplicationTitleAdmin { get; private set; }
|
||||
public bool UpdateAvailable { get; set; }
|
||||
public string LegalCopyright { get; private set; }
|
||||
public string ApplicationTitle { get; private set; }
|
||||
public string ApplicationTitleAdmin { get; private set; }
|
||||
public UpdateInfoDto UpdateInfo { get; set; }
|
||||
public string LegalCopyright { get; private set; }
|
||||
|
||||
public GlobalContext(Config config, StartUpParams startUpParams, string dataPath, LoggingRule loggingRule, IServiceProvider serviceProvider, DateTime startTime, string mutexId = null)
|
||||
{
|
||||
Config = config;
|
||||
StartUpParams = startUpParams;
|
||||
DataPath = dataPath;
|
||||
LoggingRule = loggingRule;
|
||||
ServiceProvider = serviceProvider;
|
||||
StartTime = startTime;
|
||||
MutexId = mutexId;
|
||||
public GlobalContext(Config config, StartUpParams startUpParams, string dataPath, LoggingRule loggingRule, IServiceProvider serviceProvider, DateTime startTime, string mutexId = null)
|
||||
{
|
||||
Config = config;
|
||||
StartUpParams = startUpParams;
|
||||
DataPath = dataPath;
|
||||
LoggingRule = loggingRule;
|
||||
ServiceProvider = serviceProvider;
|
||||
StartTime = startTime;
|
||||
MutexId = mutexId;
|
||||
|
||||
CurrentContext = this;
|
||||
CurrentContext = this;
|
||||
|
||||
ApplicationTitle = Application.ProductName + " " + Application.ProductVersion;
|
||||
ApplicationTitle = Application.ProductName + " " + Application.ProductVersion;
|
||||
|
||||
if (WinApiService.IsAdministratorStatic())
|
||||
{
|
||||
ApplicationTitleAdmin = ApplicationTitle + " (administrator)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationTitleAdmin = ApplicationTitle;
|
||||
}
|
||||
if (WinApiService.IsAdministratorStatic())
|
||||
{
|
||||
ApplicationTitleAdmin = ApplicationTitle + " (administrator)";
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplicationTitleAdmin = ApplicationTitle;
|
||||
}
|
||||
|
||||
var currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
|
||||
LegalCopyright = currentVersionInfo.LegalCopyright;
|
||||
}
|
||||
var currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
|
||||
LegalCopyright = currentVersionInfo.LegalCopyright;
|
||||
}
|
||||
|
||||
public void SetLogLevel(LogLevel logLevel)
|
||||
{
|
||||
Config.LogLevel = logLevel.ToString();
|
||||
LoggingRule.SetLoggingLevels(logLevel, LogLevel.Fatal);
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
}
|
||||
public void SetLogLevel(LogLevel logLevel)
|
||||
{
|
||||
Config.LogLevel = logLevel.ToString();
|
||||
LoggingRule.SetLoggingLevels(logLevel, LogLevel.Fatal);
|
||||
LogManager.ReconfigExistingLoggers();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,5 @@ public class InfoDto
|
||||
public string ApplicationTitle { get; set; }
|
||||
public string ApplicationTitleAdmin { get; set; }
|
||||
public string LegalCopyright { get; set; }
|
||||
public bool UpdateAvailable { get; set; }
|
||||
public UpdateInfoDto UpdateInfoDto { get; set; }
|
||||
}
|
||||
+96
-117
@@ -7,152 +7,131 @@ namespace ColorControl.Shared.Common;
|
||||
|
||||
public static class PipeUtils
|
||||
{
|
||||
public const string ServicePipe = "servicepipe";
|
||||
public const string ElevatedPipe = "elevatedpipe";
|
||||
public const string MainPipe = "mainpipe";
|
||||
public const string ServicePipe = "servicepipe";
|
||||
public const string ElevatedPipe = "elevatedpipe";
|
||||
public const string MainPipe = "mainpipe";
|
||||
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
public const int DefaultTimeout = 5000;
|
||||
|
||||
public static async Task<string> SendMessageAsync(string message, int timeout = 2000, string pipeName = ServicePipe)
|
||||
{
|
||||
var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
|
||||
try
|
||||
{
|
||||
await pipeClient.ConnectAsync(timeout);
|
||||
try
|
||||
{
|
||||
var ss = new StreamString(pipeClient);
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
await ss.WriteStringAsync(message);
|
||||
public static async Task<string> SendMessageAsync(string message, int timeout = DefaultTimeout, string pipeName = ServicePipe)
|
||||
{
|
||||
var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
|
||||
try
|
||||
{
|
||||
await pipeClient.ConnectAsync(timeout);
|
||||
try
|
||||
{
|
||||
var ss = new StreamString(pipeClient);
|
||||
|
||||
var result = await ss.ReadStringAsync();
|
||||
await ss.WriteStringAsync(message);
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeClient.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "PipeUtils.SendMessage");
|
||||
var result = await ss.ReadStringAsync();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeClient.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "PipeUtils.SendMessage");
|
||||
|
||||
public static string SendMessage(string message, int timeout = 2000, string pipeName = ServicePipe)
|
||||
{
|
||||
var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
|
||||
try
|
||||
{
|
||||
pipeClient.Connect(timeout);
|
||||
try
|
||||
{
|
||||
var ss = new StreamString(pipeClient);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ss.WriteString(message);
|
||||
public static string SendMessage(string message, int timeout = 5000, string pipeName = ServicePipe)
|
||||
{
|
||||
var pipeClient = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
|
||||
try
|
||||
{
|
||||
pipeClient.Connect(timeout);
|
||||
try
|
||||
{
|
||||
var ss = new StreamString(pipeClient);
|
||||
|
||||
var result = ss.ReadString();
|
||||
ss.WriteString(message);
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeClient.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "PipeUtils.SendMessage");
|
||||
var result = ss.ReadString();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
pipeClient.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "PipeUtils.SendMessage");
|
||||
|
||||
public static async Task<SvcResultMessage> SendMessageAsync(SvcMessage message, int timeout = 2000)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var resultJson = await SendMessageAsync(messageJson, timeout);
|
||||
public static async Task<SvcResultMessage> SendMessageAsync(SvcMessage message, int timeout = DefaultTimeout)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
|
||||
if (resultJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var resultJson = await SendMessageAsync(messageJson, timeout);
|
||||
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
if (resultJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultMessage;
|
||||
}
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
|
||||
public static SvcResultMessage SendMessage(SvcMessage message, int timeout = 2000)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
var resultJson = SendMessage(messageJson, timeout);
|
||||
public static SvcResultMessage SendMessage(SvcMessage message, int timeout = DefaultTimeout)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
|
||||
if (resultJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var resultJson = SendMessage(messageJson, timeout);
|
||||
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
if (resultJson == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return resultMessage;
|
||||
}
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
|
||||
public static async Task<SvcResultMessage> SendMessageAsync(SvcMessageType messageType)
|
||||
{
|
||||
var message = new SvcMessage { MessageType = messageType };
|
||||
return resultMessage;
|
||||
}
|
||||
|
||||
return await SendMessageAsync(message);
|
||||
}
|
||||
public static async Task<SvcResultMessage> SendMessageAsync(SvcMessageType messageType)
|
||||
{
|
||||
var message = new SvcMessage { MessageType = messageType };
|
||||
|
||||
public static async Task<T> SendRpcMessageAsync<T>(SvcRpcMessage message, int timeout = 2000, string pipeName = ServicePipe)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
return await SendMessageAsync(message);
|
||||
}
|
||||
|
||||
var resultJson = await SendMessageAsync(messageJson, timeout, pipeName);
|
||||
public static T SendRpcMessage<T>(SvcRpcMessage message, int timeout = DefaultTimeout, string pipeName = ServicePipe)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
|
||||
if (resultJson == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
var resultJson = SendMessage(messageJson, timeout, pipeName);
|
||||
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
if (resultJson == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (resultMessage?.Data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
|
||||
var result = JsonConvert.DeserializeObject<T>(resultMessage.Data);
|
||||
if (resultMessage?.Data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
var result = JsonConvert.DeserializeObject<T>(resultMessage.Data);
|
||||
|
||||
public static T SendRpcMessage<T>(SvcRpcMessage message, int timeout = 2000, string pipeName = ServicePipe)
|
||||
{
|
||||
var messageJson = JsonConvert.SerializeObject(message);
|
||||
|
||||
var resultJson = SendMessage(messageJson, timeout, pipeName);
|
||||
|
||||
if (resultJson == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
|
||||
|
||||
if (resultMessage?.Data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var result = JsonConvert.DeserializeObject<T>(resultMessage.Data);
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ColorControl.Shared.Common;
|
||||
|
||||
public class UpdateInfoDto
|
||||
{
|
||||
public bool UpdateAvailable { get; set; }
|
||||
public string NewVersionNumber { get; set; }
|
||||
public string HtmlUrl { get; set; }
|
||||
public string DownloadUrl { get; set; }
|
||||
}
|
||||
+718
-728
File diff suppressed because it is too large.
Load diff
@@ -66,6 +66,8 @@ public class AmdPreset : PresetBase
|
||||
primaryDisplay = preset.primaryDisplay;
|
||||
displayName = preset.displayName;
|
||||
DisplayId = preset.DisplayId;
|
||||
IsDisplayPreset = preset.IsDisplayPreset;
|
||||
ShowInQuickAccess = preset.ShowInQuickAccess;
|
||||
|
||||
applyColorData = preset.applyColorData;
|
||||
colorDepth = preset.colorDepth;
|
||||
@@ -121,7 +123,7 @@ public class AmdPreset : PresetBase
|
||||
values.Add(string.Format("{0}: {1}Hz", DisplayConfig.ApplyRefreshRate ? "Included" : "Excluded", DisplayConfig.RefreshRate));
|
||||
values.Add(string.Format("{0}{1}", DisplayConfig.ApplyResolution ? "Included" : "Excluded", DisplayConfig.ApplyResolution ? ": " + DisplayConfig.GetResolutionDesc() : ""));
|
||||
values.Add(string.Format("{0}: {1}", applyDithering ? "Included" : "Excluded", ditherState));
|
||||
values.Add(string.Format("{0}: {1}", applyHDR ? "Included" : "Excluded", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled"));
|
||||
values.Add(string.Format("{0}: {1}{2}", applyHDR ? "Included" : "Excluded", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled", SDRBrightness.HasValue ? $", SDR brightness: {SDRBrightness.Value}%" : ""));
|
||||
values.Add(shortcut);
|
||||
values.Add(string.Format("{0}", config?.AmdPresetId_ApplyOnStartup == id ? "Yes" : string.Empty));
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using MHC2Gen;
|
||||
|
||||
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 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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using MHC2Gen;
|
||||
|
||||
namespace ColorControl.Shared.Contracts.DisplayInfo;
|
||||
|
||||
public class ColorPoint
|
||||
{
|
||||
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(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 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 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 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 IsAdvancedColorSupported { get; set; }
|
||||
public bool IsAdvancedColorEnabled { get; set; }
|
||||
|
||||
public List<DisplayColorInfo> DisplayColors { get; set; } = new();
|
||||
public List<string> ColorProfiles { get; set; } = new();
|
||||
public string DefaultColorProfile { get; set; }
|
||||
|
||||
public bool IsHdrSupportedAndEnabled => IsAdvancedColorSupported && IsAdvancedColorEnabled;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName} on {AdapterName}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,116 +4,117 @@ namespace ColorControl.Shared.Contracts.LG;
|
||||
|
||||
public class LgPreset : PresetBase
|
||||
{
|
||||
public static event GetValueByKey GetDeviceName;
|
||||
public static event GetValueByKey GetAppName;
|
||||
public static event GetValueByKey GetDeviceName;
|
||||
public static event GetValueByKey GetAppName;
|
||||
|
||||
public string appId { get; set; }
|
||||
public List<string> steps { get; set; }
|
||||
public string DeviceMacAddress { get; set; }
|
||||
public dynamic AppParams { get; set; }
|
||||
public string appId { get; set; }
|
||||
public List<string> steps { get; set; }
|
||||
public string DeviceMacAddress { get; set; }
|
||||
public dynamic AppParams { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int X { get; set; }
|
||||
[JsonIgnore]
|
||||
public int Y { get; set; }
|
||||
[JsonIgnore]
|
||||
public int X { get; set; }
|
||||
[JsonIgnore]
|
||||
public int Y { get; set; }
|
||||
|
||||
public LgPreset() : base()
|
||||
{
|
||||
steps = new List<string>();
|
||||
}
|
||||
public LgPreset() : base()
|
||||
{
|
||||
steps = new List<string>();
|
||||
}
|
||||
|
||||
public LgPreset(LgPreset preset) : this()
|
||||
{
|
||||
id = GetNewId();
|
||||
public LgPreset(LgPreset preset) : this()
|
||||
{
|
||||
id = GetNewId();
|
||||
|
||||
Update(preset);
|
||||
Update(preset);
|
||||
|
||||
name = preset.name + " (copy)";
|
||||
shortcut = null;
|
||||
}
|
||||
name = preset.name + " (copy)";
|
||||
shortcut = null;
|
||||
}
|
||||
|
||||
public LgPreset(string name, string appId, IEnumerable<string> steps = null, dynamic appParams = null) : this()
|
||||
{
|
||||
this.name = name;
|
||||
this.appId = appId;
|
||||
if (steps != null)
|
||||
{
|
||||
this.steps.AddRange(steps);
|
||||
}
|
||||
AppParams = appParams;
|
||||
}
|
||||
public LgPreset(string name, string appId, IEnumerable<string> steps = null, dynamic appParams = null) : this()
|
||||
{
|
||||
this.name = name;
|
||||
this.appId = appId;
|
||||
if (steps != null)
|
||||
{
|
||||
this.steps.AddRange(steps);
|
||||
}
|
||||
AppParams = appParams;
|
||||
}
|
||||
|
||||
public LgPreset Clone()
|
||||
{
|
||||
var preset = new LgPreset(this);
|
||||
public LgPreset Clone()
|
||||
{
|
||||
var preset = new LgPreset(this);
|
||||
|
||||
return preset;
|
||||
}
|
||||
return preset;
|
||||
}
|
||||
|
||||
public LgPreset CloneWithId()
|
||||
{
|
||||
var preset = new LgPreset(this);
|
||||
preset.SetId(id);
|
||||
preset.name = name;
|
||||
preset.shortcut = shortcut;
|
||||
public LgPreset CloneWithId()
|
||||
{
|
||||
var preset = new LgPreset(this);
|
||||
preset.SetId(id);
|
||||
preset.name = name;
|
||||
preset.shortcut = shortcut;
|
||||
|
||||
return preset;
|
||||
}
|
||||
return preset;
|
||||
}
|
||||
|
||||
public void Update(LgPreset preset)
|
||||
{
|
||||
name = preset.name;
|
||||
appId = preset.appId;
|
||||
shortcut = preset.shortcut;
|
||||
steps.Clear();
|
||||
steps.AddRange(preset.steps);
|
||||
Description = preset.Description;
|
||||
DeviceMacAddress = preset.DeviceMacAddress;
|
||||
public void Update(LgPreset preset)
|
||||
{
|
||||
name = preset.name;
|
||||
appId = preset.appId;
|
||||
shortcut = preset.shortcut;
|
||||
steps.Clear();
|
||||
steps.AddRange(preset.steps);
|
||||
Description = preset.Description;
|
||||
DeviceMacAddress = preset.DeviceMacAddress;
|
||||
ShowInQuickAccess = preset.ShowInQuickAccess;
|
||||
|
||||
var trigger = preset.Triggers.FirstOrDefault();
|
||||
if (trigger != null)
|
||||
{
|
||||
UpdateTrigger(trigger.Trigger, trigger.Conditions, trigger.IncludedProcessesAsString, trigger.ExcludedProcessesAsString);
|
||||
}
|
||||
}
|
||||
var trigger = preset.Triggers.FirstOrDefault();
|
||||
if (trigger != null)
|
||||
{
|
||||
UpdateTrigger(trigger.Trigger, trigger.Conditions, trigger.IncludedProcessesAsString, trigger.ExcludedProcessesAsString);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<string> GetColumnNames()
|
||||
{
|
||||
return new List<string>() { "Name", "Device|140", "App|200", "Steps|400", "Shortcut", "Trigger" };
|
||||
}
|
||||
public static List<string> GetColumnNames()
|
||||
{
|
||||
return new List<string>() { "Name", "Device|140", "App|200", "Steps|400", "Shortcut", "Trigger" };
|
||||
}
|
||||
|
||||
public override List<string> GetDisplayValues(Config config = null)
|
||||
{
|
||||
var values = new List<string>();
|
||||
public override List<string> GetDisplayValues(Config config = null)
|
||||
{
|
||||
var values = new List<string>();
|
||||
|
||||
values.Add(name);
|
||||
values.Add(name);
|
||||
|
||||
var deviceString = "Global";
|
||||
if (!string.IsNullOrEmpty(DeviceMacAddress))
|
||||
{
|
||||
deviceString = GetDeviceName?.Invoke(DeviceMacAddress) ?? DeviceMacAddress;
|
||||
}
|
||||
values.Add(deviceString);
|
||||
var deviceString = "Global";
|
||||
if (!string.IsNullOrEmpty(DeviceMacAddress))
|
||||
{
|
||||
deviceString = GetDeviceName?.Invoke(DeviceMacAddress) ?? DeviceMacAddress;
|
||||
}
|
||||
values.Add(deviceString);
|
||||
|
||||
var app = appId;
|
||||
if (!string.IsNullOrEmpty(appId))
|
||||
{
|
||||
app = GetAppName?.Invoke(appId) ?? appId;
|
||||
}
|
||||
values.Add(app);
|
||||
values.Add(GetStepsDisplay());
|
||||
values.Add(shortcut);
|
||||
var app = appId;
|
||||
if (!string.IsNullOrEmpty(appId))
|
||||
{
|
||||
app = GetAppName?.Invoke(appId) ?? appId;
|
||||
}
|
||||
values.Add(app);
|
||||
values.Add(GetStepsDisplay());
|
||||
values.Add(shortcut);
|
||||
|
||||
if (Triggers.Any())
|
||||
{
|
||||
values.Add(Triggers.First().ToString());
|
||||
}
|
||||
if (Triggers.Any())
|
||||
{
|
||||
values.Add(Triggers.First().ToString());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
public string GetStepsDisplay()
|
||||
{
|
||||
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
|
||||
}
|
||||
public string GetStepsDisplay()
|
||||
{
|
||||
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,30 @@
|
||||
|
||||
public class ModuleDto
|
||||
{
|
||||
public bool IsActive { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string Link { get; set; }
|
||||
public List<string> Info { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public string Link { get; set; }
|
||||
public List<string> Info { get; set; }
|
||||
|
||||
public string GetIconClass()
|
||||
{
|
||||
if (DisplayName.Contains("NVIDIA"))
|
||||
{
|
||||
return "nvidia";
|
||||
}
|
||||
if (DisplayName.Contains("AMD"))
|
||||
{
|
||||
return "gpu-card";
|
||||
}
|
||||
if (DisplayName.Contains("LG") || DisplayName.Contains("Samsung"))
|
||||
{
|
||||
return "tv";
|
||||
}
|
||||
if (DisplayName.Contains("Game"))
|
||||
{
|
||||
return "controller";
|
||||
}
|
||||
|
||||
return "plus-square-fill";
|
||||
}
|
||||
}
|
||||
+524
-523
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,5 @@
|
||||
using ColorControl.Shared.Contracts;
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Contracts;
|
||||
using ColorControl.Shared.EventDispatcher;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
@@ -503,5 +504,35 @@ namespace ColorControl.Shared.Forms
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public static int? SelectIntValue(int min, int max, int? currentValue, string label)
|
||||
{
|
||||
var field = new FieldDefinition
|
||||
{
|
||||
MinValue = min,
|
||||
MaxValue = max,
|
||||
Label = label,
|
||||
FieldType = FieldType.Numeric,
|
||||
Value = currentValue
|
||||
};
|
||||
|
||||
var resultFields = MessageForms.ShowDialog(field.Label, new[] { field });
|
||||
|
||||
if (!resultFields.Any())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var strValue = (string)resultFields.First().Value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(strValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var settingValue = Utils.ParseInt(strValue);
|
||||
|
||||
return settingValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -184,6 +184,8 @@ namespace ColorControl.Shared.Native
|
||||
modesArray[targetIndex].targetMode.targetVideoSignalInfo.vSyncFreq.denominator != refreshRate.Denominator)
|
||||
{
|
||||
modesArray[targetIndex].targetMode.targetVideoSignalInfo.vSyncFreq = new DisplayConfigRational { denominator = refreshRate.Denominator, numerator = refreshRate.Numerator };
|
||||
pathsArray[pathIndex].targetInfo.refreshRate = new DisplayConfigRational { numerator = displayConfig.RefreshRate.Numerator, denominator = displayConfig.RefreshRate.Denominator };
|
||||
|
||||
isModeChangeNeeded = true;
|
||||
}
|
||||
|
||||
@@ -215,7 +217,6 @@ namespace ColorControl.Shared.Native
|
||||
pathsArray[pathIndex].targetInfo.rotation = newRotation;
|
||||
isModeChangeNeeded = true;
|
||||
}
|
||||
//pathsArray[pathIndex].targetInfo.refreshRate = new DisplayConfigRational { numerator = displayConfig.RefreshRate.Numerator, denominator = displayConfig.RefreshRate.Denominator };
|
||||
|
||||
var currentX = modesArray[sourceIndex].sourceMode.position.x;
|
||||
var currentY = modesArray[sourceIndex].sourceMode.position.y;
|
||||
@@ -501,6 +502,18 @@ namespace ColorControl.Shared.Native
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<string> GetAllColorProfileNames()
|
||||
{
|
||||
var folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "spool", "drivers", "color");
|
||||
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return Directory.EnumerateFiles(folder).Select(n => n.Split("\\").Last()).Where(n => n.EndsWith(".icm", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
private static string GetDisplayDeviceRegistryKey(string displayName)
|
||||
{
|
||||
var hMonitor = FormUtils.GetMonitorForDisplayName(displayName);
|
||||
|
||||
+147
-153
@@ -7,206 +7,200 @@ namespace ColorControl.Shared.Services;
|
||||
|
||||
public class RpcServerService
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public static List<Type> ServiceTypes = new List<Type>();
|
||||
public static List<Type> ServiceTypes = new List<Type>();
|
||||
|
||||
public RpcServerService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
public RpcServerService(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task<SvcResultMessage> ExecuteRpcAsync(SvcRpcMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = GetServiceInstance(message.ServiceName);
|
||||
public async Task<SvcResultMessage> ExecuteRpcAsync(SvcRpcMessage message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = GetServiceInstance(message.ServiceName);
|
||||
|
||||
var method = GetSuitableMethod(service, message.MethodName, message.Arguments);
|
||||
var method = GetSuitableMethod(service, message.MethodName, message.Arguments);
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No suitable method {message.MethodName} found in service {service.GetType().Name} with {message.Arguments.Length} arguments");
|
||||
}
|
||||
if (method == null)
|
||||
{
|
||||
throw new InvalidOperationException($"No suitable method {message.MethodName} found in service {service.GetType().Name} with {message.Arguments.Length} arguments");
|
||||
}
|
||||
|
||||
var convertedArgs = new List<object>();
|
||||
var convertedArgs = new List<object>();
|
||||
|
||||
var methodParams = method.GetParameters();
|
||||
for (var i = 0; i < message.Arguments.Length; i++)
|
||||
{
|
||||
var arg = message.Arguments[i];
|
||||
var methodParam = methodParams[i];
|
||||
var methodParams = method.GetParameters();
|
||||
for (var i = 0; i < message.Arguments.Length; i++)
|
||||
{
|
||||
var arg = message.Arguments[i];
|
||||
var methodParam = methodParams[i];
|
||||
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
}
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
}
|
||||
|
||||
while (convertedArgs.Count < methodParams.Length)
|
||||
{
|
||||
convertedArgs.Add(Type.Missing);
|
||||
}
|
||||
while (convertedArgs.Count < methodParams.Length)
|
||||
{
|
||||
convertedArgs.Add(Type.Missing);
|
||||
}
|
||||
|
||||
Logger.Debug($"Executing RPC: {message.ServiceName}.{message.MethodName}({string.Join(", ", convertedArgs)})");
|
||||
Logger.Debug($"Executing RPC: {message.ServiceName}.{message.MethodName}({string.Join(", ", convertedArgs)})");
|
||||
|
||||
var result = method.Invoke(service, convertedArgs.ToArray());
|
||||
var result = method.Invoke(service, convertedArgs.ToArray());
|
||||
|
||||
if (result is Task task)
|
||||
{
|
||||
await task;
|
||||
if (result is Task task)
|
||||
{
|
||||
await task;
|
||||
|
||||
result = task.GetType().GetProperty("Result").GetValue(task);
|
||||
}
|
||||
result = task.GetType().GetProperty("Result").GetValue(task);
|
||||
}
|
||||
|
||||
var data = JsonConvert.SerializeObject(result);
|
||||
var data = JsonConvert.SerializeObject(result);
|
||||
|
||||
return SvcResultMessage.FromResult(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"ExecuteRpcAsync: {ex.Message}");
|
||||
return SvcResultMessage.FromResult(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"ExecuteRpcAsync: {ex.Message}");
|
||||
|
||||
return new SvcResultMessage { ErrorMessage = ex.Message, Result = false };
|
||||
}
|
||||
}
|
||||
return new SvcResultMessage { ErrorMessage = ex.InnerException?.Message ?? ex.Message, Result = false };
|
||||
}
|
||||
}
|
||||
|
||||
private void AddArgument(object arg, Type type, List<object> list)
|
||||
{
|
||||
if (arg != null && arg.GetType() != type)
|
||||
{
|
||||
if (arg.GetType().Name == "JObject")
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject(arg.ToString(), type);
|
||||
list.Add(obj);
|
||||
return;
|
||||
}
|
||||
else if (arg.GetType().Name == "JArray")
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject(arg.ToString(), type);
|
||||
list.Add(obj);
|
||||
return;
|
||||
}
|
||||
if (type.IsEnum)
|
||||
{
|
||||
// Add enum directly to list and not return as 'object'
|
||||
list.Add(Enum.ToObject(type, arg));
|
||||
return;
|
||||
}
|
||||
private void AddArgument(object arg, Type type, List<object> list)
|
||||
{
|
||||
if (arg != null && arg.GetType() != type)
|
||||
{
|
||||
if (arg.GetType().Name == "JObject" || arg.GetType().Name == "JArray")
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject(arg.ToString(), type);
|
||||
list.Add(obj);
|
||||
return;
|
||||
}
|
||||
if (type.IsEnum)
|
||||
{
|
||||
// Add enum directly to list and not return as 'object'
|
||||
list.Add(Enum.ToObject(type, arg));
|
||||
return;
|
||||
}
|
||||
|
||||
list.Add(Convert.ChangeType(arg, type));
|
||||
return;
|
||||
}
|
||||
list.Add(Convert.ChangeType(arg, type));
|
||||
return;
|
||||
}
|
||||
|
||||
list.Add(arg);
|
||||
}
|
||||
list.Add(arg);
|
||||
}
|
||||
|
||||
public async Task<SvcResultMessage> ExecuteRpcTypedAsync(SvcRpcMessageTyped message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = GetServiceInstance(message.ServiceName);
|
||||
public async Task<SvcResultMessage> ExecuteRpcTypedAsync(SvcRpcMessageTyped message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var service = GetServiceInstance(message.ServiceName);
|
||||
|
||||
var method = service.GetType().GetMethod(message.MethodName);
|
||||
var method = service.GetType().GetMethod(message.MethodName);
|
||||
|
||||
var props = message.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
|
||||
var methodParams = method.GetParameters();
|
||||
var props = message.GetType().GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
|
||||
var methodParams = method.GetParameters();
|
||||
|
||||
var convertedArgs = new List<object>();
|
||||
var paramIndex = 0;
|
||||
var convertedArgs = new List<object>();
|
||||
var paramIndex = 0;
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
Logger.Debug($"Prop: {prop.Name}");
|
||||
foreach (var prop in props)
|
||||
{
|
||||
Logger.Debug($"Prop: {prop.Name}");
|
||||
|
||||
var arg = prop.GetValue(message);
|
||||
var methodParam = methodParams[paramIndex];
|
||||
var arg = prop.GetValue(message);
|
||||
var methodParam = methodParams[paramIndex];
|
||||
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
|
||||
paramIndex++;
|
||||
}
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
while (convertedArgs.Count < methodParams.Length)
|
||||
{
|
||||
convertedArgs.Add(Type.Missing);
|
||||
}
|
||||
while (convertedArgs.Count < methodParams.Length)
|
||||
{
|
||||
convertedArgs.Add(Type.Missing);
|
||||
}
|
||||
|
||||
Logger.Debug($"Executing RPC-typed: {message.ServiceName}.{message.MethodName}({string.Join(", ", convertedArgs)})");
|
||||
Logger.Debug($"Executing RPC-typed: {message.ServiceName}.{message.MethodName}({string.Join(", ", convertedArgs)})");
|
||||
|
||||
var result = method.Invoke(service, convertedArgs.ToArray());
|
||||
var result = method.Invoke(service, convertedArgs.ToArray());
|
||||
|
||||
if (result is Task)
|
||||
{
|
||||
await (result as Task);
|
||||
}
|
||||
if (result is Task)
|
||||
{
|
||||
await (result as Task);
|
||||
}
|
||||
|
||||
var data = JsonConvert.SerializeObject(result);
|
||||
var data = JsonConvert.SerializeObject(result);
|
||||
|
||||
return SvcResultMessage.FromResult(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"ExecuteRpcTypedAsync: {ex.Message}");
|
||||
return SvcResultMessage.FromResult(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"ExecuteRpcTypedAsync: {ex.Message}");
|
||||
|
||||
return new SvcResultMessage { ErrorMessage = ex.Message, Result = false };
|
||||
}
|
||||
}
|
||||
return new SvcResultMessage { ErrorMessage = ex.Message, Result = false };
|
||||
}
|
||||
}
|
||||
|
||||
private object GetServiceInstance(string name)
|
||||
{
|
||||
var type = Type.GetType($"ColorControl.Shared.Services.{name}");
|
||||
private object GetServiceInstance(string name)
|
||||
{
|
||||
var type = Type.GetType($"ColorControl.Shared.Services.{name}");
|
||||
|
||||
type ??= Type.GetType($"ColorControl.Services.NVIDIA.{name}");
|
||||
type ??= Type.GetType($"ColorControl.Services.NVIDIA.{name}");
|
||||
|
||||
type ??= ServiceTypes.FirstOrDefault(t => t.Name == name);
|
||||
type ??= ServiceTypes.FirstOrDefault(t => t.Name == name);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Type not found: {name}");
|
||||
}
|
||||
if (type == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Type not found: {name}");
|
||||
}
|
||||
|
||||
var service = _serviceProvider.GetRequiredService(type);
|
||||
var service = _serviceProvider.GetRequiredService(type);
|
||||
|
||||
return service;
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
private MethodInfo GetSuitableMethod(object service, string name, object[] arguments)
|
||||
{
|
||||
var methods = service.GetType().GetMethods().Where(m => m.Name == name).ToArray();
|
||||
private MethodInfo GetSuitableMethod(object service, string name, object[] arguments)
|
||||
{
|
||||
var methods = service.GetType().GetMethods().Where(m => m.Name == name).ToArray();
|
||||
|
||||
if (methods.Length == 1)
|
||||
{
|
||||
return methods[0];
|
||||
}
|
||||
if (methods.Length == 1)
|
||||
{
|
||||
return methods[0];
|
||||
}
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var convertedArgs = new List<object>();
|
||||
var methodParams = method.GetParameters();
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var convertedArgs = new List<object>();
|
||||
var methodParams = method.GetParameters();
|
||||
|
||||
if (methodParams.Length != arguments.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (methodParams.Length != arguments.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
var methodParam = methodParams[i];
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
{
|
||||
var arg = arguments[i];
|
||||
var methodParam = methodParams[i];
|
||||
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
}
|
||||
AddArgument(arg, methodParam.ParameterType, convertedArgs);
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return method;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Contracts;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace ColorControl.Shared.Services;
|
||||
|
||||
public class RpcUiClientOptions
|
||||
{
|
||||
public int Timeout { get; set; } = 2000;
|
||||
}
|
||||
|
||||
public class RpcUiClientService
|
||||
{
|
||||
public RpcUiClientService()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<T> CallAsync<T>(string serviceName, string method, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
var timeout = Debugger.IsAttached ? 60000 : 2000;
|
||||
|
||||
return await PipeUtils.SendRpcMessageAsync<T>(rpcMessage, timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
|
||||
public async Task<T> CallWithOptionsAsync<T>(string serviceName, string method, RpcUiClientOptions options, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
return await PipeUtils.SendRpcMessageAsync<T>(rpcMessage, options.Timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
|
||||
public T Call<T>(string serviceName, string method, params object[] arguments)
|
||||
{
|
||||
var rpcMessage = new SvcRpcMessage
|
||||
{
|
||||
ServiceName = serviceName,
|
||||
MethodName = method,
|
||||
Arguments = arguments
|
||||
};
|
||||
|
||||
var timeout = Debugger.IsAttached ? 60000 : 2000;
|
||||
|
||||
return PipeUtils.SendRpcMessage<T>(rpcMessage, timeout, pipeName: PipeUtils.MainPipe);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user