Compare commits

...
20 Commits
Author SHA1 Message Date
Maassoft d0d3bb4682 upgrade to .NET, fix webOS 26 compatibility, run new UI in separate process 2026-05-02 17:39:33 +02:00
月鉴风华andcyc19950621 8cb427f0b0 fix: update LG webOS register payload for webOS 26 compatibility (#577)
Co-authored-by: cyc19950621 <cyc19950621@users.noreply.github.com>

I will add a toggle so users have the option to activate/deactivate the new handshake (in case the new one does not work or reduces functionality on older webOS).
2026-05-02 14:07:29 +02:00
Maassoft 1c22b2daf8 fix some issues, add ddc settings, make new ui default 2025-09-28 15:10:25 +02:00
Maassoft 827157f30f fix several issues, add remote controls to new UI 2025-05-30 17:20:37 +02:00
DemoJameson c6c7e1ccc0 Add some LG settings from new devices (#502)
Includes 'Quick Frame Transport', '144Hz/165Hz Mode', 'ALLM', 'Dolby Vision PC'
2025-05-26 19:06:35 +02:00
Victor Nicolás Lizarraga Ochoa 043e5e7ff2 Add piecewise tone mapping option (#454)
* Add piecewise tone mapping option

* Add curve like to tone mapping transfer function

* Fix conflicts

* Experiments

* Add piecewise tone mapped option to UI

* Fix IIcc context diff and reset cproj settings
2025-03-29 17:27:01 +01:00
Vincent Maas 6e206a6728 fix Nvidia crash, remove UWP dependencies 2025-01-26 10:38:56 +01:00
Myungchul Keum d31d2f321f Add LG Dark room Mode (#459) 2024-12-28 11:48:11 +01:00
Vincent Maas 5c62273b33 fix color profile issues, add lock/unlock power off/on, add GPU OC settings to new UI 2024-10-20 11:18:02 +02:00
Vincent Maas 6f4b1e9779 fix condition 2024-09-22 15:52:42 +02:00
Vincent Maas c2b220c70b Merge branch 'master' of https://github.com/Maassoft/ColorControl 2024-09-22 15:27:16 +02:00
André Silva de Jesus a236e7a8a5 Support for display change trigger, connected display name preset condition and minor changes (#412)
* add displaychange trigger, connected display filter and other minor changes

* Undo unintended designer changes on LgPanel

* Try to fix unintended changes in the samsung panel

* Fix async handling of window message dispatcher event; Change to use native method for display enum as Screen.AllScreen caches and returns wrong results

* Uncomment SignManifests

* Fix mixed tabs/spaces
2024-09-22 15:27:07 +02:00
Vincent Maas 899e49c2d9 fix some Nvidia issues, improve color profiles, add GPU info to new UI 2024-09-22 15:24:54 +02:00
Vincent Maas f42a46e53f fix issues with new UI, add color profiles, add sdr brightness for AMD 2024-09-01 16:24:05 +02:00
Maassoft 4d2ca8a552 Merge pull request #392 from BergHeisen/improve-wol
Improve Wake on Lan Code
2024-08-11 17:22:36 +02:00
Marlon 42f6b3346c Filter on Description rather than name to detect a Hyper-V Interface 2024-08-04 20:17:27 +02:00
Marlon 048abda482 Explicitly bind to IP Address for the UDP Client 2024-08-04 19:44:45 +02:00
Marlon 05c938c8ac Correctly filter out Hyper-V Interfaces 2024-08-04 19:43:43 +02:00
Marlon 1347b700f9 Improve Wake on Lan Code 2024-08-04 16:33:23 +02:00
Vincent Maas 9e8e77d89b fix some issues after new release 2024-08-03 16:19:27 +02:00
217 changed files with 19592 additions and 12182 deletions

No files matched your search

+132 -7
View File
@@ -1,21 +1,58 @@
using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Shared.Services;
using ColorControl.UI.Pages.Generic;
using ColorControl.UI.Services;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using NStandard;
using System.Net;
namespace ColorControl.UI;
public static class Blazor
{
public static void Start(Config? config = null)
private static WebApplication? CurrentApplication;
public static CustomComponentBase? CurrentComponent { get; private set; }
public static async Task Start(string[] args)
{
var mutexId = args.GetValueOrDefault(0);
var useDarkMode = args.GetValueOrDefault(1) == "True";
var uiPort = Utils.ParseInt(args.GetValueOrDefault(2));
var allowRemoteConnections = args.GetValueOrDefault(3) == "True";
await Stop();
var builder = WebApplication.CreateBuilder();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddSingleton<AppState>(new AppState { SelectedTheme = (config?.UseDarkMode ?? true) ? "dark" : "light" });
builder.Services.AddSingleton(new AppState { SelectedTheme = useDarkMode ? "dark" : "light" });
builder.Services.AddTransient<RpcUiClientService>();
builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(config?.UiPort > 0 ? config.UiPort : 5000));
builder.Services.AddTransient<JSHelper>();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<NotificationService>();
builder.Services.Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(3));
var listenPort = uiPort > 0 ? uiPort : 0;
builder.WebHost.ConfigureKestrel(options =>
{
if (allowRemoteConnections)
{
options.ListenAnyIP(listenPort);
}
else
{
options.Listen(IPAddress.Parse("127.0.0.1"), listenPort);
}
});
builder.WebHost.UseStaticWebAssets();
var app = builder.Build();
@@ -27,14 +64,102 @@ public static class Blazor
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
//app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapStaticAssets();
//app.UsePathBase("/");
app.MapRazorComponents<Components.App>()
.AddInteractiveServerRenderMode();
app.Run();
CurrentApplication = app;
var _ = Task.Run(async () =>
{
await Task.Delay(500);
var port = GetCurrentPort();
if (port == -1)
{
return;
}
var rpcServer = app.Services.GetRequiredService<RpcUiClientService>();
await rpcServer.CallAsync<bool>("OptionsService", "SetCurrentUiSettings", port, allowRemoteConnections);
if (!mutexId.IsNullOrWhiteSpace())
{
Console.WriteLine("Waiting for mutex: " + mutexId);
var mutex = new Mutex(false, mutexId, out var createdNew);
if (createdNew)
{
Console.WriteLine("Mutex was created, skipping.");
return;
}
try
{
mutex.WaitOne();
Console.WriteLine("Waiting done.");
}
catch (Exception ex)
{
Console.WriteLine("Wait completed: " + ex.Message);
}
Environment.Exit(0);
}
});
await app.RunAsync();
}
public static async Task Stop()
{
if (CurrentApplication != null)
{
await CurrentApplication.StopAsync();
CurrentApplication = null;
}
}
public static int GetCurrentPort(Config? config = null)
{
if (CurrentApplication == null)
{
return -1;
}
var server = CurrentApplication.Services.GetService<IServer>();
var addressFeature = server?.Features.Get<IServerAddressesFeature>();
if (addressFeature == null)
{
return -1;
}
foreach (var address in addressFeature.Addresses)
{
return int.Parse(address.Split(':').Last());
}
return -1;
}
public static void SetCurrentComponent(CustomComponentBase? customComponentBase)
{
CurrentComponent = customComponentBase;
}
public static void ClearCurrentComponent(CustomComponentBase customComponentBase)
{
if (CurrentComponent == customComponentBase)
{
CurrentComponent = null;
}
}
}
+38 -3
View File
@@ -1,21 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Library</OutputType>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog" Version="6.1.2" />
</ItemGroup>
<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 +30,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>
@@ -34,6 +42,9 @@
<Content Update="Components\Pages\LG\LgExecuteActionModal.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Nvidia\NvidiaGpuSettings.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Samsung\SamsungSummary.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
@@ -52,6 +63,30 @@
<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>
<Content Update="wwwroot\remotes\LG_FactorySVC.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\remotes\LG_MR20GA.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\remotes\LG_MR23GN.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\remotes\Samsung_AA81-00243A.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\remotes\Samsung_BN59-01315B.jpg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
+40 -10
View File
@@ -1,8 +1,17 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Pages.Generic
@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
@inject JSHelper _jsHelper
@implements IDisposable
@attribute [StreamRendering]
@@ -13,30 +22,42 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<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" />
<ImportMap />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
<HeadOutlet @rendermode="InteractiveServer" />
<script>
addEventListener('focus', (event) => {
var isModalShown = document.getElementsByClassName("modal show").length > 0;
DotNet.invokeMethodAsync("ColorControl.UI", "RefreshSummary", isModalShown);
});
</script>
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<Routes @rendermode="InteractiveServer" />
<script src="@Assets["_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();
}
@@ -47,4 +68,13 @@
{
AppState.OnChange -= StateHasChanged;
}
[JSInvokable]
public static async Task RefreshSummary(bool isModalShown)
{
if (Blazor.CurrentComponent is SummaryComponentBase summaryComponent && !isModalShown)
{
await summaryComponent.RefreshExternal();
}
}
}
+16 -12
View File
@@ -1,11 +1,9 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
@@ -28,28 +26,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,10 +1,10 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@inherits LayoutComponentBase
@inject AppState AppState
@inject NavigationManager _navigationManager;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
<div class="top-row px-4 pt-3 text-end">
@* <button type="button" class="btn btn-primary me-2" @onclick="ChangeTheme">@ThemeTransitionName</button> *@
@@ -15,7 +15,7 @@
private string ThemeTransitionName = "Dark";
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme";
}
@@ -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
@rendermode Constants.RenderMode
@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>
@@ -48,7 +48,7 @@
{
<input class="form-check-input me-1" style="font-size: 1rem; visibility: hidden" type="checkbox">
}
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
Display
</button>
</span>
@@ -93,7 +93,7 @@
{
<input class="form-check-input me-1" style="font-size: 1rem" title="Apply these settings" type="checkbox" @bind="Preset.DisplayConfig.ApplyResolution" id="applyResolution">
}
<button class="accordion-button py-2 @(Preset.DisplayConfig.ApplyResolution ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#resolutionSettings">
<button class="accordion-button py-2 @(!Preset.IsDisplayPreset ? "collapsed" : "") @(Preset.DisplayConfig.ApplyResolution ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#resolutionSettings">
Resolution
</button>
</span>
@@ -157,7 +157,7 @@
</button>
</span>
</h2>
<div id="refreshRateSettings" class="accordion-collapse collapse show" data-bs-parent="#presetAccordion">
<div id="refreshRateSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="refreshRate">Refresh Rate</label>
@@ -264,10 +264,7 @@
</label>
</div>
}
<div class="mb-2">
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
</div>
<NullableRangeInput TValue="int" @bind-Value="Preset.SDRBrightness" Id="sdrBrightness" Label="SDR brightness" Unit="%" />
</div>
</div>
</div>
@@ -316,7 +313,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");
}
@@ -356,7 +353,7 @@
if (Preset.IsDisplayPreset)
{
//Preset.UpdateAutoApplySettings(PresetParam);
Preset.UpdateAutoApplySettings(PresetParam);
await ApplyDisplayPreset();
return;
@@ -1,14 +1,14 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@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,12 +3,14 @@
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Services;
@using ColorControl.UI.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inherits SummaryComponentBase
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
@@ -38,31 +40,35 @@
else
{
<div class="row mb-3">
<div class="col">
@if (!DisplayPresets.Any())
{
<span>No displays found</span>
}
@foreach (var preset in DisplayPresets)
{
<div class="card">
<div class="card-header">
@preset.displayName
@if (preset.DisplayConfig.IsPrimary == true)
{
<small class="text-muted ms-2">Primary</small>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
Settings
</button>
@if (!DisplayPresets.Any())
{
<span>No displays found</span>
}
@foreach (var preset in DisplayPresets)
{
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@preset.displayName
@if (preset.DisplayConfig.IsPrimary == true)
{
<small class="text-muted ms-2">Primary</small>
}
</span>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
Settings
</button>
</div>
</div>
<div class="card-body">
<small class="text-body">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</small>
<small class="text-muted">@preset.colorDepth, @preset.pixelFormat</small>
</div>
</div>
}
</div>
</div>
}
</div>
}
@@ -88,10 +94,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">
@@ -102,15 +112,25 @@ 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-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="#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>
</a>
</span>
}
</div>
</div>
@@ -120,17 +140,22 @@ else
@code {
private List<AmdPreset>? Presets;
private List<AmdPreset>? DisplayPresets;
private List<VirtualResolution>? Resolutions;
private AmdPreset? CurrentPreset;
private PresetOrder PresetOrder;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await Refresh();
}
protected override async Task Refresh()
{
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)
@@ -149,18 +174,19 @@ else
await _rpcClientService.CallAsync<bool>("AmdService", "ApplyPresetWithId", preset.id);
}
private async Task PresetOnEditClick(AmdPreset preset)
private void PresetOnEditClick(AmdPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnCloneClick(AmdPreset preset)
private void PresetOnCloneClick(AmdPreset preset)
{
CurrentPreset = new AmdPreset(preset);
CurrentPreset.name = preset.name;
CurrentPreset.SetCloneValues();
}
private async Task PresetOnDeleteClick(AmdPreset preset)
private void PresetOnDeleteClick(AmdPreset preset)
{
CurrentPreset = preset;
}
@@ -177,12 +203,12 @@ else
StateHasChanged();
}
private async Task DisplayPresetOnClick(AmdPreset preset)
private void DisplayPresetOnClick(AmdPreset preset)
{
CurrentPreset = preset;
}
private async Task AddPresetOnClick()
private void AddPresetOnClick()
{
CurrentPreset = new AmdPreset();
}
@@ -0,0 +1,533 @@
@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 Constants.RenderMode
@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="mb-2">
<label class="form-label" for="renderingIntent">Rendering intent</label>
<select class="form-select" id="renderingIntent" @bind="ColorProfile.RenderingIntent">
@foreach (var renderingIntent in Enum.GetValues<LittleCms.RenderingIntent>().Where(ri => ri <= LittleCms.RenderingIntent.ABSOLUTE_COLORIMETRIC))
{
<option value="@renderingIntent">@renderingIntent.GetDescription()</option>
}
</select>
</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" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</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="-50" max="200" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="shadowDetailRange">Shadow detail range (%)</label>
<input class="form-control" id="shadowDetailRange" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailRange" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="redGain">Red gain (%)</label>
<input class="form-control" id="redGain" type="number" min="1" max="200" step="0.01" @bind="ColorProfile.RedGain" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="greenGain">Green gain (%)</label>
<input class="form-control" id="greenGain" type="number" min="1" max="200" step="0.01" @bind="ColorProfile.GreenGain" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="blueGain">Blue gain (%)</label>
<input class="form-control" id="blueGain" type="number" min="1" max="200" step="0.01" @bind="ColorProfile.BlueGain" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
@if (ColorProfile.IsHDRProfile)
{
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="hdrluminance">Current HDR Luminance (nits):</label>
<input class="form-control" id="hdrluminance" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingFromLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="hdrluminanceoutput">HDR Luminance to convert (nits):</label>
<input class="form-control" id="hdrluminanceoutput" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingToLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="brightnessmultiplier">Brightness Multiplier</label>
<input class="form-control" id="brightnessmultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrBrightnessMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="gammamultiplier">Gamma Multiplier</label>
<input class="form-control" id="gammamultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrGammaMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
@if (DisplayInfo?.IsAdvancedColorSupported == true && ColorProfile != null)
{
<input class="form-check-input" type="checkbox" @bind="ColorProfile.IsHDRProfile" id="associateAsHdrProfile">
<label class="form-check-label" for="associateAsHdrProfile">
Associate as HDR profile
</label>
}
<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="CloseOnClick">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;
protected override async Task OnParametersSetAsync()
{
if (ColorProfileName == null || !IsVisible)
{
return;
}
if (ColorProfileName == "")
{
ColorProfile = ColorProfileDto.CreateDefault(DisplayInfo?.IsHdrSupportedAndEnabled ?? false);
}
else
{
ColorProfile = await _rpcClientService.CallAsync<ColorProfileDto>("ColorProfileService", "GetColorProfile", ColorProfileName, DisplayInfo?.DeviceName);
}
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);
}
private async Task CloseOnClick()
{
IsVisible = false;
if (ColorProfile != null && AfterApply != null)
{
await AfterApply.Invoke(ColorProfile);
}
}
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,355 @@
@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.Pages.Generic
@using ColorControl.UI.Services
@inherits SummaryComponentBase
@rendermode Constants.RenderMode
@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" ExtraFields="AssociateFields" 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-2">
@if (!Displays.Any())
{
<span>No displays found</span>
}
else
{
<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 display in Displays)
{
<option value="@display.DisplayId">@display.DisplayName @@ @display.AdapterName</option>
}
</select>
</div>
}
</div>
}
@if (SelectedDisplay == null)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
<div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" role="switch" id="usePerUserSettings" @bind="SelectedDisplay.UsePerUserSettings" @bind:after="AfterUsePerUseSettings">
<label class="form-check-label" for="usePerUserSettings">Use my settings for this device</label>
</div>
</div>
<div class="mb-2 d-flex align-items-center">
<h2>Display Associated Color profiles</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)
{
<span 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">
@if (colorProfile.IsHDRProfile)
{
<span class="me-2 badge text-bg-info" title="HDR profile">HDR</span>
}
else
{
<span class="me-2 badge text-bg-secondary" title="SDR profile">SDR</span>
}
@colorProfile.Name
@if (colorProfile.IsHDRProfile && colorProfile.Name == SelectedDisplay?.DefaultHdrColorProfile)
{
<span class="ms-2 badge text-bg-warning" title="Active HDR color profile">Active HDR</span>
}
else if (!colorProfile.IsHDRProfile && colorProfile.Name == SelectedDisplay?.DefaultSdrColorProfile)
{
<span class="ms-2 badge text-bg-warning" title="Active SDR color profile">Active SDR</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="() => SelectedColorProfile = 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.Name)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile.Name)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile.Name)" @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.Name">Disassociate</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile.Name)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile.Name)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile.Name)">Edit</a></li>
</ul>
</div>
</div>
</span>
}
</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="() => AssociateColorProfileOnClick(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="() => AssociateColorProfileOnClick(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 ColorProfileDto? SelectedColorProfile;
private bool UseAsTemplate;
private List<string>? ColorProfileNames;
private List<FieldDefinition>? AssociateFields;
private FieldDefinition AskHdrAssociationField = new FieldDefinition { Label = "Associate as HDR profile", FieldType = FieldType.CheckBox, Value = false };
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await Refresh();
}
protected override async Task Refresh()
{
await RefreshDisplays();
}
private void AddColorProfileOnClick()
{
SelectedColorProfileName = "";
}
private async Task EditColorProfileOnClick(string colorProfile)
{
UseAsTemplate = false;
SelectedColorProfileName = colorProfile;
await jsHelper.OpenModal("colorProfileModal");
}
private async Task SetColorProfileOnClick(ColorProfileDto colorProfile)
{
if (SelectedDisplay == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetDisplayDefaultColorProfile", SelectedDisplay.DeviceName, colorProfile.Name, Type.Missing, colorProfile.IsHDRProfile);
if (result)
{
if (colorProfile.IsHDRProfile)
{
SelectedDisplay.DefaultHdrColorProfile = colorProfile.Name;
}
else
{
SelectedDisplay.DefaultSdrColorProfile = colorProfile.Name;
}
}
}
private void AssociateColorProfileOnClick(string colorProfile)
{
SelectedColorProfileName = colorProfile;
if (SelectedDisplay?.IsAdvancedColorSupported == true)
{
AskHdrAssociationField.Value = false;
AssociateFields = [AskHdrAssociationField];
}
else
{
AssociateFields = null;
}
}
private void DeleteColorProfileOnClick(string colorProfile)
{
SelectedColorProfileName = colorProfile;
}
private void 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 (SelectedColorProfile == null || SelectedDisplay == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("ColorProfileService", "RemoveDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfile.Name, SelectedColorProfile.IsHDRProfile);
SelectedColorProfile = null;
await RefreshDisplays();
StateHasChanged();
}
private async Task ColorProfileOnAssociateConfirmed()
{
if (SelectedColorProfileName == null || SelectedDisplay == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("ColorProfileService", "AddDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, false, AskHdrAssociationField.BoolValue);
SelectedColorProfileName = null;
await RefreshDisplays();
StateHasChanged();
}
private async Task SelectedDisplayChanged()
{
SelectedDisplay = Displays?.FirstOrDefault(d => d.DisplayId == SelectedDisplayId);
ColorProfileNames = await _rpcClientService.CallAsync<List<string>>("ColorProfileService", "GetAllColorProfileNames");
if (SelectedDisplay != null)
{
ColorProfileNames = ColorProfileNames?.Except(SelectedDisplay.ColorProfiles.Select(p => p.Name)).ToList();
}
}
private async Task AfterColorProfileSaved(ColorProfileDto colorProfile)
{
await RefreshDisplays();
StateHasChanged();
}
private async Task RefreshDisplays()
{
Displays = await _rpcClientService.CallAsync<List<DisplayInfoDto>>("ColorProfileService", "GetDisplayInfos");
if (Displays?.Any() == true)
{
SelectedDisplayId = (Displays.FirstOrDefault(d => d.DisplayId == SelectedDisplayId) ?? Displays.First()).DisplayId;
}
await SelectedDisplayChanged();
}
private async Task AfterUsePerUseSettings()
{
if (SelectedDisplay == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetUsePerUserDisplayProfiles", SelectedDisplay.DeviceName, SelectedDisplay.UsePerUserSettings);
if (result)
{
await RefreshDisplays();
}
}
}
@@ -1,235 +1,242 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@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-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
</div>
<div class="modal-body">
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Preset == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
<label class="form-check-label" for="showInQuickAccess">
Show in Quick Access
</label>
</div>
<div class="mb-2">
<label class="form-label" for="path">File/URI</label>
<div class="input-group mb-2">
<input class="form-control" id="path" type="text" @bind="Preset.Path" required />
<button type="button" class="btn btn-primary" @onclick="SelectFileOnClick" disabled="@SelectingFile">
<span class="@(SelectingFile ? "spinner-border spinner-border-sm" : "")" aria-hidden="true"></span>
<span role="status">Select...</span>
</button>
</div>
<div id="selectFileHelp" class="form-text">
Selecting a file opens a file dialog outside of the browser on the system where the ColorControl application is running.
</div>
</div>
<div class="mb-2">
<label class="form-label" for="parameters">Parameters</label>
<input class="form-control" id="parameters" type="text" @bind="Preset.Parameters" />
</div>
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
</div>
<div class="modal-body">
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Preset == null)
{
<div>Loading...</div>
}
else
{
if (RecentApps != null)
{
<div class="mb-2">
<label class="form-label" for="recentApp">Recent app</label>
<select class="form-select" id="recentApp" @bind="RecentAppFilename" @bind:after="RecentAppSelected">
@foreach (var app in RecentApps)
{
<option value="@app.Filename" title="@app.Path">@app.FriendlyName</option>
}
</select>
</div>
}
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
<label class="form-check-label" for="showInQuickAccess">
Show in Quick Access
</label>
</div>
<div class="accordion accordion-flush" id="presetAccordion">
<StartProgramInput @bind-Path="Preset.Path" @bind-Parameters="Preset.Parameters" @bind-Path:after="PathChanged"></StartProgramInput>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Advanced settings
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
<label class="form-check-label" for="runAsAdministrator">
Run as administrator
</label>
</div>
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
<div class="mb-2">
<label class="form-label" for="priority">Process priority</label>
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
{
<option value="@priority.ToString()">@priority.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#autoApplySettings">
Auto-apply settings
</button>
</h2>
<div id="autoApplySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.AutoSettings.AllowAutoApply" id="autoApply">
<label class="form-check-label" for="autoApply">
Automatically execute preset when process starts
</label>
</div>
<div class="mb-2">
<label class="form-label" for="autoApplyAction">Auto-apply action</label>
<select class="form-select" id="priority" @bind="Preset.AutoSettings.ProcessAutoAction">
@foreach (var action in Enum.GetValues<ProcessAutoAction>())
{
<option value="@action.ToString()">@action.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Pre-launch" @bind-Steps="Preset.PreLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Post-launch" @bind-Steps="Preset.PostLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Finalize" @bind-Steps="Preset.FinalizeSteps" TPreset="GamePreset"></StepsInput>
</div>
</div>
</div>
<div class="accordion accordion-flush" id="presetAccordion">
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Advanced settings
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
<label class="form-check-label" for="runAsAdministrator">
Run as administrator
</label>
</div>
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
<div class="mb-2">
<label class="form-label" for="priority">Process priority</label>
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
{
<option value="@priority.ToString()">@priority.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#autoApplySettings">
Auto-apply settings
</button>
</h2>
<div id="autoApplySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.AutoSettings.AllowAutoApply" id="autoApply">
<label class="form-check-label" for="autoApply">
Automatically execute preset when process starts
</label>
</div>
<div class="mb-2">
<label class="form-label" for="autoApplyAction">Auto-apply action</label>
<select class="form-select" id="priority" @bind="Preset.AutoSettings.ProcessAutoAction">
@foreach (var action in Enum.GetValues<ProcessAutoAction>())
{
<option value="@action.ToString()">@action.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Pre-launch" @bind-Steps="Preset.PreLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Post-launch" @bind-Steps="Preset.PostLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Finalize" @bind-Steps="Preset.FinalizeSteps" TPreset="GamePreset"></StepsInput>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
private GamePreset? Preset;
private GamePreset? Preset;
[Parameter]
public GamePreset? PresetParam { get; set; }
[Parameter]
public GamePreset? PresetParam { get; set; }
[Parameter]
public Func<GamePreset, Task>? AfterApply { get; set; }
[Parameter]
public Func<GamePreset, Task>? AfterApply { get; set; }
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
private List<GameApp>? RecentApps;
private string RecentAppFilename = "";
public bool SelectingFile { get; set; }
public bool SelectingFile { get; set; }
private string WasValidated = "";
private bool IsVisible;
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
{
if (PresetParam == null)
{
return;
}
protected override async Task OnParametersSetAsync()
{
if (PresetParam == null)
{
return;
}
Preset = PresetParam.CloneWithId();
}
Preset = PresetParam.CloneWithId();
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (Preset.id == 0)
{
RecentApps = await _rpcClientService.CallAsync<List<GameApp>>("GameService", "GetMruApps", true);
RecentAppFilename = "";
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
if (RecentApps != null)
{
RecentApps.Insert(0, new GameApp { Filename = "", FriendlyName = "Choose a recent app to fill values" });
}
}
else
{
RecentApps = null;
}
}
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
IsVisible = false;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
}
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
private async Task SelectFileOnClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
SelectingFile = true;
try
{
var options = new RpcUiClientOptions
{
Timeout = 60000
};
IsVisible = false;
}
var result = await _rpcClientService.CallWithOptionsAsync<string>("GameService", "SelectPath", options);
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
}
if (result != null)
{
Preset.Path = result;
if (string.IsNullOrEmpty(Preset.name))
{
Preset.name = Path.GetFileNameWithoutExtension(result);
}
}
}
finally
{
SelectingFile = false;
}
}
private void RecentAppSelected()
{
if (RecentApps == null || Preset == null)
{
return;
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
var app = RecentApps.FirstOrDefault(a => a.Filename == RecentAppFilename);
if (app != null)
{
Preset.name = app.FriendlyName;
Preset.Path = app.Path;
}
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private void PathChanged()
{
if (Preset == null)
{
return;
}
if (Preset.Path != null && string.IsNullOrEmpty(Preset.name))
{
Preset.name = Path.GetFileNameWithoutExtension(Preset.Path);
}
}
}
@@ -1,15 +1,15 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@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,13 +2,13 @@
@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
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
@@ -52,10 +52,14 @@ 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)
{
<a class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)">
<span class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)">
<div class="d-flex align-items-start justify-content-between">
<div>
<h5 class="mb-1">
@@ -64,15 +68,29 @@ else
{
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
}
@if (!preset.PathExists)
{
<span class="ms-2 badge text-bg-danger" title="@($"Path '{preset.Path}' is not found")">Not found</span>
}
</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-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="#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>
</span>
}
</div>
</div>
@@ -106,18 +124,18 @@ else
await _rpcClientService.CallAsync<bool>("GameService", "ApplyPresetWithId", preset.id);
}
private async Task PresetOnEditClick(GamePreset preset)
private void PresetOnEditClick(GamePreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnCloneClick(GamePreset preset)
private void PresetOnCloneClick(GamePreset preset)
{
CurrentPreset = new GamePreset(preset);
CurrentPreset.SetCloneValues();
}
private async Task PresetOnDeleteClick(GamePreset preset)
private void PresetOnDeleteClick(GamePreset preset)
{
CurrentPreset = preset;
}
@@ -134,7 +152,7 @@ else
StateHasChanged();
}
private async Task AddPresetOnClick()
private void AddPresetOnClick()
{
CurrentPreset = new GamePreset();
}
@@ -1,10 +1,10 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
<FieldsModal ModalId="confirmModal" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick" NextModalId="@NextModalId"></FieldsModal>
@code {
[Parameter]
@@ -16,13 +16,27 @@
[Parameter]
public Func<Task>? OnConfirm { get; set; }
[Parameter]
public string ModalId { get; set; } = "confirmModal";
[Parameter]
public string? NextModalId { get; set; }
[Parameter]
public List<FieldDefinition>? ExtraFields { get; set; }
public List<FieldDefinition>? Fields { get; set; }
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (Message != null)
{
Fields = [new FieldDefinition { FieldType = FieldType.Label, Label = Message }];
if (ExtraFields != null)
{
Fields.AddRange(ExtraFields);
}
}
}
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Components;
namespace ColorControl.UI.Pages.Generic;
public abstract class CustomComponentBase : ComponentBase, IDisposable
{
public void Dispose()
{
GC.SuppressFinalize(this);
Blazor.ClearCurrentComponent(this);
}
protected override Task OnInitializedAsync()
{
Blazor.SetCurrentComponent(this);
return Task.CompletedTask;
}
}
@@ -1,10 +1,8 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@inject NavigationManager _navigationManager;
@rendermode Constants.RenderMode
@if (Fields == null)
{
@@ -48,7 +46,7 @@
<div class="mb-2">
<label class="form-label" for="@fieldId">@field.Label</label>
<select class="form-select" id="@fieldId" @bind="field.StringValue">
<select class="form-select" id="@fieldId" @bind="field.StringValue" @bind:after="() => FieldBindAfter(field)">
@if (field.ValueType != null)
{
@foreach (var item in Enum.GetValues(field.ValueType))
@@ -106,20 +104,19 @@
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-range" for="@fieldId">@field.Label (Current value: @field.IntValue)</label>
<input class="form-range" type="range" min="@((int)field.MinValue)" max="@((int)field.MaxValue)" @bind="field.IntValue" id="@fieldId">
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
<RangeInput TValue="int" @bind-Value="field.IntValue" Id="@fieldId" Label="@field.Label" Min="@((int)field.MinValue)" Max="@((int)field.MaxValue)" SubLabel="@field.SubLabel" />
}
}
@code {
[Parameter]
public List<FieldDefinition>? Fields { get; set; }
[Parameter]
public EventCallback<FieldDefinition> FieldValueChanged { get; set; }
private async Task FieldBindAfter(FieldDefinition field)
{
await FieldValueChanged.InvokeAsync(field);
}
}
@@ -1,13 +1,12 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@inject NavigationManager _navigationManager;
@rendermode Constants.RenderMode
@inject JSHelper jsHelper;
<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>
@@ -41,6 +40,9 @@
[Parameter]
public string? ModalId { get; set; }
[Parameter]
public string? NextModalId { get; set; }
[Parameter]
public string? ConfirmCaption { get; set; } = "Yes";
@@ -123,5 +125,10 @@
private async Task OnClose(MouseEventArgs e)
{
IsVisible = false;
if (!string.IsNullOrEmpty(NextModalId))
{
await jsHelper.OpenModal(NextModalId);
}
}
}
@@ -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 Constants.RenderMode
@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 position-fixed 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 void OnAfterRender(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 void 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);
}
}
@@ -0,0 +1,73 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@* @rendermode Constants.RenderMode *@
@typeparam TValue where TValue : struct, IComparable, IFormattable, IConvertible, IComparable<TValue>, IEquatable<TValue>
@if (Id != null)
{
<div class="mb-2">
<label class="form-label @(ExtraLabelStyling ?? "")" for="@Id">@Label (@Value@Unit)</label>
<input class="form-range" type="range" min="@(Values?.Min() ?? Min)" max="@(Values?.Max() ?? Max)" @bind="Value" id="@Id" @oninput="e => RangeOnChange(e)">
</div>
}
@code {
[Parameter]
public TValue? Value { get; set; }
[Parameter]
public string? Label { get; set; }
[Parameter]
public string? Unit { get; set; }
[Parameter]
public string? Id { get; set; }
[Parameter]
public TValue? Min { get; set; }
[Parameter]
public TValue? Max { get; set; }
[Parameter]
public List<TValue>? Values { get; set; }
[Parameter]
public EventCallback<TValue?> ValueChanged { get; set; }
[Parameter]
public Func<TValue?, string>? LabelStylingFunc { get; set; }
private string? ExtraLabelStyling;
protected override void OnParametersSet()
{
ExtraLabelStyling = LabelStylingFunc?.Invoke(Value);
Min ??= (TValue)Convert.ChangeType(0, typeof(TValue));
Max ??= (TValue)Convert.ChangeType(100, typeof(TValue));
}
private async Task RangeOnChange(ChangeEventArgs eventArgs)
{
if (typeof(TValue) == typeof(int))
{
Value = (TValue)Convert.ChangeType(int.Parse(eventArgs.Value?.ToString() ?? "0"), typeof(TValue));
}
else if (typeof(TValue) == typeof(uint))
{
Value = (TValue)Convert.ChangeType(uint.Parse(eventArgs.Value?.ToString() ?? "0"), typeof(TValue));
}
await ValueChanged.InvokeAsync(Value);
ExtraLabelStyling = LabelStylingFunc?.Invoke(Value);
}
}
@@ -0,0 +1,115 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@* @rendermode Constants.RenderMode *@
@typeparam TValue where TValue : struct, IComparable, IFormattable, IConvertible, IComparable<TValue>, IEquatable<TValue>
@if (Id != null)
{
<div class="mb-2">
<label class="form-label @(ExtraLabelStyling ?? "")" for="@Id">@Label: @GetValueDisplay()@Unit</label>
<input class="form-range" type="range" step="@Step" min="@(Values?.Min() ?? Min)" max="@(Values?.Max() ?? Max)" @bind="Value" id="@Id" @oninput="e => RangeOnChange(e)">
@if (SubLabel != null)
{
<div id="@(Id)_help" class="form-text">
@SubLabel
</div>
}
</div>
}
@code {
[Parameter]
public TValue Value { get; set; }
[Parameter]
public string? Label { get; set; }
[Parameter]
public string? SubLabel { get; set; }
[Parameter]
public string? Unit { get; set; }
[Parameter]
public string? UnitString { get; set; }
[Parameter]
public string? Id { get; set; }
[Parameter]
public TValue? Min { get; set; }
[Parameter]
public TValue? Max { get; set; }
[Parameter]
public TValue? Step { get; set; }
[Parameter]
public List<TValue>? Values { get; set; }
[Parameter]
public EventCallback<TValue> ValueChanged { get; set; }
[Parameter]
public Func<TValue, string>? LabelStylingFunc { get; set; }
[Parameter]
public Func<TValue, string>? CustomLabelFunc { get; set; }
private string? ExtraLabelStyling;
protected override void OnParametersSet()
{
ExtraLabelStyling = LabelStylingFunc?.Invoke(Value);
Min ??= (TValue)Convert.ChangeType(0, typeof(TValue));
Max ??= (TValue)Convert.ChangeType(100, typeof(TValue));
Step ??= (TValue)Convert.ChangeType(1, typeof(TValue));
}
private string? GetValueDisplay()
{
if (CustomLabelFunc != null)
{
return CustomLabelFunc(Value);
}
if (string.IsNullOrEmpty(UnitString))
{
return Value.ToString();
}
if (Value is uint uintValue)
{
return uintValue.ToUnitString(UnitString);
}
if (Value is int intValue)
{
return intValue.ToSignedUnitString(UnitString);
}
return Value.ToString();
}
private async Task RangeOnChange(ChangeEventArgs eventArgs)
{
if (typeof(TValue) == typeof(int))
{
Value = (TValue)Convert.ChangeType(int.Parse(eventArgs.Value?.ToString() ?? "0"), typeof(TValue));
}
else if (typeof(TValue) == typeof(uint))
{
Value = (TValue)Convert.ChangeType(uint.Parse(eventArgs.Value?.ToString() ?? "0"), typeof(TValue));
}
await ValueChanged.InvokeAsync(Value);
ExtraLabelStyling = LabelStylingFunc?.Invoke(Value);
}
}
@@ -1,63 +1,140 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@if (Label != null)
{
<div class="mb-2">
<label class="form-label" for="name">@Label</label>
<input class="form-control" type="text" readonly value="@Shortcut"
@onchange="(ChangeEventArgs e) => ShortcutOnChange(e)"
@onkeydown="ShortcutOnKeyDown"
@onkeyup="(KeyboardEventArgs e) => ShortcutOnKeyUp(e)"
@onfocus="ShortcutOnFocus"
@onblur="ShortcutOnBlur"/>
</div>
<div class="mb-2">
<label class="form-label" for="name">@Label</label>
<div class="input-group">
<input class="form-control" type="text" readonly value="@Shortcut"
@onchange="(ChangeEventArgs e) => ShortcutOnChange(e)"
@onkeydown="ShortcutOnKeyDown"
@onkeyup="(KeyboardEventArgs e) => ShortcutOnKeyUp(e)"
@onfocus="ShortcutOnFocus"
@onblur="ShortcutOnBlur"/>
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu">
@foreach (var modifier in Modifiers)
{
<li>
<a class="dropdown-item" @onclick="() => ModifierOnClick(modifier.Key)">
<input class="form-check-input me-2" type="checkbox" id="flexCheckChecked" checked="@modifier.Value" @onchange="(e) => ModifierKeyChanged(e, modifier.Key)">@modifier.Key
</a>
</li>
}
</ul>
</div>
</div>
}
@code {
[Parameter]
public string? Label { get; set; }
[Parameter]
public string? Label { get; set; }
[Parameter]
public string? Shortcut { get; set; }
[Parameter]
public string? Shortcut { get; set; }
[Parameter]
public EventCallback<string?> ShortcutChanged { get; set; }
[Parameter]
public EventCallback<string?> ShortcutChanged { get; set; }
private async Task ShortcutOnChange(ChangeEventArgs e)
{
Shortcut = e.Value?.ToString();
await ShortcutChanged.InvokeAsync(Shortcut);
}
private Dictionary<string, bool> Modifiers = new() {
{ "Shift", false },
{ "Control", false },
{ "Alt", false },
{ "Win", false }
};
private void ShortcutOnKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Tab")
{
return;
}
protected override void OnParametersSet()
{
LoadModifiers();
}
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
Shortcut = text;
}
private async Task ShortcutOnChange(ChangeEventArgs e)
{
Shortcut = e.Value?.ToString();
LoadModifiers();
await ShortcutChanged.InvokeAsync(Shortcut);
}
private async Task ShortcutOnKeyUp(KeyboardEventArgs e)
{
//KeyboardShortcutDispatcher.HandleKeyboardShortcutUp()
await ShortcutChanged.InvokeAsync(Shortcut);
}
private void ShortcutOnKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Tab")
{
return;
}
private void ShortcutOnFocus(FocusEventArgs e)
{
KeyboardShortcutDispatcher.IsActive = false;
}
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
Shortcut = text;
}
private void ShortcutOnBlur(FocusEventArgs e)
{
KeyboardShortcutDispatcher.IsActive = true;
}
private async Task ShortcutOnKeyUp(KeyboardEventArgs e)
{
//KeyboardShortcutDispatcher.HandleKeyboardShortcutUp()
await ShortcutChanged.InvokeAsync(Shortcut);
}
private void ShortcutOnFocus(FocusEventArgs e)
{
KeyboardShortcutDispatcher.IsActive = false;
}
private void ShortcutOnBlur(FocusEventArgs e)
{
KeyboardShortcutDispatcher.IsActive = true;
}
private void LoadModifiers()
{
foreach (var modifier in Modifiers.Keys)
{
Modifiers[modifier] = Shortcut?.Contains(modifier) == true;
}
}
private async Task ModifierKeyChanged(ChangeEventArgs e, string modifier)
{
var value = e.Value is bool boolValue && boolValue;
Modifiers[modifier] = value;
await SetModifiers();
}
private async Task SetModifiers()
{
var shortcut = Shortcut ?? "";
foreach (var modifier in Modifiers.Keys)
{
shortcut = shortcut.Replace(modifier, "").Trim().TrimStart(',');
}
shortcut = shortcut.Trim();
foreach (var modifier in Modifiers.Where(kv => kv.Value).Select(kv => kv.Key).Reverse())
{
if (shortcut.StartsWith("+"))
{
shortcut = $"{modifier} {shortcut}";
}
else
{
shortcut = $"{modifier}, {shortcut}";
}
}
Shortcut = shortcut;
await ShortcutChanged.InvokeAsync(Shortcut);
}
private async Task ModifierOnClick(string modifier)
{
Modifiers[modifier] = !Modifiers[modifier];
await SetModifiers();
}
}
@@ -0,0 +1,64 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@inject RpcUiClientService _rpcClientService
<div class="mb-2">
<label class="form-label" for="path">File/URI</label>
<div class="input-group mb-2">
<input class="form-control" id="path" type="text" @bind="Path" required @onblur="() => PathChanged.InvokeAsync(Path)" />
<button type="button" class="btn btn-primary" @onclick="SelectFileOnClick" disabled="@SelectingFile">
<span class="@(SelectingFile ? "spinner-border spinner-border-sm" : "")" aria-hidden="true"></span>
<span role="status">Select...</span>
</button>
</div>
<div id="selectFileHelp" class="form-text">
Selecting a file opens a file dialog outside of the browser on the system where the ColorControl application is running.
</div>
</div>
<div class="mb-2">
<label class="form-label" for="parameters">Parameters</label>
<input class="form-control" id="parameters" type="text" @bind="Parameters" @bind:after="() => ParametersChanged.InvokeAsync(Parameters)" />
</div>
@code {
[Parameter]
public string? Path { get; set; }
[Parameter]
public string? Parameters { get; set; }
[Parameter]
public EventCallback<string?> PathChanged { get; set; }
[Parameter]
public EventCallback<string?> ParametersChanged { get; set; }
private bool SelectingFile;
private async Task SelectFileOnClick(MouseEventArgs e)
{
SelectingFile = true;
try
{
var options = new RpcUiClientOptions
{
Timeout = 60000
};
var result = await _rpcClientService.CallWithOptionsAsync<string>("GameService", "SelectPath", options);
if (result != null)
{
Path = result;
await PathChanged.InvokeAsync(Path);
}
}
finally
{
SelectingFile = false;
}
}
}
@@ -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
@@ -12,94 +12,177 @@
@if (Label != null && Steps != null)
{
var index = 0;
<div class="mb-2">
<div class="d-flex align-items-center">
<label class="form-label" for="name">@Label</label>
<div class="d-flex ms-auto">
@if (NvPresets != null || SamsungPresets != null || CustomItems != null)
@if (NvPresets != null || SamsungPresets != null || LgPresets != null || CustomItems != null)
{
<div class="dropdown dropend mb-1">
<div class="dropdown dropstart mb-1">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">Add</button>
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
@if (NvPresets != null && NvPresetsEnabled)
{
<div class="mb-2">
<label class="form-label" for="nvPreset">NVIDIA preset</label>
<select class="form-select" id="nvPreset" @bind="NvPresetToAddId">
<option value="null">Select preset</option>
@foreach (var preset in NvPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
}
@if (SamsungPresets != null && SamsungPresetsEnabled)
{
<div class="mb-2">
<label class="form-label" for="samsungPreset">Samsung preset</label>
<select class="form-select" id="samsungPreset" @bind="SamsungPresetToAddId">
<option value="null">Select preset</option>
@foreach (var preset in SamsungPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
}
@if (LgPresets != null && LgPresetsEnabled)
{
<div class="mb-2">
<label class="form-label" for="lgPreset">LG preset</label>
<select class="form-select" id="lgPreset" @bind="LgPresetToAddId">
<option value="null">Select preset</option>
@foreach (var preset in LgPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
}
@if (CustomItems != null)
{
foreach (var customItem in CustomItems)
{
<div class="mb-2">
<label class="form-label" for="customItem">@customItem.Name</label>
<select class="form-select" id="customItem" @bind="customItem.SelectedItem">
<option value="null">Select item</option>
@foreach (var item in customItem.Items)
{
<option value="@item">@item</option>
}
</select>
</div>
<form class="dropdown-menu p-3 dropdown-menu-end" style="width: 21rem; height: 430px">
<ul class="nav nav-tabs" id="presetTab" role="tablist">
@{
var linkActive = "active";
}
}
@if (ActionItems != null)
{
foreach (var customItem in ActionItems)
@if (NvPresets != null && NvPresetsEnabled)
{
<div class="mb-2">
<label class="form-label" for="advancedCustomItem">@customItem.Name</label>
<select class="form-select" id="advancedCustomItem" @bind="customItem.SelectedActionName" @bind:after="() => AdvancedItemChanged(customItem)">
<option value="null">Select action</option>
@foreach (var keyValue in customItem.GroupedActions)
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="nvidia-tab" data-bs-toggle="tab" data-bs-target="#nvidia-tab-pane" type="button" role="tab" aria-controls="nvidia-tab-pane" aria-selected="true">NVIDIA</button>
</li>
linkActive = "";
}
@if (SamsungPresets != null && SamsungPresetsEnabled)
{
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="samsung-tab" data-bs-toggle="tab" data-bs-target="#samsung-tab-pane" type="button" role="tab" aria-controls="samsung-tab-pane" aria-selected="true">Samsung</button>
</li>
linkActive = "";
}
@if (LgPresets != null && LgPresetsEnabled)
{
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="lg-tab" data-bs-toggle="tab" data-bs-target="#lg-tab-pane" type="button" role="tab" aria-controls="lg-tab-pane" aria-selected="true">LG</button>
</li>
linkActive = "";
}
@if (CustomItems != null)
{
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="custom-tab" data-bs-toggle="tab" data-bs-target="#custom-tab-pane" type="button" role="tab" aria-controls="custom-tab-pane" aria-selected="true">Custom</button>
</li>
linkActive = "";
}
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="program-tab" data-bs-toggle="tab" data-bs-target="#program-tab-pane" type="button" role="tab" aria-controls="program-tab-pane" aria-selected="true">Program</button>
</li>
@if (ActionItems != null)
{
linkActive = "";
<li class="nav-item" role="presentation">
<button class="nav-link px-2 @(linkActive)" id="action-tab" data-bs-toggle="tab" data-bs-target="#action-tab-pane" type="button" role="tab" aria-controls="action-tab-pane" aria-selected="true">Action</button>
</li>
}
</ul>
<div class="tab-content" id="presetTabContent">
@{
var tabActive = "show active";
}
@if (NvPresets != null && NvPresetsEnabled)
{
<div class="tab-pane fade @(tabActive)" id="nvidia-tab-pane" role="tabpanel" aria-labelledby="nvidia-tab">
<div class="mt-3 mb-3">
<label class="form-label" for="nvPreset">NVIDIA preset</label>
<select class="form-select" multiple id="nvPreset" @onchange="NvPresetChange" style="height: 250px">
@foreach (var preset in NvPresets)
{
<optgroup label="@keyValue.Key">
@foreach (var item in keyValue.Value)
{
<option value="@item.Name">@item.Title</option>
}
</optgroup>
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</select>
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddNvPresetOnClick()" disabled="@(NvPresetsToAdd.Count == 0)">Add item(s)</button>
</div>
<FieldDefs Fields="ActionFields"></FieldDefs>
tabActive = "";
}
}
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddPresetOnClick()">Add items</button>
@if (SamsungPresets != null && SamsungPresetsEnabled)
{
<div class="tab-pane fade @(tabActive)" id="samsung-tab-pane" role="tabpanel" aria-labelledby="samsung-tab">
<div class="mt-3 mb-3">
<label class="form-label" for="samsungPreset">Samsung preset</label>
<select class="form-select" id="samsungPreset" @bind="SamsungPresetToAddId">
<option value="">Select preset</option>
@foreach (var preset in SamsungPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddSamsungPresetOnClick()">Add item</button>
</div>
tabActive = "";
}
@if (LgPresets != null && LgPresetsEnabled)
{
<div class="tab-pane fade @(tabActive)" id="lg-tab-pane" role="tabpanel" aria-labelledby="lg-tab">
<div class="mt-3 mb-3">
<label class="form-label" for="lgPreset">LG preset</label>
<select class="form-select" id="lgPreset" @bind="LgPresetToAddId">
<option value="">Select preset</option>
@foreach (var preset in LgPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddLgPresetOnClick()">Add item</button>
</div>
tabActive = "";
}
@if (CustomItems != null)
{
<div class="tab-pane fade @(tabActive)" id="custom-tab-pane" role="tabpanel" aria-labelledby="custom-tab">
@foreach (var customItem in CustomItems)
{
<div class="mt-3 mb-3">
<label class="form-label" for="customItem">@customItem.Name</label>
<select class="form-select" id="customItem" @bind="customItem.SelectedItem">
<option value="">Select item</option>
@foreach (var item in customItem.Items)
{
<option value="@item">@item</option>
}
</select>
</div>
}
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddCustomItemsOnClick()">Add item</button>
</div>
tabActive = "";
}
<div class="tab-pane fade @(tabActive)" id="program-tab-pane" role="tabpanel" aria-labelledby="program-tab">
<div class="mt-3 mb-3">
<StartProgramInput @bind-Path="StartProgramPath" @bind-Parameters="StartProgramParameters"></StartProgramInput>
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddStartProgramOnClick()">Add item</button>
</div>
@{
tabActive = "";
}
@if (ActionItems != null)
{
<div class="tab-pane fade @(tabActive)" id="action-tab-pane" role="tabpanel" aria-labelledby="action-tab">
<div class="mt-3 mb-3">
@foreach (var customItem in ActionItems)
{
<div class="mb-2">
<label class="form-label" for="advancedCustomItem">@customItem.Name</label>
<select class="form-select" id="advancedCustomItem" @bind="customItem.SelectedActionName" @bind:after="() => AdvancedItemChanged(customItem)">
<option value="">Select action</option>
@foreach (var keyValue in customItem.GroupedActions)
{
<optgroup label="@keyValue.Key">
@foreach (var item in keyValue.Value)
{
<option value="@item.Name">@item.Title</option>
}
</optgroup>
}
</select>
</div>
<FieldDefs Fields="ActionFields"></FieldDefs>
}
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddActionItemsOnClick()">Add item</button>
</div>
}
</div>
</form>
</div>
}
@@ -119,15 +202,18 @@
{
var stepIndex = index;
<button type="button" class="btn btn-outline-primary dropdown-toggle text-break text-wrap" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">@step.Raw</button>
<button type="button" class="btn btn-outline-primary dropdown-toggle text-break text-wrap" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" @onclick="() => PresetStepClick(step)">@step.Raw</button>
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
<div class="mb-2">
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" title="Move left" disabled="@(stepIndex == 0)" @onclick="() => MoveLeftOnClick(stepIndex)">&lt;</button>
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" title="Move right" disabled="@(stepIndex == PresetSteps.Count - 1)" @onclick="() => MoveRightOnClick(stepIndex)">&gt;</button>
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" @onclick="() => RemoveOnClick(stepIndex)">Remove</button>
</div>
<div class="mb-2">
<label class="form-label" for="delay">Delay (@(step.Delay)ms)</label>
<input class="form-range" id="delay" type="range" min="0" max="10000" step="50" @bind="step.Delay" @bind:after="() => AfterDelayChanged()">
</div>
<RangeInput TValue="int" @bind-Value="step.Delay" Id="delay" Label="Delay" Unit="ms" Max="10000" @bind-Value:after="() => AfterDelayChanged()" />
@if (ActionFieldsEdit != null)
{
<FieldDefs Fields="ActionFieldsEdit" FieldValueChanged="ActionFieldValueChanged"></FieldDefs>
}
</form>
index++;
@@ -166,6 +252,8 @@
[Parameter]
public bool LgPresetsEnabled { get; set; } = true;
private PresetStep? CurrentPresetStep;
public class ActionStepItem
{
public string Name { get; private set; }
@@ -210,7 +298,6 @@
private List<SamsungPreset>? SamsungPresets { get; set; }
private List<LgPreset>? LgPresets { get; set; }
private int? NvPresetToAddId { get; set; }
private int? SamsungPresetToAddId { get; set; }
private int? LgPresetToAddId { get; set; }
@@ -219,14 +306,27 @@
private string? RawText;
private List<FieldDefinition>? ActionFields;
private List<FieldDefinition>? ActionFieldsEdit;
protected override async Task OnParametersSetAsync()
private string? StartProgramPath;
private string? StartProgramParameters;
private List<int> NvPresetsToAdd = [];
protected override void OnParametersSet()
{
if (Steps != null)
{
var stepIndex = CurrentPresetStep != null ? PresetSteps.IndexOf(CurrentPresetStep) : -1;
PresetSteps.Clear();
PresetSteps.AddRange(Steps.Select(s => PresetStep.Parse(s)));
RawText = string.Join(", ", PresetSteps);
if (stepIndex > -1 && stepIndex < PresetSteps.Count)
{
CurrentPresetStep = PresetSteps[stepIndex];
}
}
}
@@ -260,14 +360,106 @@
await EmitSteps();
}
private async Task MoveLeftOnClick(int index)
{
if (PresetSteps.Count > index && index > 0)
{
var step = PresetSteps[index];
PresetSteps.RemoveAt(index);
PresetSteps.Insert(index - 1, step);
}
await EmitSteps();
}
private async Task MoveRightOnClick(int index)
{
if (PresetSteps.Count > index + 1)
{
var step = PresetSteps[index];
PresetSteps.RemoveAt(index);
PresetSteps.Insert(index + 1, step);
}
await EmitSteps();
}
private async Task AfterDelayChanged()
{
await EmitSteps();
}
private async Task AddNvPresetOnClick()
{
NvPresetsToAdd.ForEach(id => AddPreset(NvPresets, "NvPreset", id));
await EmitSteps();
}
private async Task AddSamsungPresetOnClick()
{
AddPreset(SamsungPresets, "SamsungPreset", SamsungPresetToAddId);
await EmitSteps();
}
private async Task AddLgPresetOnClick()
{
AddPreset(LgPresets, "LgPreset", LgPresetToAddId);
await EmitSteps();
}
private async Task AddCustomItemsOnClick()
{
if (CustomItems != null && Steps != null)
{
foreach (var customItem in CustomItems)
{
if (string.IsNullOrWhiteSpace(customItem.SelectedItem))
{
continue;
}
PresetSteps.Add(PresetStep.Parse(customItem.SelectedItem));
}
}
await EmitSteps();
}
private async Task AddActionItemsOnClick()
{
if (ActionItems != null && Steps != null)
{
foreach (var customItem in ActionItems)
{
if (string.IsNullOrWhiteSpace(customItem.SelectedActionName))
{
continue;
}
var stepString = customItem.GetStepString(ActionFields);
PresetSteps.Add(PresetStep.Parse(stepString));
}
}
await EmitSteps();
}
private async Task AddStartProgramOnClick()
{
AddStartProgram();
await EmitSteps();
}
private async Task AddPresetOnClick()
{
AddPreset(NvPresets, "NvPreset", NvPresetToAddId);
NvPresetsToAdd.ForEach(id => AddPreset(NvPresets, "NvPreset", id));
AddPreset(SamsungPresets, "SamsungPreset", SamsungPresetToAddId);
AddPreset(LgPresets, "LgPreset", LgPresetToAddId);
@@ -300,6 +492,8 @@
}
}
AddStartProgram();
await EmitSteps();
}
@@ -315,6 +509,22 @@
PresetSteps.Add(PresetStep.Parse($"{type}({preset.name})"));
}
private void AddStartProgram()
{
if (string.IsNullOrWhiteSpace(StartProgramPath))
{
return;
}
var path = StartProgramPath;
if (!string.IsNullOrWhiteSpace(StartProgramParameters))
{
path += $"; {string.Join("; ", StartProgramParameters.Split(' '))}";
}
PresetSteps.Add(PresetStep.Parse($"StartProgram({path})"));
}
private async Task EmitSteps()
{
if (Steps != null)
@@ -341,7 +551,7 @@
await EmitSteps();
}
private async Task RawTextOnChange(ChangeEventArgs e)
private void RawTextOnChange(ChangeEventArgs e)
{
RawText = e.Value?.ToString();
}
@@ -399,4 +609,43 @@
return field;
}
private void NvPresetChange(ChangeEventArgs eventArgs)
{
if (eventArgs.Value is IEnumerable<string> items)
{
NvPresetsToAdd.Clear();
NvPresetsToAdd.AddRange(items.Select(i => int.Parse(i)));
}
}
private void PresetStepClick(PresetStep step)
{
CurrentPresetStep = step;
var action = ActionItems?.SelectMany(ai => ai.Actions).FirstOrDefault(a => a.Name.Equals(step.ActionName, StringComparison.OrdinalIgnoreCase));
if (action == null)
{
ActionFieldsEdit = null;
return;
}
var field = CreateField(action);
field.StringValue = step.ActionValue;
ActionFieldsEdit = [field];
}
private async Task ActionFieldValueChanged(FieldDefinition field)
{
if (CurrentPresetStep == null)
{
return;
}
CurrentPresetStep.ActionValue = field.StringValue;
await EmitSteps();
}
}
@@ -0,0 +1,13 @@
namespace ColorControl.UI.Pages.Generic;
public abstract class SummaryComponentBase : CustomComponentBase
{
protected abstract Task Refresh();
public async Task RefreshExternal()
{
await Refresh();
await InvokeAsync(StateHasChanged);
}
}
@@ -1,14 +1,26 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@if (Triggers != null)
{
if (Triggers.Count < 2)
{
<div>
<label class="form-label">Add trigger:</label>
<button class="btn btn-primary btn-sm" type="button" @onclick="() => Triggers.Add(new PresetTrigger())">Add</button>
</div>
}
foreach (var trigger in Triggers)
{
<div class="clearfix">
<label class="form-label">Trigger @(Triggers.IndexOf(trigger) + 1)</label>
<button class="btn btn-danger btn-sm float-end" type="button" @onclick="() => Triggers.Remove(trigger)">Remove</button>
</div>
<div class="mb-2">
<label class="form-label" for="triggerType">Trigger type</label>
<select class="form-select" id="triggerType" @bind="trigger.Trigger">
@@ -18,7 +30,7 @@
}
</select>
</div>
<FlagsInput Label="Conditions" Value="@((uint)trigger.Conditions)" Labels="ConditionLabels" Col="6" ValueChanged="ConditionsChanged"></FlagsInput>
<FlagsInput Label="Conditions" Value="@((uint)trigger.Conditions)" Labels="ConditionLabels" Col="6" ValueChanged="value => ConditionsChanged(value, trigger)"></FlagsInput>
<div class="mb-2">
<label class="form-label" for="includedProcesses">Included processes</label>
<input class="form-control" type="text" @bind="trigger.IncludedProcessesAsString" />
@@ -27,6 +39,16 @@
<label class="form-label" for="excludedProcesses">Excluded processes</label>
<input class="form-control" type="text" @bind="trigger.ExcludedProcessesAsString" />
</div>
<div class="mb-2">
<label class="form-label" for="connectedDisplays">Connected displays Regex</label>
<input class="form-control" type="text" @bind="trigger.ConnectedDisplaysRegex"
title="The preset will be executed only if the supplied Regular Expression matches the device name of at least one of the displays that are connected at the moment the trigger is fired." />
</div>
@if (Triggers.IndexOf(trigger) < Triggers.Count - 1)
{
<div class="border-bottom mb-2"></div>
}
}
}
@@ -36,14 +58,8 @@
private List<string> ConditionLabels = Utils.GetDescriptions<PresetConditionType>(fromValue: 1).ToList();
public void ConditionsChanged(uint value)
public void ConditionsChanged(uint value, PresetTrigger trigger)
{
if (Triggers == null || !Triggers.Any())
{
return;
}
var trigger = Triggers.First();
trigger.Conditions = (PresetConditionType)value;
}
}
+36 -7
View File
@@ -1,13 +1,16 @@
@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
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
<PageTitle>Home</PageTitle>
@@ -24,10 +27,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 +50,8 @@ else
@code {
private Config? Config;
private List<ModuleDto>? Modules;
private InfoDto? Info;
private bool IsUpdateAvailable;
protected override async Task OnInitializedAsync()
{
@@ -57,4 +62,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) });
}
}
}
}
+73 -3
View File
@@ -1,13 +1,13 @@
@page "/info"
@using ColorControl.Shared.Common;
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject IHttpContextAccessor _httpContextAccessor
<PageTitle>Logging</PageTitle>
@@ -20,7 +20,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 +75,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,17 +1,16 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.UI.Services
@using Microsoft.AspNetCore.Components.Web
@using ColorControl.Shared.Common
@using ColorControl.UI.Components.Pages.Generic
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="deviceModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal" id="lgDeviceModal" tabindex="-1">
<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>
@@ -73,7 +72,7 @@
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
@@ -143,10 +142,22 @@
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOff" @bind="Device.Options.PowerOffByWindows">
<label class="form-check-label" for="checkWindowsPowerOff">Use Windows power settings to power off</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterSessionUnlock" @bind="Device.Options.PowerOnAfterSessionUnlock">
<label class="form-check-label" for="checkPowerOnAfterSessionUnlock">Power on when unlocking the pc</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffAfterSessionLock" @bind="Device.Options.PowerOffAfterSessionLock">
<label class="form-check-label" for="checkPowerOffAfterSessionLock">Power off when locking the pc</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkUseSecureConnection" @bind="Device.Options.UseSecureConnection">
<label class="form-check-label" for="checkUseSecureConnection">Use secure connection</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkUseNewHandshake" @bind="Device.Options.UseNewHandshake">
<label class="form-check-label" for="checkUseNewHandshake">Enable webOS 26 compatibility</label>
</div>
</div>
</div>
</div>
@@ -157,6 +168,10 @@
</form>
</div>
<div class="modal-footer">
@if (Device?.IsCustom == true)
{
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#removeDeviceModal">Remove</button>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
</div>
@@ -164,6 +179,8 @@
</div>
</div>
<ConfirmModal ModalId="removeDeviceModal" Title="Remove device" Message="Are you sure you want to remove this device?" OnConfirm="DeviceOnRemoveConfirmed" NextModalId="lgDeviceModal" />
@code {
private LgDeviceDto? _device;
@@ -177,7 +194,7 @@
private bool? IsTestConnectionSuccessful;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (_device != Device)
{
@@ -200,6 +217,21 @@
}
}
private async Task DeviceOnRemoveConfirmed()
{
if (Device == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("LgService", "RemoveDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
private async Task TestConnectionClick(MouseEventArgs e)
{
IsTestingConnection = true;
@@ -1,10 +1,10 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@@ -28,7 +28,7 @@
private FieldDefinition? Field;
private FieldDefinition? DeviceField;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (Action == null)
{
@@ -1,19 +1,16 @@
@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
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.LG.LgPreset>
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@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>
@@ -74,7 +71,7 @@
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Steps" @bind-Steps="Preset.steps" CustomItems="CustomItems" ActionItems="CustomActions" LgPresetsEnabled="false" TPreset="LgPreset"></StepsInput>
<StepsInput Label="Steps" @bind-Steps="Preset.Steps" CustomItems="CustomItems" ActionItems="CustomActions" LgPresetsEnabled="false" TPreset="LgPreset"></StepsInput>
<div class="mb-2">
<label class="form-label" for="description">Description</label>
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
@@ -114,7 +111,7 @@
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (PresetParam == null)
{
@@ -1,10 +1,10 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal Title="Lg settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
@@ -4,12 +4,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services;
@using ColorControl.UI.Components.Pages.RemoteControl
@using ColorControl.UI.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inherits SummaryComponentBase
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
@@ -22,6 +25,9 @@
<div class="d-flex align-items-center">
<h1>LG controller</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#rcCanvasRight" aria-controls="rcCanvasRight">
Remote control
</button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
Buttons
@@ -64,7 +70,18 @@
</div>
</div>
<LgDevicePage Device="CurrentDevice" />
<div class="offcanvas offcanvas-end" style="width: 280px" data-bs-scroll="true" tabindex="-1" id="rcCanvasRight" aria-labelledby="rcCanvasRightRightLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="rcCanvasRightRightLabel">Remote control</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<GenericRcPage ServiceName="LgService" Device="CurrentDevice" TDevice="LgDeviceDto" TPreset="LgPreset" TButtonEnum="ButtonType" Presets="RcPresets"></GenericRcPage>
</div>
</div>
<LgDevicePage Device="CurrentDevice" AfterApply="AfterDeviceApplied" />
<LgPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@@ -84,7 +101,7 @@ else
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#lgDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
@@ -119,7 +136,7 @@ else
{
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#lgDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
@@ -155,10 +172,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">@preset.name <small class="text-muted">@GetDeviceName(preset)</small></h5>
@@ -168,13 +189,23 @@ 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-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="#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>
</span>
}
</div>
</div>
@@ -207,15 +238,23 @@ else
private InvokableActionDto<LgPreset>? CurrentInvokableAction;
private PresetOrder PresetOrder;
private List<LgPreset> RcPresets = [LgPresets.InStartPreset, LgPresets.EzAdjustPreset];
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await Refresh();
}
protected override async Task Refresh()
{
Config = await _rpcClientService.CallAsync<LgServiceConfigDto>("LgService", "GetConfig");
await RefreshPresets();
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)
@@ -234,18 +273,18 @@ else
await _rpcClientService.CallAsync<bool>("LgService", "ApplyPresetWithId", preset.id);
}
private async Task PresetOnEditClick(LgPreset preset)
private void PresetOnEditClick(LgPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnCloneClick(LgPreset preset)
private void PresetOnCloneClick(LgPreset preset)
{
CurrentPreset = new LgPreset(preset);
CurrentPreset.SetCloneValues();
}
private async Task PresetOnDeleteClick(LgPreset preset)
private void PresetOnDeleteClick(LgPreset preset)
{
CurrentPreset = preset;
}
@@ -262,17 +301,17 @@ else
StateHasChanged();
}
private async Task DeviceOnClick(LgDeviceDto device)
private void DeviceOnClick(LgDeviceDto device)
{
CurrentDevice = device;
}
private async Task AddDeviceOnClick()
private void AddDeviceOnClick()
{
CurrentDevice = new LgDeviceDto { IsCustom = true };
}
private async Task AddPresetOnClick()
private void AddPresetOnClick()
{
CurrentPreset = new LgPreset();
}
@@ -283,7 +322,13 @@ else
StateHasChanged();
}
private async Task RefreshDevicesOnClick(MouseEventArgs e)
private async Task AfterDeviceApplied(LgDeviceDto device)
{
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
StateHasChanged();
}
private async Task RefreshDevicesOnClick()
{
IsRefreshDisabled = true;
try
@@ -306,7 +351,7 @@ else
StateHasChanged();
}
private async Task ActionOnclick(InvokableActionDto<LgPreset> action)
private void ActionOnclick(InvokableActionDto<LgPreset> action)
{
CurrentInvokableAction = action;
}
@@ -1,13 +1,11 @@
@page "/logging"
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<PageTitle>Logging</PageTitle>
@@ -147,6 +145,7 @@ else
"WARN" => "warning",
"ERROR" => "danger",
"FATAL" => "secondary",
_ => "info"
};
public bool Show(NLog.LogLevel level) => LogLevel >= level;
@@ -154,10 +153,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()
@@ -197,7 +199,7 @@ else
await _rpcClientService.CallAsync<bool>("LoggingService", "SetLogLevel", value);
}
private async Task LogLevelFilterInput(ChangeEventArgs e)
private void LogLevelFilterInput(ChangeEventArgs e)
{
var value = e.Value as string ?? NLog.LogLevel.Debug.Name;
logLevelFilter = NLog.LogLevel.FromString(value);
@@ -1,14 +1,11 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal modal-lg" id="ditheringModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
@@ -89,8 +86,8 @@
</div>
<div class="modal-footer">
<span class="me-auto">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="SetDitherRegistryKeyOnClick">Set dither registry key</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="RestartDriverOnClick">Restart driver</button>
<button type="button" class="btn btn-secondary" @onclick="SetDitherRegistryKeyOnClick">Set dither registry key</button>
<button type="button" class="btn btn-secondary" @onclick="RestartDriverOnClick">Restart driver</button>
</span>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
@@ -1,17 +1,16 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject JSHelper jsHelper;
<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>
@@ -27,17 +26,23 @@
<div class="modal-body">
<form class="row g-1">
@if (ProfileNames != null)
@if (Profiles != null)
{
<div class="mb-2">
<label class="form-label" for="profileName">Profile</label>
<select class="form-select" id="profileName" @bind="CurrentProfileName" @bind:after="ProfileOnChange">
@foreach (var profileName in ProfileNames)
@foreach (var profile in Profiles)
{
<option value="@profileName">@profileName</option>
<option value="@profile.Name" title="@string.Join(", ", profile.Apps)">@profile.Name</option>
}
</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">
@@ -59,10 +64,8 @@
{
if (NvSettingConstants.RangeDriverSettings.Contains(setting.SettingId) && setting.Values != null)
{
<div class="mb-2">
<label class="form-label @(setting.Value != setting.DefaultValue && setting.Value != NvSettingConstants.UnsetDwordValue ? "text-warning fw-bold" : "")" for="@setting.SettingId">@setting.SettingText (@setting.Value)</label>
<input class="form-range" type="range" min="@setting.Values.Min(v => v.Value)" max="@setting.Values.Max(v => v.Value)" @bind="setting.Value" id="@setting.SettingId">
</div>
<RangeInput TValue="uint" @bind-Value="setting.Value" Id="@setting.SettingId.ToString()" Label="@setting.SettingText" Values="setting.Values.Select(v => v.Value).ToList()"
LabelStylingFunc="value => RangeLabelStyleFunc(value, setting)" />
}
else if (setting.Values != null)
{
@@ -98,7 +101,7 @@
else
{
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => IsVisible = false">Close</button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="ApplyClick">Save</button>
<button type="button" class="btn btn-primary" @onclick="ApplyClick">Save</button>
}
</div>
</div>
@@ -132,11 +135,13 @@
private List<GroupedSettings>? Groups { get; set; }
private List<NvSettingItemDto>? DriverSettings { get; set; }
private List<string>? ProfileNames { get; set; }
private List<NvProfile>? Profiles { get; set; }
private string? CurrentProfileName { get; set; }
private bool IsVisible { get; set; }
private bool OnlyFoundPrograms = true;
protected override async Task OnParametersSetAsync()
{
if (_preset != Preset)
@@ -207,6 +212,9 @@
{
Preset.UpdateDriverSettings(changedSettings);
await jsHelper.CloseModal("driverSettingsModal");
await jsHelper.OpenModal("presetModal");
return;
}
@@ -232,13 +240,28 @@
await LoadDriverSettings();
}
private async Task OnlyFoundProgramsOnChange()
{
Profiles = await _rpcClientService.CallAsync<List<NvProfile>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
}
private async Task OnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
IsVisible = true;
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames");
Profiles ??= await _rpcClientService.CallAsync<List<NvProfile>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
await LoadDriverSettings();
}
}
private void RangeOnChange(ChangeEventArgs eventArgs, NvSettingItemDto s)
{
s.Value = uint.Parse(eventArgs.Value?.ToString() ?? "0");
}
private string RangeLabelStyleFunc(uint value, NvSettingItemDto setting)
{
return value != setting.DefaultValue && value != NvSettingConstants.UnsetDwordValue ? "text-warning fw-bold" : "";
}
}
@@ -0,0 +1,186 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper;
<div class="modal modal-lg" id="gpuSettingsModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" @onfocus="OnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset != null ? $"GPU settings for {Preset.IdOrName}" : $"GPU settings for {GpuInfo?.Name ?? "?"}")</h5>
@if (Preset == null)
{
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
}
else
{
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#presetModal" aria-label="Close" @onclick="() => IsVisible = false"></button>
}
</div>
<div class="modal-body">
<form class="row g-1">
@if (OverclockSettings != null && GpuInfos?.Any() == true)
{
<div class="mb-2">
<label class="form-label" for="gpu">GPU</label>
<select class="form-select" id="gpu" @bind="OverclockSettings.PCIIdentifier">
@foreach (var gpu in GpuInfos)
{
<option value="@gpu.PCIIdentifier">@gpu.Name</option>
}
</select>
</div>
}
@if (OverclockSettings != null && GpuInfo != null)
{
<div class="mb-2">
<label class="form-label" for="ocType">Overclock method</label>
<select class="form-select" id="ocType" @bind="OverclockSettings.Type">
@foreach (var ocType in Enum.GetValues<NvGpuOcType>())
{
<option value="@ocType.ToString()">@ocType.GetDescription()</option>
}
</select>
</div>
@if (OverclockSettings.Type is NvGpuOcType.Curve or NvGpuOcType.Offset)
{
<RangeInput TValue="int" Min="@GpuInfo.CorrectedMinCoreDeltaInMHz" Max="@GpuInfo.MaxCoreDeltaInMHz" Step="@GpuInfo.MinCoreStepInMHz" @bind-Value="OverclockSettings.GraphicsOffsetMHz" Id="coreOffset" Label="Core offset" UnitString="MHz"
SubLabel="@($"Min: {GpuInfo.MinCoreDeltaInMHz.ToSignedUnitString()}, Max: {GpuInfo.MaxCoreDeltaInMHz.ToSignedUnitString()}")" />
<RangeInput TValue="int" Min="@GpuInfo.MinMemoryDeltaInMHz" Max="@GpuInfo.MaxMemoryDeltaInMHz" @bind-Value="OverclockSettings.MemoryOffsetMHz" Id="memoryOffset" Label="Memory offset" UnitString="MHz"
SubLabel="@($"Min: {GpuInfo.MinMemoryDeltaInMHz.ToSignedUnitString()}, Max: {GpuInfo.MaxMemoryDeltaInMHz.ToSignedUnitString()}")" />
}
@if (OverclockSettings.Type is NvGpuOcType.Curve or NvGpuOcType.BoostLock)
{
<RangeInput TValue="uint" Min="@GpuInfo.MinCurveFrequencyInMHz" Max="@GpuInfo.MaxCurveFrequencyInMHz" Step="@((uint)GpuInfo.MinCoreStepInMHz)" @bind-Value="OverclockSettings.MaximumFrequencyMHz" Id="maxCoreFrequency" Label="Maximum core frequency" UnitString="MHz"
SubLabel="@($"Setting this value to anything else than '0' will limit the maximum frequency of the curve. Min: {GpuInfo.MinCurveFrequencyInMHz.ToSignedUnitString()}, Max: {GpuInfo.MaxCurveFrequencyInMHz.ToSignedUnitString()}")" />
<RangeInput TValue="uint" Min="@GpuInfo.MinCurveVoltageInMv" Max="@GpuInfo.MaxCurveVoltageInMv" Step="5" @bind-Value="OverclockSettings.MaximumVoltageMv" Id="maxCoreVoltage" Label="Maximum core voltage" UnitString="mv"
SubLabel="@($"Setting this value to anything else than '0' will limit the maximum voltage (and also the frequency) of the curve. Min: {GpuInfo.MinCurveVoltageInMv.ToUnitString("mv")}, Max: {GpuInfo.MaxCurveVoltageInMv.ToUnitString("mv")}")" />
}
@if (OverclockSettings.Type != NvGpuOcType.None)
{
<RangeInput TValue="uint" @bind-Value="OverclockSettings.VoltageBoostPercent" Id="voltageBoost" Label="Voltage boost" UnitString="%" />
@if (GpuInfo.DefaultPowerInMilliWatts > 0 && GpuInfo.MaxPowerInMilliWatts > 0 && GpuInfo.MinPowerInMilliWatts > 0 && GpuInfo.MaxPowerInMilliWatts > GpuInfo.MinPowerInMilliWatts)
{
<RangeInput TValue="uint" Min="@GpuInfo.MinimumPowerInPCM" Max="@GpuInfo.MaximumPowerInPCM" @bind-Value="OverclockSettings.PowerPCM" Id="powerLimitWatts" Label="Power limit"
CustomLabelFunc='value => $"{((uint)(OverclockSettings.PowerPCM / 100000f * GpuInfo.DefaultPowerInMilliWatts)).ToKiloUnitString(units: "W")} ({OverclockSettings.PowerPCM.ToKiloUnitString(units: "%")})"'
SubLabel="@($"Min: {GpuInfo.MinPowerInMilliWatts.ToKiloUnitString(units: "W")}, Max: {GpuInfo.MaxPowerInMilliWatts.ToKiloUnitString(units: "W")}")" />
}
else
{
<RangeInput TValue="uint" Min="@GpuInfo.MinimumPowerInPCM" Max="@GpuInfo.MaximumPowerInPCM" @bind-Value="OverclockSettings.PowerPCM" Id="powerLimitPercentage" Label="Power limit"
CustomLabelFunc='value => $"{OverclockSettings.PowerPCM.ToKiloUnitString(units: "%")}"'
SubLabel="@($"Min: {GpuInfo.MinimumPowerInPCM.ToKiloUnitString(units: "%")}, Max: {GpuInfo.MaximumPowerInPCM.ToKiloUnitString(units: "%")}")" />
}
}
}
</form>
</div>
<div class="modal-footer">
@if (Preset == null)
{
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Apply</button>
}
else
{
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => IsVisible = false">Close</button>
<button type="button" class="btn btn-primary" @onclick="ApplyClick">Save</button>
}
</div>
</div>
</div>
</div>
@code {
[Parameter]
public NvGpuOcSettings? OverclockSettings { get; set; }
[Parameter]
public NvGpuInfoDto? GpuInfo { get; set; }
private NvPreset? _preset;
[Parameter]
public NvPreset? Preset { get; set; }
[Parameter]
public Func<NvGpuInfoDto, Task>? AfterApply { get; set; }
private List<NvGpuInfoDto>? GpuInfos;
private bool IsVisible { get; set; }
protected override async Task OnParametersSetAsync()
{
if (!IsVisible)
{
return;
}
if (_preset != Preset)
{
_preset = Preset;
}
if (GpuInfo == null)
{
GpuInfos = await _rpcClientService.CallAsync<List<NvGpuInfoDto>>("NvService", "GetGpuInfos");
GpuInfo = GpuInfos?.FirstOrDefault();
}
if (Preset != null)
{
OverclockSettings = Preset.ocSettings.FirstOrDefault();
}
if (GpuInfo != null && OverclockSettings == null)
{
OverclockSettings = GpuInfo.OverclockSettings;
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (OverclockSettings == null)
{
return;
}
if (Preset != null)
{
Preset.UpdateOverclockingSetting(OverclockSettings);
await jsHelper.CloseModal("gpuSettingsModal");
await jsHelper.OpenModal("presetModal");
return;
}
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyOverclocking", new List<NvGpuOcSettings> { OverclockSettings });
if (AfterApply != null && GpuInfo != null)
{
await AfterApply.Invoke(GpuInfo);
}
}
private void OnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
IsVisible = true;
}
}
}
@@ -0,0 +1,108 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using static ColorControl.Shared.Common.NestedItemsBuilder
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper
<div class="modal modal-lg" id="infoModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Info</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="row g-1">
<div class="row">
@if (InfoItems != null)
{
@RenderItems(InfoItems)
}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@code {
private bool _visible;
[Parameter]
public bool Visible { get; set; }
private string? SelectedDisplayId { get; set; }
private List<NestedItem>? InfoItems;
protected override async Task OnParametersSetAsync()
{
if (_visible != Visible)
{
_visible = Visible;
if (Visible)
{
await GetSettingsAsync();
}
}
}
protected async Task GetSettingsAsync()
{
InfoItems = await _rpcClientService.CallAsync<List<NestedItem>>("NvService", "GetInfoItems");
if (InfoItems != null)
{
InfoItems.ForEach(i => i.Expanded = true);
}
}
RenderFragment<List<NestedItem>> RenderItems => items =>
@<div class="accordion accordion-flush" id="@items.GetHashCode()">
@foreach(var item in items)
{
@if (item.NestedItems.Count > 0)
{
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button py-2 @(item.Expanded ? "show" : "collapsed")" type="button" data-bs-toggle="collapse" data-bs-target="#@item.GetHashCode()" aria-expanded="false" aria-controls="flush-collapseOne" @onclick="() => OnItemClick(item)">
@item.Value
</button>
</h2>
<div id="@item.GetHashCode()" class="accordion-collapse collapse @(item.Expanded ? "show" : "")" data-bs-parent="#@items.GetHashCode()">
<div class="accordion-body">
@if (item.Expanded)
{
@RenderItems(item.NestedItems)
}
</div>
</div>
</div>
}
else
{
<div class="mt-1" style="margin-left: 1.25rem">
@item.Value
</div>
}
}
</div>
;
private async Task OnItemClick(NestedItem item)
{
await Task.Delay(400);
var classes = await jsHelper.GetElementClassName(item.GetHashCode().ToString());
item.Expanded = classes?.Contains(" show") == true;
}
}
@@ -1,18 +1,21 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.DisplayInfo
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@using ColorControl.Shared.Common
@using ColorControl.UI.Generics
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject JSHelper jsHelper
@inject ProtectedLocalStorage _localStorage
<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>
@@ -42,50 +45,62 @@
}
<div class="accordion accordion-flush" id="presetAccordion">
@if (!Preset.IsDisplayPreset)
{
<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; visibility: hidden" type="checkbox">
}
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
Display
</button>
</span>
</h2>
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<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; visibility: hidden" type="checkbox">
}
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
Display
</button>
</span>
</h2>
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
@if (!Preset.IsDisplayPreset)
{
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.primaryDisplay" id="primaryDisplay">
<label class="form-check-label" for="primaryDisplay">
Primary display
</label>
</div>
@if (displayPresets != null)
}
@if (displayPresets != null)
{
if (!Preset.IsDisplayPreset)
{
<div class="mb-2">
<label class="form-label" for="mode">Display</label>
<select class="form-select" id="mode" @bind="Preset.DisplayId" disabled="@(Preset.primaryDisplay)">
@foreach (var display in displayPresets)
{
<option value="@display.DisplayId">@display.displayName</option>
}
<select class="form-select" id="mode" @bind="Preset.DisplayId" disabled="@Preset.primaryDisplay">
@foreach (var display in displayPresets)
{
<option value="@display.DisplayId">@display.displayName</option>
}
</select>
</div>
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.DisplayConfig.IsPrimary" id="setAsPrimaryDisplay" disabled="@(Preset.primaryDisplay)">
<label class="form-check-label" for="setAsPrimaryDisplay">
Set display as primary display
</label>
</div>
}
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.DisplayConfig.IsPrimary" id="setAsPrimaryDisplay" disabled="@(Preset.IsDisplayPreset && displayPresets.Count == 1)">
<label class="form-check-label" for="setAsPrimaryDisplay">
Set display as primary display
</label>
</div>
}
<div class="mb-2">
<label class="form-label" for="connectivity">Connectivity</label>
<select class="form-select" id="scaling" @bind="Preset.DisplayConfig.Connectivity">
@foreach (var connectivity in Enum.GetValues<Shared.Contracts.DisplayConnectivity>())
{
<option value="@connectivity.ToString()">@connectivity.ToString()</option>
}
</select>
</div>
</div>
</div>
}
</div>
@if (resolutions != null)
{
@@ -107,7 +122,7 @@
{
<div class="mb-2">
<label class="form-label" for="activeResolution">Active resolution</label>
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution">
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution" @bind:after="() => AfterResolutionChange()">
<option value="0x0">Not set</option>
@foreach (var resolution in resolutions)
{
@@ -117,7 +132,7 @@
</div>
<div class="mb-2">
<label class="form-label" for="virtualResolution">Virtual resolution</label>
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution">
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution" @bind:after="() => AfterResolutionChange()">
<option value="0x0">Not set</option>
@foreach (var resolution in resolutions)
{
@@ -168,7 +183,7 @@
{
<div class="mb-2">
<label class="form-label" for="refreshRate">Refresh Rate</label>
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate">
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate" @bind:after="() => AfterRefreshRateChange()">
@foreach (var refreshRate in refreshRates)
{
<option value="@refreshRate.ToString()">@refreshRate.ToString() Hz</option>
@@ -180,6 +195,34 @@
</div>
</div>
}
<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.DpiScaling.ApplyScaling" id="applyDpiScaling">
}
<button class="accordion-button collapsed py-2 @(Preset.DpiScaling.ApplyScaling ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#dpiScalingSettings">
Dpi Scaling
</button>
</span>
</h2>
<div id="dpiScalingSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="dpiScaling">Percentage</label>
<select class="form-select" id="dpiScaling" @bind="Preset.DpiScaling.Percentage">
@foreach (var dpiScale in Shared.Native.CCD.DpiValues)
{
<option value="@dpiScale.ToString()">@dpiScale.ToString()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
@@ -254,14 +297,8 @@
</h2>
<div id="colorEnhancements" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="digitalVibrance">Digital vibrance: @Preset.ColorEnhancementSettings.DigitalVibranceLevel</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.ColorEnhancementSettings.DigitalVibranceLevel" id="digitalVibrance">
</div>
<div class="mb-2">
<label class="form-label" for="hue">Hue: @Preset.ColorEnhancementSettings.HueAngle°</label>
<input class="form-range" type="range" min="0" max="359" @bind="Preset.ColorEnhancementSettings.HueAngle" id="hue">
</div>
<RangeInput TValue="int" @bind-Value="Preset.ColorEnhancementSettings.DigitalVibranceLevel" Id="digitalVibrance" Label="Digital vibrance" />
<RangeInput TValue="int" @bind-Value="Preset.ColorEnhancementSettings.HueAngle" Id="hue" Label="Hue" Unit="°" Max="359" />
</div>
</div>
</div>
@@ -281,7 +318,7 @@
<div id="driverSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-3">
<button class="btn btn-secondary btn-sm" type="button" data-bs-target="#driverSettingsModal" data-bs-toggle="modal" @onclick="DriverSettingsOnClick">Open driver settings</button>
<button class="btn btn-secondary btn-sm me-1" type="button" data-bs-target="#driverSettingsModal" data-bs-toggle="modal" @onclick="DriverSettingsOnClick">Open driver settings</button>
<span>@Preset.driverSettings.Count settings configured</span>
</div>
</div>
@@ -355,7 +392,7 @@
</div>
<div class="mb-2">
<label class="form-label" for="bits">Bit depth</label>
<select class="form-select" id="bits" @bind="Preset.NvDitherBits">
<select class="form-select" id="bits" @bind="Preset.NvDitherBits">
@foreach (var bits in Enum.GetValues<NvDitherBits>())
{
<option value="@bits.ToString()">@bits.GetDescription()</option>
@@ -403,10 +440,8 @@
}
</select>
</div>
<div class="mb-2">
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
</div>
<NullableRangeInput TValue="int" @bind-Value="Preset.SDRBrightness" Id="sdrBrightness" Label="SDR brightness" Unit="%" />
</div>
</div>
</div>
@@ -448,6 +483,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,13 +494,213 @@
</div>
</div>
}
@if (ColorProfiles?.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 ColorProfiles)
{
<option value="@colorProfile.Name">@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 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.DdcSettings.ApplyDdc" id="applyDdc">
}
<button class="accordion-button collapsed py-2 @(Preset.DdcSettings.ApplyDdc ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#ddcSettings">
DDC settings
</button>
</span>
</h2>
<div id="ddcSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
@if (!Preset.DdcSettings.Settings.Any())
{
<div>
<label class="form-label">No settings found. Click to add:</label>
<button class="btn btn-primary btn-sm" type="button" @onclick="() => Preset.DdcSettings.Settings.Add(new DdcSetting())">Add</button>
</div>
}
@foreach (var ddcSetting in Preset.DdcSettings.Settings)
{
<div class="mb-2">
<label class="form-label" for="vcpCode">VCP code</label>
<select class="form-select" id="vcpCode" @bind="ddcSetting.VcpHexCode" @bind:after="() => AfterVcpChanged(ddcSetting)">
<option value="">Not selected</option>
@foreach (var vcpCodeKeyValue in VcpCodes.CodeList)
{
<option value="@vcpCodeKeyValue.Key">@($"{vcpCodeKeyValue.Key}: {(string.IsNullOrEmpty(vcpCodeKeyValue.Value) ? "Unknown" : vcpCodeKeyValue.Value)}")</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="ddcValue">Value</label>
<input class="form-control" type="number" @bind="ddcSetting.Value" id="ddcValue">
@if (ddcSetting.MaxValue != 0)
{
<div id="ddcValue_help" class="form-text">
Max. value: @ddcSetting.MaxValue
</div>
}
</div>
@if (!Preset.IsDisplayPreset)
{
<div class="mb-2">
<label class="form-label" for="ddcValueChangeType">Value change type</label>
<select class="form-select" id="ddcValueChangeType" @bind="ddcSetting.ValueChangeType">
@foreach (var changeType in Enum.GetValues<DdcSetting.ValueChangeTypes>())
{
<option value="@changeType">@changeType.GetDescription()</option>
}
</select>
</div>
}
}
@if (!string.IsNullOrEmpty(DdcErrorMessage))
{
<div class="alert alert-danger align-items-center" role="alert">
<div>
@DdcErrorMessage
</div>
</div>
}
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.DdcSettings.RemoveOutOfRangeOsd" id="ddcRemoveOsd">
<label class="form-check-label" for="ddcRemoveOsd">
Try to remove "Out of range" OSD
</label>
</div>
</div>
</div>
</div>
@if (ShowOverclocking)
{
<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.applyOverclocking" id="applyOverclocking">
}
<button class="accordion-button collapsed py-2 @(Preset.applyOverclocking ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#overclocking">
Overclocking
</button>
</span>
</h2>
<div id="overclocking" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-3">
<button class="btn btn-secondary btn-sm me-1" type="button" data-bs-target="#gpuSettingsModal" data-bs-toggle="modal" @onclick="GpuSettingsOnClick">Open GPU settings</button>
@if (Preset.ocSettings.Any())
{
<span>Overclocking settings configured:</span>
@foreach (var ocSetting in Preset.ocSettings)
{
<div>@ocSetting.ToString()</div>
}
}
else
{
<span>No overclocking settings configured</span>
}
</div>
</div>
</div>
</div>
}
@if (!Preset.IsDisplayPreset)
{
<div class="accordion accordion-flush" id="presetAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Triggers
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<TriggerInput Triggers="Preset.Triggers"></TriggerInput>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Steps" @bind-Steps="Preset.Steps" NvPresetsEnabled="false" TPreset="NvPreset"></StepsInput>
<div class="mb-2">
<label class="form-label" for="description">Description</label>
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
</div>
</div>
</div>
</div>
</div>
}
</div>
}
</form>
</div>
<div class="modal-footer">
@if (Preset?.IsDisplayPreset == true)
{
<input class="form-check-input" type="checkbox" @bind="ApplyImmediately" id="directApply" @bind:after="ApplyImmediatelyAfter">
<label class="form-check-label" for="directApply">
Apply changes immediately
</label>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
<button type="submit" class="btn btn-primary" disabled="@(!IsValid())" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
</div>
</div>
</div>
@@ -482,6 +718,9 @@
[Parameter]
public Func<NvPreset, Task>? SetDriverSettingsPreset { get; set; }
[Parameter]
public Func<NvPreset, Task>? SetGpuSettingsPreset { get; set; }
private string? selectedActiveResolution;
private string? selectedVirtualResolution;
private string? selectedRefreshRate;
@@ -489,40 +728,108 @@
private List<VirtualResolution>? resolutions;
private List<Rational>? refreshRates;
private List<NvPreset>? displayPresets;
private List<ColorProfileDto>? ColorProfiles;
private string WasValidated = "";
private bool IsVisible;
private bool IsDriverSettingsOpened;
private bool IsGpuSettingsOpened;
private bool ShowOverclocking;
private bool ApplyImmediately;
private NvPreset? OriginalPreset;
private bool Applying;
private bool Queued;
private bool SkipNextApply;
private string? DdcErrorMessage;
protected override async Task OnParametersSetAsync()
{
if (IsDriverSettingsOpened && Preset != null)
if (!IsVisible)
{
return;
}
Preset = PresetParam == null ? null : PresetParam.CloneWithId();
if ((IsDriverSettingsOpened || IsGpuSettingsOpened) && Preset != null)
{
return;
}
ApplyImmediately = false;
Preset = PresetParam?.CloneWithId();
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
if (Preset?.IsDisplayPreset == true)
{
Preset = displayPresets.FirstOrDefault(p => p.DisplayId == Preset.DisplayId) ?? Preset;
}
if (Preset != null && PresetParam != null)
{
resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("NvService", "GetAvailableResolutionsV2", Preset);
refreshRates = await _rpcClientService.CallAsync<List<Rational>>("NvService", "GetAvailableRefreshRatesV2", Preset);
if (Preset.IsDisplayPreset && !Preset.DdcSettings.Settings.Any())
{
Preset.DdcSettings.Settings.Add(new DdcSetting());
}
}
selectedActiveResolution = Preset?.DisplayConfig.Resolution?.GetActiveString();
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
if (Preset?.IsDisplayPreset != true)
var deviceName = Preset?.IsDisplayPreset == true ? Preset.DisplayId : Preset?.DisplayId ?? displayPresets?.FirstOrDefault()?.DisplayId;
if (Preset != null)
{
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
ColorProfiles = await _rpcClientService.CallAsync<List<ColorProfileDto>>("ColorProfileService", "GetAllDisplayColorProfiles", deviceName);
OriginalPreset = Preset.Clone();
}
ApplyImmediately = await _localStorage.TryGet<bool>($"{nameof(NvPreset)}_{nameof(ApplyImmediately)}");
ShowOverclocking = true;
}
protected override void OnAfterRender(bool firstRender)
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// if (IsVisible && Preset?.IsDisplayPreset == false)
// {
// Preset.UpdateAutoApplySettings(keepChanges: true);
// }
await DoApplyImmediately();
}
private async Task DoApplyImmediately()
{
if (SkipNextApply)
{
SkipNextApply = false;
return;
}
if (IsVisible && OriginalPreset != null && Preset?.IsDisplayPreset == true)
{
if (Applying)
{
Queued = true;
return;
}
do
{
Applying = true;
Queued = false;
Preset.UpdateAutoApplySettings(OriginalPreset, true);
if (ApplyImmediately && Preset.HasApplicableSettings)
{
await ApplyDisplayPreset();
}
OriginalPreset = Preset.Clone();
Applying = false;
}
while (Queued && !SkipNextApply);
}
}
private async Task ApplyClick(MouseEventArgs e)
@@ -538,6 +845,100 @@
return;
}
Preset.ApplyNovideoSettings = Preset.NovideoSettings.ApplyClamp;
if (Preset.IsDisplayPreset)
{
if (OriginalPreset != null)
{
Preset.UpdateAutoApplySettings(OriginalPreset, true);
}
if (Preset.HasApplicableSettings)
{
Preset.UpdateAutoApplySettings(PresetParam);
await ApplyDisplayPreset();
}
}
else
{
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdatePreset", Preset);
if (!result)
{
return;
}
}
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
await jsHelper.CloseModal("presetModal");
IsVisible = false;
}
private async Task ApplyDisplayPreset()
{
var result = await _rpcClientService.CallGenericBoolResultAsync("NvService", "ApplyPresetWithResult", Preset);
}
private void HdmiSettingsOnInput(ChangeEventArgs e)
{
Preset!.applyHdmiSettings = true;
}
private Task DriverSettingsOnClick(MouseEventArgs e)
{
IsDriverSettingsOpened = true;
SetDriverSettingsPreset?.Invoke(Preset!);
return Task.CompletedTask;
}
private Task GpuSettingsOnClick(MouseEventArgs e)
{
IsGpuSettingsOpened = true;
SetGpuSettingsPreset?.Invoke(Preset!);
return Task.CompletedTask;
}
private void ModalOnFocus(FocusEventArgs e)
{
if (IsVisible && IsDriverSettingsOpened)
{
IsDriverSettingsOpened = false;
StateHasChanged();
}
if (IsVisible && IsGpuSettingsOpened)
{
IsGpuSettingsOpened = false;
StateHasChanged();
}
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
}
private async Task AfterResolutionChange()
{
if (Preset == null)
{
return;
}
var newResolution = resolutions?.FirstOrDefault(r => r.GetActiveString() == selectedActiveResolution);
if (newResolution != null)
{
@@ -555,64 +956,53 @@
Preset.DisplayConfig.Resolution = new VirtualResolution(0, 0);
}
await DoApplyImmediately();
}
private async Task AfterRefreshRateChange()
{
if (Preset == null)
{
return;
}
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
if (newRefreshRate != null)
{
Preset.DisplayConfig.RefreshRate = newRefreshRate;
}
Preset.ApplyNovideoSettings = Preset.NovideoSettings.ApplyClamp;
await DoApplyImmediately();
}
if (Preset.IsDisplayPreset)
private async Task ApplyImmediatelyAfter()
{
await _localStorage.SetAsync($"{nameof(NvPreset)}_{nameof(ApplyImmediately)}", ApplyImmediately);
}
private async Task AfterVcpChanged(DdcSetting ddcSetting)
{
SkipNextApply = true;
if (Preset?.IsDisplayPreset != true || Preset.DisplayId == null)
{
Preset.UpdateAutoApplySettings(PresetParam);
return;
}
await ApplyDisplayPreset();
var result = await _rpcClientService.CallAsync<GenericResult<VcpInfo>>("NvService", "GetVcpInfo", Preset.DisplayId, ddcSetting.VcpCode);
SkipNextApply = true;
if (result.HasResult)
{
DdcErrorMessage = null;
ddcSetting.Value = result.Result.Value;
ddcSetting.MaxValue = result.Result.MaxValue;
}
else
{
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdatePreset", Preset);
DdcErrorMessage = result.ErrorMessages.FirstOrDefault();
ddcSetting.Value = 0;
ddcSetting.MaxValue = 0;
}
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
}
private async Task ApplyDisplayPreset()
{
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyPreset", Preset);
}
private void HdmiSettingsOnInput(ChangeEventArgs e)
{
Preset!.applyHdmiSettings = true;
}
private async Task DriverSettingsOnClick(MouseEventArgs e)
{
IsDriverSettingsOpened = true;
SetDriverSettingsPreset?.Invoke(Preset!);
}
private void ModalOnFocus(FocusEventArgs e)
{
if (IsVisible && IsDriverSettingsOpened)
{
IsDriverSettingsOpened = false;
StateHasChanged();
}
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
}
}
@@ -1,14 +1,14 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@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>
@@ -26,7 +26,7 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Config.ShowOverclocking" id="showOverclocking">
<label class="form-check-label" for="showOverclocking">
Show overclocking
Show and allow overclocking
</label>
</div>
<div class="form-check">
@@ -2,13 +2,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inherits SummaryComponentBase
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
@@ -18,6 +20,8 @@
<NvidiaSettings />
<NvidiaDithering Visible="DitheringVisible" />
<NvidiaDriverSettings Preset="CurrentDriverSettingsPreset" />
<NvidiaGpuSettings Preset="CurrentGpuSettingsPreset" GpuInfo="CurrentGpuInfo" />
<NvidiaInfo Visible="InfoVisible" />
<div class="d-flex align-items-center">
<h1>NVIDIA controller</h1>
@@ -29,6 +33,7 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#ditheringModal" @onclick="() => DitheringVisible = true">Test dithering</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#driverSettingsModal" @onclick="() => CurrentDriverSettingsPreset = null">Driver settings</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#infoModal" @onclick="() => InfoVisible = true">Info</a></li>
</ul>
</div>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
@@ -39,7 +44,7 @@
<h2>Displays</h2>
<NvidiaPreset PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" SetDriverSettingsPreset="SetDriverSettingsPreset" />
<NvidiaPreset PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" SetDriverSettingsPreset="SetDriverSettingsPreset" SetGpuSettingsPreset="SetGpuSettingsPreset" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@if (DisplayPresets == null)
@@ -49,31 +54,98 @@
else
{
<div class="row mb-3">
<div class="col">
@if (!DisplayPresets.Any())
{
<span>No displays found</span>
}
@foreach (var preset in DisplayPresets)
{
<div class="card mb-3">
<div class="card-header">
@preset.displayName
@if (preset.DisplayConfig.IsPrimary == true)
{
<span class="ms-2 badge text-bg-primary" title="Primary display">Primary</span>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
Settings
</button>
@if (!DisplayPresets.Any())
{
<span>No displays found</span>
}
@foreach (var preset in DisplayPresets)
{
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@preset.displayName
@if (preset.DisplayConfig.IsPrimary == true)
{
<span class="ms-2 badge text-bg-primary" title="Primary display">Primary</span>
}
</span>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
Settings
</button>
</div>
</div>
<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>
<small class="text-muted">Scale: @preset.DpiScaling.ToString()</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>
}
@if (preset.driverSettings?.Any() == true)
{
<br />
@RenderPresetSettings(preset)
}
</div>
</div>
}
</div>
</div>
}
</div>
}
<h2>Display Adapters</h2>
@if (GpuInfos == null)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
@if (!GpuInfos.Any())
{
<span>No display adapters found</span>
}
@foreach (var gpuInfo in GpuInfos)
{
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@gpuInfo.Name
</span>
@if (NvConfig?.ShowOverclocking == true)
{
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#gpuSettingsModal" @onclick="() => GpuSettingsOnClick(gpuInfo)">
Settings
</button>
</div>
}
</div>
<div class="card-body">
<small class="text-body">GPU: @gpuInfo.GpuFrequencyInKhz.ToKiloUnitString() @@ @gpuInfo.VoltageInMv mV, @Utils.ToUnitString(values: [gpuInfo.GpuTemperature, gpuInfo.HotSpotTemperature])</small>
<small class="text-body">Memory: @gpuInfo.GpuFrequencyInKhz.ToKiloUnitString(), @Utils.ToUnitString(values: [gpuInfo.MemoryTemperature])</small>
<small class="text-body">Power: @(gpuInfo.PowerInWatts)W</small>
<small class="text-body"><br/>Overclock info: @gpuInfo.OverclockSettings</small>
</div>
</div>
</div>
}
</div>
}
@@ -99,10 +171,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">
@@ -113,34 +189,72 @@ 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-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="#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">@RenderPresetSettings(preset)</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>
}
@if (preset.DdcSettings.ApplyDdc && preset.DdcSettings.Settings.Count > 0)
{
<li class="list-group-item p-1">DDC: @preset.DdcSettings.Settings[0]</li>
}
</ul>
</small>
<small class="text-muted">@preset.shortcut</small>
</a>
</span>
}
</div>
</div>
@@ -150,17 +264,32 @@ else
@code {
private List<NvPreset>? Presets;
private List<NvPreset>? DisplayPresets;
private List<NvGpuInfoDto>? GpuInfos;
private NvServiceConfig? NvConfig;
private NvPreset? CurrentPreset;
private NvPreset? CurrentDriverSettingsPreset;
private NvPreset? CurrentGpuSettingsPreset;
private NvGpuInfoDto? CurrentGpuInfo;
private bool DitheringVisible;
private bool InfoVisible;
private List<NvSettingItemDto>? DriverSettings;
private PresetOrder PresetOrder;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await Refresh();
}
protected override async Task Refresh()
{
NvConfig = await _rpcClientService.CallAsync<NvServiceConfig>("NvService", "GetConfig");
await RefreshPresets();
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
GpuInfos = await _rpcClientService.CallAsync<List<NvGpuInfoDto>>("NvService", "GetGpuInfos");
}
protected override async Task OnAfterRenderAsync(bool firstTime)
@@ -174,23 +303,57 @@ else
}
}
RenderFragment<NvPreset> RenderPresetSettings => preset =>
@<div class="dropdown">
<span role="button" class="text-decoration-underline dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" @onclick="() => DriverSettingsOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
<small>@preset.driverSettings.Count() driver settings changed</small>
</span>
<form class="dropdown-menu" style="width: max-content">
@if (DriverSettings != null)
{
<ul class="list-group p-0">
@foreach (var settingId in preset.driverSettings.Keys)
{
var setting = DriverSettings.FirstOrDefault(s => s.SettingId == settingId);
if (setting == null)
{
continue;
}
var settingValue = setting.Values.FirstOrDefault(v => v.Value == preset.driverSettings[settingId]);
if (settingValue == null) {
continue;
}
<li class="list-group-item py-0 px-1"><small>@setting.Name: @settingValue.ValueName</small></li>
}
</ul>
}
</form>
</div>
;
private async Task PresetOnClick(NvPreset preset)
{
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
if (result)
{
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
}
private async Task PresetOnEditClick(NvPreset preset)
private void PresetOnEditClick(NvPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnCloneClick(NvPreset preset)
private void PresetOnCloneClick(NvPreset preset)
{
CurrentPreset = new NvPreset(preset);
CurrentPreset.name = preset.name;
CurrentPreset.SetCloneValues();
}
private async Task PresetOnDeleteClick(NvPreset preset)
private void PresetOnDeleteClick(NvPreset preset)
{
CurrentPreset = preset;
}
@@ -207,12 +370,12 @@ else
StateHasChanged();
}
private async Task DisplayPresetOnClick(NvPreset preset)
private void DisplayPresetOnClick(NvPreset preset)
{
CurrentPreset = preset;
}
private async Task AddPresetOnClick()
private void AddPresetOnClick()
{
CurrentPreset = new NvPreset();
}
@@ -230,10 +393,26 @@ else
StateHasChanged();
}
private async Task SetDriverSettingsPreset(NvPreset preset)
private Task SetDriverSettingsPreset(NvPreset preset)
{
CurrentDriverSettingsPreset = preset;
StateHasChanged();
return Task.CompletedTask;
}
private Task SetGpuSettingsPreset(NvPreset preset)
{
CurrentGpuSettingsPreset = preset;
StateHasChanged();
return Task.CompletedTask;
}
private void GpuSettingsOnClick(NvGpuInfoDto gpuInfo)
{
CurrentGpuInfo = gpuInfo;
CurrentGpuSettingsPreset = null;
}
private async Task PresetOrderChanged()
@@ -248,4 +427,9 @@ else
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
private async Task DriverSettingsOnClick(NvPreset preset)
{
DriverSettings ??= await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", Type.Missing, true);
}
}
+75 -22
View File
@@ -1,15 +1,18 @@
@page "/options"
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject NavigationManager _navigationManager
@inject NotificationService _notificationService
@inject JSHelper _jsHelper;
@inject IHttpContextAccessor _httpContextAccessor
<PageTitle>Options</PageTitle>
@@ -49,11 +52,11 @@ else
<label class="form-label" for="elevationMethod">
Elevation Method
</label>
<select class="form-select" @bind="elevationMethod" id="elevationMethod" @oninput="ElevationMethodInput" aria-describedby="elevationMethodHelp">
<option value="0">None</option>
<option value="1">Run as administrator</option>
<option value="2">Use service</option>
<option value="3">Use elevated process</option>
<select class="form-select" @bind="config.ElevationMethod" id="elevationMethod" @oninput="ElevationMethodInput" aria-describedby="elevationMethodHelp">
@foreach (var method in Enum.GetValues<ElevationMethod>())
{
<option value="@method">@method.GetDescription()</option>
}
</select>
<div id="elevationMethodHelp" class="form-text">
Elevation is needed in some cases where ColorControl needs administrator rights.
@@ -80,9 +83,26 @@ else
</div>
<div class="mb-3">
<label class="form-label" for="webUiPort">
Web UI port
Web UI port (use 0 for automatic assignment)
</label>
@if (IsChangingUiPort)
{
<div class="alert alert-warning mb-2" role="alert">
Changing the UI port. If the UI is not reloaded correctly, close this window and relaunch the UI.
</div>
}
<input class="form-control" type="number" @bind="config.UiPort" id="webUiPort" @bind:after="UiPortBlur">
@if (config.CurrentUiPort > 0)
{
<div id="uiPortHelp" class="form-text">Currently listening on port @config.CurrentUiPort</div>
}
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.UiAllowRemoteConnections" id="uiAllowRemoteConnections" @bind:after="UiAllowRemoteConnectionsChange">
<label class="form-check-label" for="uiAllowRemoteConnections">
Web UI: Allow remote connections
</label>
<div id="uiAllowRemoteConnectionsHelp" class="form-text">Allows or disallows remote connections to the UI. Only enable this if you want to access the UI from another device (eg. your phone) than the system ColorControl is running on</div>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.MinimizeOnClose" id="minimizeOnClose" @oninput="MinimizeOnCloseInput">
@@ -133,13 +153,9 @@ else
<div class="card-body">
<h5 class="card-title">Advanced</h5>
<form>
<div class="mb-3 form-check">
<label class="form-range" for="processMonitorPollingInterval">
Polling interval of process monitor (@config.ProcessMonitorPollingInterval milliseconds)
</label>
<input class="form-range" type="range" min="50" max="10000" step="50" @bind="config.ProcessMonitorPollingInterval" id="processMonitorPollingInterval" aria-describedby="processMonitorPollingIntervalHelp" @bind:after="PollingIntervalOnInput">
<div id="processMonitorPollingIntervalHelp" class="form-text">Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load</div>
</div>
<RangeInput TValue="int" Min="50" Max="10000" Step="50" @bind-Value="config.ProcessMonitorPollingInterval" Id="processMonitorPollingInterval" Label="Polling interval of process monitor" Unit=" milliseconds"
SubLabel="Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load"
/>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.UseRawInput" id="useRawInput" aria-describedby="useRawInputHelp" @oninput="UseRawInputInput">
<label class="form-check-label" for="useRawInput">
@@ -186,12 +202,13 @@ else
@code {
private Config? config;
private string elevationMethod = "0";
private bool IsEmbeddedBrowser;
private bool IsChangingUiPort;
protected override async Task OnInitializedAsync()
{
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
elevationMethod = ((int)config.ElevationMethod).ToString();
IsEmbeddedBrowser = _httpContextAccessor.HttpContext?.Request.Headers["user-agent"].ToString().Contains("ColorControlEmbedded") == true;
}
private async Task SaveConfig()
@@ -252,6 +269,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()
@@ -297,10 +319,8 @@ else
private async Task ElevationMethodInput(ChangeEventArgs e)
{
var value = e.Value != null ? (ElevationMethod)int.Parse((string)e.Value) : ElevationMethod.None;
var value = e.Value != null ? Enum.Parse<ElevationMethod>((string)e.Value) : ElevationMethod.None;
await _rpcClientService.CallAsync<bool>("OptionsService", "SetElevationMethod", value);
config!.ElevationMethod = value;
}
private async Task UiTypeOnInput(ChangeEventArgs e)
@@ -315,6 +335,39 @@ else
private async Task UiPortBlur()
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiPort", config!.UiPort);
if (config == null)
{
return;
}
IsChangingUiPort = true;
if (config.UiPort > 0 || IsEmbeddedBrowser)
{
await NavigateToNewPort(config.UiPort, 5000);
}
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiPort", config.UiPort);
}
private async Task UiAllowRemoteConnectionsChange()
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiAllowRemoteConnections", config!.UiAllowRemoteConnections);
}
private async Task NavigateToNewPort(int port, int timeout = 0)
{
var uri = new UriBuilder(_navigationManager.Uri);
uri.Port = port;
var newUri = uri.Uri.ToString();
if (timeout == 0)
{
_navigationManager.NavigateTo(uri.ToString(), true);
}
else
{
await _jsHelper.NavigateTo(newUri, timeout);
}
}
}
@@ -0,0 +1,120 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Contracts.RemoteControl
@using ColorControl.UI.Services
@using ColorControl.Shared.Common
@* @rendermode Constants.RenderMode *@
@typeparam TPreset where TPreset : PresetBase
@typeparam TButtonEnum where TButtonEnum : struct, Enum
@inject RpcUiClientService _rpcClientService
<div class="modal" id="rcButtonModal" data-bs-backdrop="false" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">RC button</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Button = null"></button>
</div>
<div class="modal-body">
<form class="row g-1">
@if (Button == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" id="name" type="text" @bind="Button.Name" required />
</div>
<div class="mb-2">
<label class="form-label" for="button">Button</label>
<select class="form-select" id="button" @bind="Button.Button">
@foreach (var buttonType in Enum.GetValues<TButtonEnum>())
{
<option value="@buttonType.ToString()">@buttonType.GetDescription()</option>
}
</select>
</div>
@if (Presets?.Any() == true)
{
<div class="mb-2">
<label class="form-label" for="preset">Preset</label>
<select class="form-select" id="preset" @bind="PresetName">
<option value="">Select preset</option>
@foreach (var preset in Presets)
{
<option value="@preset.name">@preset.name</option>
}
</select>
</div>
}
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-bs-dismiss="modal" @onclick="RemoveClick">Remove</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => Button = null">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
[Parameter]
public BaseRcButtonDto<TPreset, TButtonEnum>? ButtonParam { get; set; }
[Parameter]
public Func<BaseRcButtonDto<TPreset, TButtonEnum>, Task>? AfterApply { get; set; }
[Parameter]
public List<TPreset>? Presets { get; set; }
private BaseRcButtonDto<TPreset, TButtonEnum>? Button { get; set; }
private string? PresetName;
protected override void OnParametersSet()
{
if (ButtonParam == null)
{
return;
}
Button = new BaseRcButtonDto<TPreset, TButtonEnum>(ButtonParam);
PresetName = Button.Preset?.name;
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Button == null || ButtonParam == null)
{
return;
}
Button.Preset = (Presets?.FirstOrDefault(p => p.name == PresetName))!;
ButtonParam.Update(Button);
if (AfterApply != null)
{
await AfterApply.Invoke(ButtonParam);
}
}
private async Task RemoveClick(MouseEventArgs e)
{
if (Button == null || ButtonParam == null)
{
return;
}
if (AfterApply != null)
{
await AfterApply.Invoke(null!);
}
}
}
@@ -0,0 +1,318 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Base
@using ColorControl.Shared.Contracts.RemoteControl
@using ColorControl.UI.Services
@using ColorControl.Shared.Common
@using System.Globalization
@* @rendermode Constants.RenderMode *@
@typeparam TDevice where TDevice : BaseDeviceDto
@typeparam TPreset where TPreset : PresetBase, new()
@typeparam TButtonEnum where TButtonEnum : struct, Enum
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper
<style>
.myhover {
border-radius: 25px;
width: @(ButtonSize)%;
aspect-ratio: 1;
opacity: 0.2;
border-width: 0;
}
.myhover:hover {
opacity: 0.4;
background-color: white;
}
</style>
<GenericRcButtonPage ButtonParam="CurrentButton" AfterApply="AfterApplyButton" TPreset="TPreset" TButtonEnum="TButtonEnum" Presets="Presets"></GenericRcButtonPage>
<div>
<div>
<h5>@(Device?.Name == null ? $"No device selected" : Device.Name)</h5>
</div>
<div>
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Remotes == null)
{
<div>Loading...</div>
}
else
{
<select class="form-select" id="remoteControl" @bind="SelectedRemoteName" @bind:after="RemoteChanged">
@foreach (var remote in Remotes)
{
<option id="@remote.Name">@remote.Name</option>
}
</select>
if (RemoteController == null)
{
<div>Select a remote</div>
}
else
{
@if (AllowEditing)
{
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
}
@* <p>Left: @CurrentX, Top: @CurrentY</p> *@
<div style="position: relative" @onclick="Button_Clicked" @oncontextmenu:preventDefault="true" @oncontextmenu="e => NewRcButtonRightClick(e)">
<img src="remotes/@RemoteController.ImageFileName" id="rcImage" class="w-100" />
@foreach (var button in RemoteController.Buttons)
{
<button class="myhover" type="button"
style="position: absolute; top: @(button.Top.ToString(CultureInfo.InvariantCulture))%; left: @(button.Left.ToString(CultureInfo.InvariantCulture))%"
@onclick="() => RcButtonOnClick(button)" @oncontextmenu="e => RcButtonRightClick(e, button)" @oncontextmenu:preventDefault="true" @oncontextmenu:stopPropagation>
</button>
}
</div>
}
}
</form>
</div>
</div>
@code {
private TDevice? _device;
[Parameter]
public TDevice? Device { get; set; }
[Parameter]
public Func<TDevice, Task>? AfterApply { get; set; }
[Parameter]
public string? ServiceName { get; set; }
[Parameter]
public List<TPreset>? Presets { get; set; }
private string WasValidated = "";
private bool IsVisible;
private string? SelectedRemoteName;
private BaseRcDto<TPreset, TButtonEnum>? RemoteController;
private List<BaseRcDto<TPreset, TButtonEnum>>? Remotes;
private double CurrentX, CurrentY;
private RectDimension? ImageDimension;
private BaseRcButtonDto<TPreset, TButtonEnum>? CurrentButton;
private double ButtonSize = 13;
private bool AllowEditing = false;
protected override async void OnParametersSet()
{
if (ServiceName != null && (_device != Device || Device == null))
{
_device = Device;
Remotes = await _rpcClientService.CallAsync<List<BaseRcDto<TPreset, TButtonEnum>>>(ServiceName, "GetRemotes");
if (Remotes?.Any() == true)
{
RemoteController = Remotes.First();
SelectedRemoteName = RemoteController.Name;
StateHasChanged();
}
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (RemoteController == null || ServiceName == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>(ServiceName, "UpdateRemote", RemoteController);
// if (AfterApply != null)
// {
// await AfterApply.Invoke(Device);
// }
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return true; // !string.IsNullOrEmpty(Device?.Name) && (Device?.IsCustom != true || !string.IsNullOrEmpty(Device?.IpAddress));
}
private async Task RcButtonOnClick(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (ServiceName == null)
{
return;
}
var rcButton = button.Button;
var preset = button.Preset;
if (preset == null)
{
preset = new TPreset();
preset.name = rcButton.ToString();
preset.SetSteps([rcButton.ToString()]);
}
await _rpcClientService.CallAsync<bool>(ServiceName, "ApplyPreset", preset);
}
public async Task Button_Clicked(MouseEventArgs e)
{
if (ImageDimension == null)
{
ImageDimension = await jsHelper.GetElementDimensions("rcImage");
}
if (ImageDimension != null)
{
CurrentX = Math.Round(((double)e.OffsetX / ImageDimension.Width) * 100, 4);
CurrentY = Math.Round(((double)e.OffsetY / ImageDimension.Height) * 100, 4);
return;
}
CurrentX = e.OffsetX;
CurrentY = e.OffsetY;
}
private async Task RcButtonRightClick(MouseEventArgs e, BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (!AllowEditing)
{
return;
}
CurrentButton = button;
if (e.ShiftKey)
{
AlignItemsHorizontally(button);
return;
}
if (e.CtrlKey)
{
AlignItemsVertically(button);
return;
}
await jsHelper.OpenModal("rcButtonModal");
}
private void AlignItemsHorizontally(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (RemoteController?.Buttons == null)
{
return;
}
var alignedButtons = RemoteController.Buttons.Where(b => b.Top - 2 <= button.Top && b.Top + 2 >= button.Top);
foreach (var b in alignedButtons)
{
b.Top = button.Top;
}
}
private void AlignItemsVertically(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (RemoteController?.Buttons == null)
{
return;
}
var alignedButtons = RemoteController.Buttons.Where(b => b.Top - 15 <= button.Top && b.Top + 15 >= button.Top && b.Left - 8 <= button.Left && b.Left + 8 >= button.Left);
foreach (var b in alignedButtons)
{
b.Left = button.Left;
}
}
private async Task NewRcButtonRightClick(MouseEventArgs e)
{
if (!AllowEditing)
{
return;
}
if (ImageDimension == null)
{
ImageDimension = await jsHelper.GetElementDimensions("rcImage");
}
if (ImageDimension != null)
{
var posX = Math.Round(((double)e.OffsetX / ImageDimension.Width) * 100, 4);
var posY = Math.Round(((double)e.OffsetY / ImageDimension.Height) * 100, 4);
posX -= (ButtonSize / 2);
posY -= ((double)ImageDimension.Width / ImageDimension.Height) * ((ButtonSize / 2) - 0.5);
CurrentButton = new BaseRcButtonDto<TPreset, TButtonEnum>
{
Top = posY,
Left = posX
};
await jsHelper.OpenModal("rcButtonModal");
}
}
private Task AfterApplyButton(BaseRcButtonDto<TPreset, TButtonEnum>? button)
{
if (RemoteController == null)
{
return Task.CompletedTask;
}
if (button == null)
{
RemoteController.Buttons.Remove(CurrentButton);
}
else if (!RemoteController.Buttons.Contains(button))
{
RemoteController.Buttons.Add(button);
}
StateHasChanged();
return Task.CompletedTask;
}
private void KeyDown(KeyboardEventArgs e)
{
}
private void KeyUp(KeyboardEventArgs e)
{
}
private void RemoteChanged()
{
RemoteController = Remotes?.FirstOrDefault(r => r.Name == SelectedRemoteName);
ImageDimension = null;
}
}
@@ -1,17 +1,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services
@using ColorControl.Shared.Common
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@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" id="samsungDeviceModal" 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">@(Device?.Name == null ? $"Add new device" : $"Settings of device {Device.Name}")</h5>
@@ -73,7 +71,7 @@
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
@@ -143,6 +141,14 @@
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOff" @bind="Device.Options.PowerOffByWindows">
<label class="form-check-label" for="checkWindowsPowerOff">Use Windows power settings to power off</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterSessionUnlock" @bind="Device.Options.PowerOnAfterSessionUnlock">
<label class="form-check-label" for="checkPowerOnAfterSessionUnlock">Power on when unlocking the pc</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffAfterSessionLock" @bind="Device.Options.PowerOffAfterSessionLock">
<label class="form-check-label" for="checkPowerOffAfterSessionLock">Power off when locking the pc</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkUseSecureConnection" @bind="Device.Options.UseSecureConnection">
<label class="form-check-label" for="checkUseSecureConnection">Use secure connection</label>
@@ -157,6 +163,10 @@
</form>
</div>
<div class="modal-footer">
@if (Device?.IsCustom == true)
{
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#removeDeviceModal">Remove</button>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
@@ -164,6 +174,8 @@
</div>
</div>
<ConfirmModal ModalId="removeDeviceModal" Title="Remove device" Message="Are you sure you want to remove this device?" OnConfirm="DeviceOnRemoveConfirmed" NextModalId="samsungDeviceModal" />
@code {
private SamsungDeviceDto? _device;
@@ -179,7 +191,7 @@
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (_device != Device)
{
@@ -208,6 +220,21 @@
}
}
private async Task DeviceOnRemoveConfirmed()
{
if (Device == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "RemoveDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
private async Task TestConnectionClick(MouseEventArgs e)
{
IsTestingConnection = true;
@@ -1,19 +1,16 @@
@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
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.Samsung.SamsungPreset>
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@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>
@@ -114,7 +111,7 @@
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
protected override void OnParametersSet()
{
if (PresetParam == null)
{
@@ -1,10 +1,10 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal ModalId="serviceMenuModal" Title="@Title" ConfirmCaption="@ConfirmCaption" SecondaryCaption="@SecondaryCaption"
@@ -23,7 +23,7 @@
private Func<Task>? NextAction;
private Func<Task>? SecondaryAction;
protected override async Task OnInitializedAsync()
protected override void OnInitialized()
{
// NextAction = FirstStepExit;
// Title = "Access Service Menu";
@@ -31,7 +31,7 @@
// Fields = [AddField("Service Menu Access Type", ServiceMenuType.FactoryMenu, subLabel: "Both types will not reset your settings. 'FactoryMenu' is recommended for 2024 series but will also work for older ones. Click 'Next >' to continue or 'X' to stop")];
}
protected async Task OnShow()
protected Task OnShow()
{
NextAction = FirstStepExit;
Title = "Access Service Menu";
@@ -50,6 +50,8 @@
}
StateHasChanged();
return Task.CompletedTask;
}
private async Task NextClick()
@@ -98,12 +100,16 @@
ConfirmCaption = "Next >";
SecondaryCaption = "Skip";
SecondaryAction = FactoryMenuSecondStepEnterWithoutExecute;
Fields = [AddField("Service Menu - Advanced", null, FieldType.Label, "If the service menu opened and you want to access the Advanced settings, click 'Next >' or click 'Skip' to skip")];
Fields =
[
AddField("Service Menu - Advanced", null, FieldType.Label, "If the service menu opened and you want to access the Advanced settings, click 'Next >' or click 'Skip' to skip. You can also manually enter the unlock codes 0098 (for TV's) and 0038 (for monitors) while the 'Advanced' item is highlighted."),
AddField("Advanced Unlock Type", AdvancedUnlockType.TV, subLabel: "Select the desired unlock type for the Advanced section. If the target is a TV choose TV, otherwise choose Monitor")
];
StateHasChanged();
}
protected async Task FactoryMenuSecondStepEnterWithoutExecute()
protected Task FactoryMenuSecondStepEnterWithoutExecute()
{
NextAction = FactoryMenuSecondStepExit;
Title = "Exit Service Menu";
@@ -113,11 +119,27 @@
Fields = [AddField("Exit Service Menu Type", ExitServiceMenuType.Exit, subLabel: "Choose how to exit the service menu. You can either reboot the TV or just exit the service menu (if, for example, no changes have been made)")];
StateHasChanged();
return Task.CompletedTask;
}
protected async Task FactoryMenuSecondStepEnter()
{
await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvanced);
if (Fields == null)
{
return;
}
var unlockType = Fields.First(f => f.ValueType == typeof(AdvancedUnlockType)).ValueAsEnum<AdvancedUnlockType>();
if (unlockType == AdvancedUnlockType.TV)
{
await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvanced);
}
else
{
await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvancedMonitor);
}
await FactoryMenuSecondStepEnterWithoutExecute();
}
@@ -1,10 +1,10 @@
@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
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal Title="Samsung settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
@@ -4,12 +4,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services;
@using ColorControl.UI.Components.Pages.RemoteControl
@using ColorControl.UI.Pages.Generic
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inherits SummaryComponentBase
@rendermode InteractiveServer
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
@@ -22,6 +25,9 @@
<div class="d-flex align-items-center">
<h1>Samsung controller</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#rcCanvasRight" aria-controls="rcCanvasRight">
Remote control
</button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
Buttons
@@ -62,6 +68,16 @@
</div>
</div>
<div class="offcanvas offcanvas-end" style="width: 280px" data-bs-scroll="true" tabindex="-1" id="rcCanvasRight" aria-labelledby="rcCanvasRightRightLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="rcCanvasRightRightLabel">Remote control</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<GenericRcPage ServiceName="SamsungService" Device="CurrentDevice" TDevice="SamsungDeviceDto" TPreset="SamsungPreset" TButtonEnum="ButtonType"></GenericRcPage>
</div>
</div>
<SamsungDevicePage Device="CurrentDevice" AfterApply="AfterDeviceApplied" />
<SamsungPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@@ -82,7 +98,7 @@ else
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#samsungDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
@@ -117,7 +133,7 @@ else
{
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#samsungDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
@@ -153,10 +169,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">@preset.name <small class="text-muted">@GetDeviceName(preset)</small></h5>
@@ -166,13 +186,23 @@ 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-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="#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>
</span>
}
</div>
</div>
@@ -192,10 +222,18 @@ else
private PresetOrder PresetOrder;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await Refresh();
}
protected override async Task Refresh()
{
Config = await _rpcClientService.CallAsync<SamsungServiceConfigDto>("SamsungService", "GetConfig");
await RefreshPresets();
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
CurrentDevice = Devices?.FirstOrDefault(d => d.MacAddress == Config?.PreferredMacAddress) ?? Devices?.FirstOrDefault();
InvokableActions = await _rpcClientService.CallAsync<List<InvokableActionDto<SamsungPreset>>>("SamsungService", "GetInvokableActions");
}
@@ -215,18 +253,18 @@ else
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPresetWithId", preset.id);
}
private async Task PresetOnEditClick(SamsungPreset preset)
private void PresetOnEditClick(SamsungPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnCloneClick(SamsungPreset preset)
private void PresetOnCloneClick(SamsungPreset preset)
{
CurrentPreset = new SamsungPreset(preset);
CurrentPreset.SetCloneValues();
}
private async Task PresetOnDeleteClick(SamsungPreset preset)
private void PresetOnDeleteClick(SamsungPreset preset)
{
CurrentPreset = preset;
}
@@ -243,17 +281,17 @@ else
StateHasChanged();
}
private async Task DeviceOnClick(SamsungDeviceDto device)
private void DeviceOnClick(SamsungDeviceDto device)
{
CurrentDevice = device;
}
private async Task AddDeviceOnClick()
private void AddDeviceOnClick()
{
CurrentDevice = new SamsungDeviceDto { IsCustom = true };
}
private async Task AddPresetOnClick()
private void AddPresetOnClick()
{
CurrentPreset = new SamsungPreset();
}
+16
View File
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
public static class Constants
{
public const string Primary = "primary";
public const string Secondary = "secondary";
public const string Success = "success";
public const string Danger = "danger";
public const string Warning = "warning";
public const string Info = "info";
public const string Light = "light";
public const string Dark = "dark";
public static IComponentRenderMode RenderMode = new InteractiveServerRenderMode(prerender: false);
}
@@ -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;
}
}
}
+16 -34
View File
@@ -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;
}
}
+26
View File
@@ -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)
};
}
+16 -28
View File
@@ -1,32 +1,20 @@
//using ColorControl.UI;
using ColorControl.Shared.Contracts;
using ColorControl.UI;
using ColorControl.UI.Services;
using System.Diagnostics;
//Blazor.Start();
var arguments = args;
//var builder = WebApplication.CreateBuilder(args);
if (Debugger.IsAttached)
{
var rpcServer = new RpcUiClientService(null);
var options = new RpcUiClientOptions
{
Timeout = 10000
};
//// Add services to the container.
//builder.Services.AddRazorComponents()
// .AddInteractiveServerComponents();
var config = await rpcServer.CallWithOptionsAsync<Config>("OptionsService", "GetConfig", options);
arguments = ["", config.UseDarkMode.ToString(), config.UiPort.ToString(), config.UiAllowRemoteConnections.ToString()];
}
//builder.Services.AddSingleton<AppState>();
//builder.Services.AddSingleton<OptionsService>();
//var app = builder.Build();
//// Configure the HTTP request pipeline.
//if (!app.Environment.IsDevelopment())
//{
// app.UseExceptionHandler("/Error", createScopeForErrors: true);
// // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
// app.UseHsts();
//}
//app.UseHttpsRedirection();
//app.UseStaticFiles();
//app.UseAntiforgery();
//app.MapRazorComponents<App>()
// .AddInteractiveServerRenderMode();
//app.Run();
await Blazor.Start(arguments);
@@ -1,13 +1,5 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:31433",
"sslPort": 44330
}
},
"profiles": {
"http": {
"commandName": "Project",
@@ -26,13 +18,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+79
View File
@@ -0,0 +1,79 @@
using Microsoft.JSInterop;
namespace ColorControl.UI.Services;
public class RectDimension
{
public double X { get; set; }
public double Y { get; set; }
public double Width { get; set; }
public double Height { get; set; }
}
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<bool> IsModalShown()
{
return await InvokeAsync<bool>("IsModalShown");
}
public async Task ShowToast(string id)
{
await InvokeVoidAsync("ShowToast", id);
}
public async Task NavigateTo(string uri, int timeout = 0)
{
await InvokeVoidAsync("NavigateTo", uri, timeout);
}
public async Task<RectDimension> GetElementDimensions(string id)
{
jsModule = await LoadModule();
return await jsModule.InvokeAsync<RectDimension>("GetElementDimensions", id);
}
public async Task<string> GetElementClassName(string id)
{
jsModule = await LoadModule();
return await jsModule.InvokeAsync<string>("GetElementClassName", 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,34 @@
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);
});
}
public async Task SendNotificationDirect(NotificationDto notification)
{
if (OnNotification != null)
{
await OnNotification.Invoke(notification);
}
}
}
@@ -0,0 +1,102 @@
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<bool> CallGenericBoolResultAsync(string serviceName, string method, params object?[] arguments)
{
var rpcMessage = new SvcRpcMessage
{
ServiceName = serviceName,
MethodName = method,
Arguments = arguments
};
var timeout = Debugger.IsAttached ? 60000 : PipeUtils.DefaultTimeout;
var result = await SendRpcMessageAsync<GenericBoolResult>(rpcMessage, timeout, pipeName: PipeUtils.MainPipe);
if (result?.Result == true)
{
return true;
}
if (result?.ErrorMessages.Count > 0 && notificationService != null)
{
await notificationService.SendNotificationDirect(new NotificationDto(result.ErrorMessages.First(), Constants.Danger));
}
return false;
}
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);
}
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";
if (notificationService != null)
{
await notificationService.SendNotificationDirect(new NotificationDto(LastErrorMessage, Constants.Danger));
}
return default;
}
LastErrorMessage = null;
var result = JsonConvert.DeserializeObject<T>(resultMessage.Data);
return result;
}
}
+3 -3
View File
@@ -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;
-7
View File
@@ -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
+33
View File
@@ -0,0 +1,33 @@
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();
}
export function GetElementDimensions(elementId) {
return document.getElementById(elementId).getBoundingClientRect();
}
export function GetElementClassName(elementId) {
return document.getElementById(elementId).className;
}
export function NavigateTo(uri, timeout = 0) {
setTimeout(() => window.location = uri, timeout);
}
export function IsModalShown() {
return document.getElementsByClassName("modal show").length > 0;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

-111
View File
@@ -1,111 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorControl", "ColorControl\ColorControl.csproj", "{A6005D08-70F0-44CB-A384-9219EBA84F21}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nspector", "Nspector\Nspector.csproj", "{76797290-48F5-4A82-86C3-FAD25DAE4500}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvAPIWrapper", "NvAPIWrapper\NvAPIWrapper.csproj", "{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "novideo_srgb", "novideo_srgb\novideo_srgb.csproj", "{A6A97834-7BE1-474A-B92F-A512DB1D5186}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogViewer", "NLogViewer\NLogViewer.csproj", "{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LittleCms", "LittleCms\LittleCms.csproj", "{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MHC2Gen", "MHC2Gen\MHC2Gen.csproj", "{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorControl.UI", "ColorControl.UI\ColorControl.UI.csproj", "{85EA3079-BB73-4CE5-A72F-E56906187051}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.ActiveCfg = Debug|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.Build.0 = Debug|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|Any CPU.Build.0 = Release|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|x64.ActiveCfg = Release|Any CPU
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|x64.Build.0 = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|x64.ActiveCfg = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|x64.Build.0 = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|Any CPU.Build.0 = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|x64.ActiveCfg = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|x64.Build.0 = Release|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Debug|x64.ActiveCfg = Debug|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Debug|x64.Build.0 = Debug|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|Any CPU.Build.0 = Release|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|x64.ActiveCfg = Release|Any CPU
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|x64.Build.0 = Release|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|x64.ActiveCfg = Debug|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|x64.Build.0 = Debug|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.Build.0 = Release|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.ActiveCfg = Release|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.Build.0 = Release|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.ActiveCfg = Debug|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.Build.0 = Debug|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.Build.0 = Release|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.ActiveCfg = Release|Any CPU
{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.Build.0 = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Debug|x64.ActiveCfg = Debug|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Debug|x64.Build.0 = Debug|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|Any CPU.Build.0 = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|x64.ActiveCfg = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|x64.Build.0 = Release|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|x64.ActiveCfg = Debug|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|x64.Build.0 = Debug|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|Any CPU.Build.0 = Release|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|x64.ActiveCfg = Release|Any CPU
{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|x64.Build.0 = Release|Any CPU
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|Any CPU.ActiveCfg = Debug|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|Any CPU.Build.0 = Debug|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|x64.ActiveCfg = Debug|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|x64.Build.0 = Debug|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|Any CPU.ActiveCfg = Release|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|Any CPU.Build.0 = Release|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|x64.ActiveCfg = Release|x64
{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|x64.Build.0 = Release|x64
{85EA3079-BB73-4CE5-A72F-E56906187051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Debug|Any CPU.Build.0 = Debug|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Debug|x64.ActiveCfg = Debug|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Debug|x64.Build.0 = Debug|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Release|Any CPU.ActiveCfg = Release|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Release|Any CPU.Build.0 = Release|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Release|x64.ActiveCfg = Release|Any CPU
{85EA3079-BB73-4CE5-A72F-E56906187051}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {34BA158F-AE57-4A7B-A372-11FEFBBC1F98}
EndGlobalSection
EndGlobal
+17
View File
@@ -0,0 +1,17 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
</Configurations>
<Project Path="ColorControl.UI/ColorControl.UI.csproj" />
<Project Path="ColorControl/ColorControl.csproj" />
<Project Path="LittleCms/LittleCms.csproj" />
<Project Path="MHC2Gen/MHC2Gen.csproj">
<Platform Project="x64" />
</Project>
<Project Path="NLogViewer/NLogViewer.csproj" />
<Project Path="novideo_srgb/novideo_srgb.csproj" />
<Project Path="Nspector/Nspector.csproj" />
<Project Path="NvAPIWrapper/NvAPIWrapper.csproj" />
<Project Path="Shared/Shared.csproj" />
</Solution>
+111
View File
@@ -0,0 +1,111 @@
using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Shared.Services;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace ColorControl;
public static class BlazorUiManager
{
private const string ImageName = "ColorControl.UI.exe";
private const string ProcessName = "ColorControl.UI";
private const bool DebugUi = true;
private static bool FirsTime = true;
private static Mutex UiMutex;
private static string MutexId;
public static bool IsRunning(Config config)
{
return Process.GetProcessesByName(ProcessName).Length > 0 &&
(config.UiPort == 0 || config.CurrentUiPort == config.UiPort) &&
config.CurrentUiAllowRemoteConnections == config.UiAllowRemoteConnections;
}
public static async Task<bool> Start(Config config)
{
if ((!FirsTime || Debugger.IsAttached && DebugUi) && IsRunning(config))
{
return false;
}
FirsTime = false;
await Stop();
var uiExe = Path.Combine(Environment.CurrentDirectory, ImageName);
if (!File.Exists(uiExe))
{
uiExe = Path.Combine(Environment.CurrentDirectory.Replace("ColorControl\\bin", "ColorControl.UI\\bin"), ImageName);
if (!File.Exists(uiExe))
{
throw new InvalidOperationException("UI executable not found");
}
}
MutexId = $"Global\\{ProcessName}_{Guid.NewGuid()}";
Utils.ExecuteOnMainThread(() =>
{
UiMutex = new Mutex(false, MutexId);
UiMutex.WaitOne();
});
var windowStyle = ProcessWindowStyle.Hidden;
if (Debugger.IsAttached)
{
windowStyle = ProcessWindowStyle.Normal;
}
var processStartInfo = new ProcessStartInfo(uiExe, [MutexId, config.UseDarkMode.ToString(), config.UiPort.ToString(), config.UiAllowRemoteConnections.ToString()])
{
WindowStyle = windowStyle
};
Process.Start(processStartInfo);
return true;
}
public static async Task Stop()
{
if (UiMutex != null)
{
Utils.ExecuteOnMainThread(() =>
{
UiMutex.ReleaseMutex();
UiMutex.Close();
UiMutex = null;
});
return;
}
var processes = Process.GetProcessesByName(ProcessName);
if (processes.Length <= 0)
{
return;
}
var winApi = Program.ServiceProvider.GetRequiredService<WinApiAdminService>();
winApi.StopProcessByName(ProcessName);
}
public static string GetCurrentUrl(Config config)
{
var port = GetCurrentPort(config);
return $"http://localhost:{port}";
}
public static int GetCurrentPort(Config config = null)
{
return config?.CurrentUiPort ?? -1;
}
}
+19 -31
View File
@@ -1,25 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>false</MapFileExtensions>
<UpdateUrl>https://www.maassoft.com/downloads/ColorControl/</UpdateUrl>
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<Company>Maassoft</Company>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>10.0.0.0</ApplicationVersion>
<Version>10.0.0.0</Version>
<ApplicationVersion>11.0.0.0</ApplicationVersion>
<Version>11.0.0.0</Version>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>false</BootstrapperEnabled>
@@ -28,17 +21,10 @@
<UseWPF>false</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<ApplicationIcon>Resources\pngbarn.ico</ApplicationIcon>
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
<Copyright>2020-2024 Maassoft</Copyright>
<Copyright>2020-2025 Maassoft</Copyright>
<Authors>Maassoft</Authors>
<ManifestCertificateThumbprint>CF0BC153EA664CD97AC6F234B91F45D34630958F</ManifestCertificateThumbprint>
<ManifestKeyFile>ColorControl_1_TemporaryKey.pfx</ManifestKeyFile>
<GenerateManifests>true</GenerateManifests>
<TargetZone>LocalIntranet</TargetZone>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
<SignManifests>true</SignManifests>
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<OutputPath>bin\x64\Debug\</OutputPath>
@@ -50,8 +36,11 @@
</PropertyGroup>
<PropertyGroup>
<PathMap Condition="'$(Configuration)' == 'Release'">$(MSBuildThisFileDirectory)=.\</PathMap>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\LgRemotes.json" />
<None Remove="Resources\SamsungRemotes.json" />
<None Remove="XForms\BrowserWindow.xaml" />
<None Remove="XForms\ColorProfileWindow.xaml" />
<None Remove="XForms\LogWindow.xaml" />
@@ -82,10 +71,13 @@
<Compile Update="Services\Samsung\SamsungPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<EmbeddedResource Include="Resources\LgRemotes.json" />
<EmbeddedResource Include="Resources\LG_register_legacy.json" />
<EmbeddedResource Include="Resources\LG_register.json" />
<EmbeddedResource Include="Resources\RC_coords.txt" />
<EmbeddedResource Include="Resources\LG_remote.jpg" />
<EmbeddedResource Include="Resources\pngbarn.ico" />
<EmbeddedResource Include="Resources\SamsungRemotes.json" />
<Content Include="LgPresets.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -105,18 +97,14 @@
</COMReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2277.86" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NStandard" Version="0.59.0" />
<PackageReference Include="NWin32" Version="1.2.6" />
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="TaskScheduler" Version="2.10.1" />
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.17" />
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.7" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3912.50" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="NLog" Version="6.1.2" />
<PackageReference Include="NStandard" Version="0.110.7" />
<PackageReference Include="NWin32" Version="1.2.8" />
<PackageReference Include="System.Management" Version="10.0.7" />
<PackageReference Include="TaskScheduler" Version="2.12.2" />
</ItemGroup>
<ItemGroup>
<Page Include="XForms\ColorProfileWindow.xaml">
+17 -13
View File
@@ -1,4 +1,8 @@
using ColorControl.Services.AMD;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using ColorControl.Services.AMD;
using ColorControl.Services.LG;
using ColorControl.Services.NVIDIA;
using ColorControl.Services.Samsung;
@@ -6,10 +10,6 @@ using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Svc;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
{
@@ -23,20 +23,18 @@ namespace ColorControl
return true;
}
var useConsole = startUpParams.NoGui || existingProcess != null;
var useConsole = startUpParams.NoGui || existingProcess?.SessionId > 0;
if (!useConsole)
{
return false;
}
startUpParams.NoGui = true;
var result = false;
if (startUpParams.ExecuteHelp)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine();
Console.WriteLine($@"ColorControl CLI {Application.ProductVersion}
@@ -56,7 +54,7 @@ Options:
if (startUpParams.ExecuteLgPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing LG-preset '{startUpParams.LgPresetName}'...");
await LgService.ExecutePresetAsync(startUpParams.LgPresetName);
@@ -68,7 +66,7 @@ Options:
if (startUpParams.ExecuteSamsungPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing Samsung-preset '{startUpParams.SamsungPresetName}'...");
await SamsungService.ExecutePresetAsync(startUpParams.SamsungPresetName);
@@ -80,7 +78,7 @@ Options:
if (startUpParams.ExecuteNvidiaPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing NVIDIA-preset '{startUpParams.NvidiaPresetIdOrName}'...");
await NvService.ExecutePresetAsync(startUpParams.NvidiaPresetIdOrName);
@@ -92,7 +90,7 @@ Options:
if (startUpParams.ExecuteAmdPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing AMD-preset '{startUpParams.AmdPresetIdOrName}'...");
await AmdService.ExecutePresetAsync(startUpParams.AmdPresetIdOrName);
@@ -105,6 +103,12 @@ Options:
return result;
}
private static void PrepareConsole(StartUpParams startUpParams)
{
Utils.OpenConsole();
startUpParams.NoGui = true;
}
private static async Task StartElevated()
{
try
-2
View File
@@ -499,7 +499,6 @@ namespace ColorControl.Forms
private System.Windows.Forms.GroupBox grpGeneralOptions;
private System.Windows.Forms.CheckBox chkStartMinimized;
private System.Windows.Forms.CheckBox chkStartAfterLogin;
private System.Windows.Forms.TabPage tabInfo;
private System.Windows.Forms.GroupBox grpMiscellaneousOptions;
private System.Windows.Forms.CheckBox chkFixChromeFonts;
private System.Windows.Forms.Label lblFixChromeFontsDescription;
@@ -518,7 +517,6 @@ namespace ColorControl.Forms
private System.Windows.Forms.Button btnElevationInfo;
private System.Windows.Forms.Button btnStartStopService;
private System.Windows.Forms.CheckBox chkAutoInstallUpdates;
private System.Windows.Forms.Button btnUpdate;
private System.Windows.Forms.GroupBox grpOptionsModules;
private System.Windows.Forms.CheckedListBox chkModules;
private System.Windows.Forms.Label label1;
+1 -1
View File
@@ -386,6 +386,6 @@ Currently ColorControl is {(_winApiService.IsAdministrator() ? "" : "not ")}runn
return;
}
_config.UiType = (UiType)cbxUiType.SelectedIndex;
_optionsService.SetUiType((UiType)cbxUiType.SelectedIndex);
}
}
+302
View File
@@ -0,0 +1,302 @@
using ColorControl.Shared.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Net.Security;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LgTv
{
public class SendMessageExceptionCws : Exception
{
public SendMessageExceptionCws(string message, Exception e) : base(message, e)
{
}
}
public delegate void IsConnectedDelegateCws(bool status);
public class LgTvApiCoreCws : IAsyncDisposable
{
public bool ConnectionClosed { get; private set; }
private ClientWebSocket _clientWebSocket;
private int _commandCount;
private Uri _uri;
public event IsConnectedDelegateCws IsConnected;
private readonly ConcurrentDictionary<string, TaskCompletionSource<dynamic>> _tokens = new ConcurrentDictionary<string, TaskCompletionSource<dynamic>>();
private readonly ConcurrentDictionary<string, Func<dynamic, bool>> _callbacks = new ConcurrentDictionary<string, Func<dynamic, bool>>();
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public async Task<bool> Connect(Uri uri, bool ignoreReceiver = false)
{
_uri = uri;
try
{
ConnectionClosed = false;
_commandCount = 0;
CreateWebSocket();
try
{
await _clientWebSocket.ConnectAsync(uri, CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(5000));
ConnectionClosed = false;
var _ = Receive(false);
IsConnected?.Invoke(true);
return true;
}
catch (TimeoutException te)
{
Logger.Error($"Timeout while connecting to {uri}: {te.Message}");
return false;
}
}
catch (Exception e)
{
Logger.Error($"Connect to {uri}: {e.Message}");
return false;
}
}
private void CreateWebSocket()
{
_clientWebSocket = new ClientWebSocket();
_clientWebSocket.Options.RemoteCertificateValidationCallback += CertificationValidationCallback;
}
private bool CertificationValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) => true;
public async Task SendMessageAsync(string message, bool reconnect = true)
{
try
{
if (_clientWebSocket?.State != WebSocketState.Open)
{
throw new InvalidOperationException("Socket not open");
}
var bytes = UTF8Encoding.UTF8.GetBytes(message);
var data = bytes.AsMemory();
await _clientWebSocket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (WebSocketException)
{
if (reconnect && await Connect(_uri))
{
await SendMessageAsync(message, false);
}
}
catch (Exception e)
{
Logger.Error("SendMessageAsync: " + e.Message);
ConnectionClosed = true;
}
}
public async Task<dynamic> SendCommandAsync(string message)
{
var obj = JsonConvert.DeserializeObject<dynamic>(message);
return await SendCommandAsync((string)obj.id, message);
}
private async Task<dynamic> SendCommandAsync(string id, string message)
{
try
{
var taskSource = new TaskCompletionSource<dynamic>();
_tokens.TryAdd(id, taskSource);
await SendMessageAsync(message);
if (ConnectionClosed)
{
throw new Exception("Connection closed");
}
return await taskSource.Task;
}
catch (Exception e)
{
throw new SendMessageExceptionCws("Can't send message", e);
}
}
public async Task<dynamic> SendCommandAsync(RequestMessage message)
{
var rawMessage = new RawRequestMessage(message, ++_commandCount);
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return await SendCommandAsync(rawMessage.Id, serialized);
}
public async Task<dynamic> SubscribeAsync(RequestMessage message, Func<dynamic, bool> callback)
{
var rawMessage = new RawRequestMessage(message, ++_commandCount);
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
_callbacks.TryAdd(rawMessage.Id, callback);
return await SendCommandAsync(rawMessage.Id, serialized);
}
private async Task Receive(bool singleResponse)
{
var dataPerPacket = 8 * 1024; //8192
var strBuilder = new StringBuilder();
var buffer = new ArraySegment<byte>(new byte[dataPerPacket]);
try
{
while (_clientWebSocket?.State == WebSocketState.Open)
{
var receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
//keep doing until end of message
while (!receiveResult.EndOfMessage)
{
receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
}
var message = strBuilder.ToString();
strBuilder.Clear();
HandleMessage(message);
if (singleResponse)
{
return;
}
}
}
catch (Exception)
{
ConnectionClosed = true;
Logger.Debug("Connection closed");
IsConnected?.Invoke(false);
}
}
public void HandleMessage(string message)
{
try
{
if (string.IsNullOrEmpty(message))
{
throw new InvalidOperationException("Empty response received. Assuming connection closed.");
}
var obj = JsonConvert.DeserializeObject<dynamic>(message);
var id = (string)obj.id;
var type = (string)obj.type;
TaskCompletionSource<dynamic> taskCompletion;
if (type == "registered")
{
if (_tokens.TryRemove(id, out taskCompletion))
{
var key = (string)JObject.Parse(message)["payload"]["client-key"];
taskCompletion.TrySetResult(new { clientKey = key });
}
}
else if (_tokens.TryGetValue(id, out taskCompletion))
{
if (id == "register_0")
{
//taskCompletion.TrySetResult(null);
return;
}
if (obj.type == "error")
{
taskCompletion.SetException(new Exception(obj.error?.ToString()));
}
//else if (args.Cancelled)
//{
// taskSource.SetCanceled();
//}
taskCompletion.TrySetResult(obj.payload);
if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
{
try
{
callback(obj.payload);
}
catch (Exception callbackException)
{
Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
}
}
}
}
catch (Exception ex)
{
Logger.Error($"HandleMessage: status: {_clientWebSocket.State}, exception: {ex.ToLogString()}");
SetExceptionOnAllTokens(ex);
ConnectionClosed = true;
}
}
public async ValueTask DisposeAsync()
{
if (_clientWebSocket != null)
{
if (_clientWebSocket.State == WebSocketState.Open)
{
try
{
await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
}
catch { }
}
_clientWebSocket?.Dispose();
}
GC.SuppressFinalize(this);
}
private void SetExceptionOnAllTokens(Exception ex)
{
//Logger.Debug($"Tokens: {_tokens.Count}");
foreach (var taskCompletion in _tokens.Values)
{
try
{
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
{
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
taskCompletion.SetException(new Exception(ex.Message));
}
}
catch (Exception ex2)
{
Logger.Error("SetExceptionOnAllTokens: " + ex2.Message);
}
}
_tokens.Clear();
}
}
}
+317 -341
View File
@@ -16,356 +16,332 @@ using NWin32;
using System;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
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();
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();
btnUpdate.Visible = _updateManager.UpdateAvailable();
}
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 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 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);
}
}
}
+13 -1
View File
@@ -107,6 +107,11 @@ internal class MainWorker
_keyboardShortcutDispatcher.RegisterShortcut(SHORTCUTID_SCREENSAVER, _config.ScreenSaverShortcut);
_keyboardShortcutDispatcher.RegisterEventHandler(KeyboardShortcutDispatcher.Event_HotKey, HandleHotKeyEvent);
if (_config.UiType != UiType.WinForms)
{
await Program.StartUiServer();
}
}
private bool _startupSent = false;
@@ -122,7 +127,10 @@ internal class MainWorker
_ = _powerEventDispatcher.SendEventAsync(PowerEventDispatcher.Event_Startup);
_ = _updateManager.CheckForUpdates();
if (_config.CheckForUpdates)
{
_ = _updateManager.CheckForUpdatesAndInstall();
}
if (!_config.StartMinimized /*|| Debugger.IsAttached*/)
{
@@ -154,6 +162,8 @@ internal class MainWorker
Logger.Debug($"MainWorker: QueryEndSession");
Logger.Debug($"MainWorker: SystemShutdown");
_serviceManager.Save();
_powerEventDispatcher.SendEvent(PowerEventDispatcher.Event_Shutdown);
}
@@ -167,6 +177,8 @@ internal class MainWorker
private void StartScreenSaver()
{
//NativeMethods.SendMessageW(-1, NativeConstants.WM_SYSCOMMAND, NativeConstants.SC_MONITORPOWER, (int)WinApi.MonitorState.OFF);
var screenSaver = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "scrnsave.scr");
Process.Start("explorer.exe", screenSaver);
}
+456 -426
View File
@@ -29,430 +29,460 @@ using System.Windows.Forms;
namespace ColorControl
{
static class Program
{
public const string TS_TASKNAME = "ColorControl";
public static string DataDir { get; private set; }
public static string ConfigFilename { get; private set; }
public static string LogFilename { get; private set; }
public static Config Config { get; private set; }
public static GlobalContext AppContext { get; private set; }
public static bool IsRestarting { get; private set; }
public static bool UserExit = false;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
public static MainForm _mainForm;
private static LoggingRule _loggingRule;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] // autogenerated
public static void Main(string[] args)
{
// STA
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
DataDir = Utils.GetDataPath();
var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true;
var runElevated = args.Contains("--elevated");
InitLogger(runAsService, runElevated);
Logger.Debug($"Using data path: {DataDir}");
//Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName);
if (runAsService)
{
await RunService(args);
return;
}
await RunApp(args);
}
static async Task RunApp(string[] args)
{
var startTime = DateTime.Now;
var currentDomain = AppDomain.CurrentDomain;
// Handler for unhandled exceptions.
currentDomain.UnhandledException += GlobalUnhandledExceptionHandler;
// Handler for exceptions in threads behind forms.
Application.ThreadException += GlobalThreadExceptionHandler;
LoadConfig();
if (Config.UseGdiScaling)
{
var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
Logger.Debug($"Result of setting SetHighDpiMode: {result}");
}
var mutexId = $"Global\\{typeof(MainForm).GUID}";
var startUpParams = StartUpParams.Parse(args);
AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, mutexId);
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
MessageForms.Title = AppContext.ApplicationTitle;
var existingProcess = Utils.GetProcessByName("ColorControl");
try
{
if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess))
{
return;
}
}
finally
{
Utils.CloseConsole();
}
_mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated);
try
{
if (!mutexCreated)
{
if (existingProcess != null && existingProcess.Threads.Count > 0)
{
var thread = existingProcess.Threads[0];
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
return;
}
MessageBox.Show("Only one instance of this program can be active.", "ColorControl");
}
else
{
_mutex.WaitOne();
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (Debugger.IsAttached)
{
//Utils.StartService();
var winApiService = ServiceProvider.GetRequiredService<WinApiService>();
if (winApiService.IsAdministrator())
{
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.ElevatedPipe;
Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None));
}
}
var worker = ServiceProvider.GetRequiredService<MainWorker>();
await worker.DoWork();
if (Debugger.IsAttached && !IsRestarting)
{
var winApiAdminService = ServiceProvider.GetRequiredService<WinApiAdminService>();
winApiAdminService.StopService();
}
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
finally
{
if (mutexCreated)
{
_mutex?.Close();
}
}
}
public static void OpenDefaultUi()
{
var _ = Config.UiType switch
{
UiType.WebDefaultBrowser => OpenNewUi(),
UiType.WebEmbedded => OpenNewUiEmbedded(),
_ => OpenMainForm()
};
}
public static async Task OpenMainForm()
{
_mainForm ??= ServiceProvider.GetRequiredService<MainForm>();
_mainForm.OpenForm();
}
public static void Exit()
{
UserExit = true;
_mainForm?.Close();
if (AppContext.MainHandle != 0)
{
NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0);
}
}
public static void Restart()
{
IsRestarting = true;
_mainForm?.CloseForRestart();
_mutex?.Close();
_mutex = null;
Application.Restart();
Environment.Exit(0);
}
public static async Task OpenNewUi()
{
await StartUiServer();
var winApi = AppContext.ServiceProvider.GetRequiredService<WinApiAdminService>();
winApi.StartProcess(GetNewUiUrl());
}
public static string GetNewUiUrl()
{
return $"http://localhost:{(Config.UiPort > 0 ? Config.UiPort : 5000)}";
}
public static async Task OpenNewUiEmbedded()
{
await StartUiServer();
BrowserWindow.CreateAndShow(GetNewUiUrl());
}
private static bool UiServerStarted;
private static async Task StartUiServer()
{
if (UiServerStarted)
{
return;
}
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.MainPipe;
await backgroundService.StartAsync(CancellationToken.None);
var _ = Task.Run(() => ColorControl.UI.Blazor.Start(Config));
UiServerStarted = true;
await Task.Delay(500);
}
private static void InitLogger(bool runAsService, bool runElevated)
{
var config = new LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : "";
LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt");
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
_loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile);
_loggingRule.RuleName = "ColorControl";
// Rules for mapping loggers to targets
config.AddRule(_loggingRule);
// Apply config
LogManager.Configuration = config;
}
private static void LoadConfig()
{
ConfigFilename = Path.Combine(DataDir, "Settings.json");
try
{
if (File.Exists(ConfigFilename))
{
var data = File.ReadAllText(ConfigFilename);
Config = JsonConvert.DeserializeObject<Config>(data);
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new Config();
var minLogLevel = LogLevel.FromString(Config.LogLevel);
if (minLogLevel != LogLevel.Trace)
{
_loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
}
}
public static IServiceProvider ServiceProvider { get; private set; }
private static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
//services.AddSingleton<NvService>();
services.RegisterSharedServices();
services.AddSingleton<MainWorker>();
services.AddSingleton<NotifyIconManager>();
services.AddSingleton<LgService>();
services.AddSingleton<GameService>();
services.AddSingleton<SamsungService>();
services.AddSingleton<AmdService>();
services.AddSingleton<PowerEventDispatcher>();
services.AddSingleton<ProcessEventDispatcher>();
services.AddSingleton<WindowMessageDispatcher>();
services.AddSingleton<KeyboardShortcutDispatcher>();
services.AddSingleton<MainForm>();
services.AddSingleton<LogWindow>();
services.AddSingleton<BrowserWindow>();
services.AddTransient<ColorProfileWindow>();
services.AddSingleton<RestartDetector>();
services.AddSingleton<InfoPanel>();
services.AddSingleton<OptionsPanel>();
services.AddSingleton<ElevationService>();
services.AddSingleton<UpdateManager>();
services.AddSingleton<OptionsService>();
services.AddSingleton<LoggingService>();
services.AddSingleton<AmdPanel>();
services.AddSingleton<NvPanel>();
services.AddSingleton<LgPanel>();
services.AddSingleton<SamsungPanel>();
services.AddSingleton<GamePanel>();
});
}
private static async Task RunService(string[] args)
{
var startTime = DateTime.Now;
Logger.Debug("RUNNING SERVICE");
AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime);
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ColorControl Service";
})
.ConfigureServices(services =>
{
services.RegisterSharedServices();
services.AddSingleton<PowerEventDispatcher>();
services.AddHostedService<ColorControlBackgroundService>();
})
.Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
await host.RunAsync();
}
private static void RegisterSharedServices(this IServiceCollection services)
{
services.AddSingleton(AppContext);
services.AddSingleton<SessionSwitchDispatcher>();
services.AddSingleton<WinApiAdminService>();
services.AddSingleton<WinApiService>();
services.AddSingleton<WinElevatedProcessManager>();
services.AddTransient<RpcClientService>();
services.AddTransient<RpcServerService>();
services.AddSingleton<NvService>();
services.AddSingleton<WolService>();
services.AddSingleton<ColorControlBackgroundService>();
services.AddSingleton<ServiceManager>();
RpcServerService.ServiceTypes.Add(typeof(NvService));
RpcServerService.ServiceTypes.Add(typeof(GameService));
RpcServerService.ServiceTypes.Add(typeof(OptionsService));
RpcServerService.ServiceTypes.Add(typeof(LoggingService));
RpcServerService.ServiceTypes.Add(typeof(SamsungService));
RpcServerService.ServiceTypes.Add(typeof(LgService));
RpcServerService.ServiceTypes.Add(typeof(AmdService));
}
public static int EnumThreadWindows(IntPtr handle, IntPtr param)
{
NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero);
return 1;
}
private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception");
}
private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
GlobalHandleException(e.Exception, "Exception in thread");
}
private static void GlobalHandleException(Exception exception, string type)
{
if (UserExit)
{
return;
}
var trace = exception.ToLogString(Environment.StackTrace);
var message = $"{type}: {trace}";
Logger.Error(message);
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void SetTheme(bool useDarkMode)
{
DarkModeUtils.SetDarkMode(useDarkMode);
WinApi.RefreshImmersiveColorPolicyState();
DarkModeUtils.InitWpfTheme();
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor);
_mainForm?.SetTheme(useDarkMode);
}
internal static NotifyIcon GetNotifyIcon()
{
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
return notifyIconManager.NotifyIcon;
}
}
static class Program
{
public const string TS_TASKNAME = "ColorControl";
public static string DataDir { get; private set; }
public static string ConfigFilename { get; private set; }
public static string LogFilename { get; private set; }
public static Config Config { get; private set; }
public static GlobalContext AppContext { get; private set; }
public static bool IsRestarting { get; private set; }
public static bool UserExit = false;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
public static string MutexId;
public static MainForm _mainForm;
private static LoggingRule _loggingRule;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] // autogenerated
public static void Main(string[] args)
{
// STA
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
DataDir = Utils.GetDataPath();
var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true;
var runElevated = args.Contains("--elevated");
InitLogger(runAsService, runElevated);
Logger.Debug($"Using data path: {DataDir}");
//Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName);
if (runAsService)
{
await RunService(args);
return;
}
await RunApp(args);
}
static async Task RunApp(string[] args)
{
var startTime = DateTime.Now;
var currentDomain = AppDomain.CurrentDomain;
// Handler for unhandled exceptions.
currentDomain.UnhandledException += GlobalUnhandledExceptionHandler;
// Handler for exceptions in threads behind forms.
Application.ThreadException += GlobalThreadExceptionHandler;
LoadConfig();
if (Config.UseGdiScaling)
{
var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
Logger.Debug($"Result of setting SetHighDpiMode: {result}");
}
MutexId = $"Global\\{typeof(MainForm).GUID}";
var startUpParams = StartUpParams.Parse(args);
AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, MutexId);
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
MessageForms.Title = AppContext.ApplicationTitle;
var existingProcess = Utils.GetProcessByName("ColorControl");
try
{
if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess))
{
return;
}
}
finally
{
Utils.CloseConsole();
}
_mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated);
try
{
if (!mutexCreated)
{
if (existingProcess != null && existingProcess.Threads.Count > 0)
{
var thread = existingProcess.Threads[0];
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
return;
}
MessageBox.Show("Only one instance of this program can be active.", "ColorControl");
}
else
{
_mutex.WaitOne();
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (Debugger.IsAttached)
{
//Utils.StartService();
var winApiService = ServiceProvider.GetRequiredService<WinApiService>();
if (winApiService.IsAdministrator())
{
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.ElevatedPipe;
_ = Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None));
}
}
var worker = ServiceProvider.GetRequiredService<MainWorker>();
await worker.DoWork();
if (Debugger.IsAttached && !IsRestarting)
{
var winApiAdminService = ServiceProvider.GetRequiredService<WinApiAdminService>();
winApiAdminService.StopService();
}
await BlazorUiManager.Stop();
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
finally
{
if (mutexCreated)
{
_mutex?.Close();
}
}
}
public static void OpenDefaultUi()
{
var _ = Config.UiType switch
{
UiType.WebDefaultBrowser => OpenNewUi(),
UiType.WebEmbedded => OpenNewUiEmbedded(),
_ => OpenMainForm()
};
}
public static Task OpenMainForm()
{
_mainForm ??= ServiceProvider.GetRequiredService<MainForm>();
_mainForm.OpenForm();
return Task.CompletedTask;
}
public static bool IsMainFormOpened()
{
return _mainForm != null;
}
public static void Exit()
{
UserExit = true;
_mainForm?.Close();
if (AppContext.MainHandle != 0)
{
NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0);
}
}
public static void Restart()
{
IsRestarting = true;
_mainForm?.CloseForRestart();
_mutex?.Close();
_mutex = null;
Application.Restart();
Environment.Exit(0);
}
public static async Task OpenNewUi()
{
await OpenNewUi(false);
}
public static async Task OpenNewUiEmbedded()
{
await OpenNewUi(true);
}
public static async Task OpenNewUi(bool embedded)
{
if (await StartUiServer())
{
await Task.Delay(1000);
}
if (BlazorUiManager.GetCurrentPort(Config) <= 0)
{
await OpenMainForm();
return;
}
if (embedded)
{
BrowserWindow.CreateAndShow(BlazorUiManager.GetCurrentUrl(Config));
}
else
{
var winApi = AppContext.ServiceProvider.GetRequiredService<WinApiAdminService>();
winApi.StartProcess(BlazorUiManager.GetCurrentUrl(Config));
}
}
public static async Task<bool> StartOrStopUiServer()
{
if (Config.UiType == UiType.WinForms)
{
var _ = Task.Run(() => BlazorUiManager.Stop());
return true;
}
return await StartUiServer();
}
public static async Task<bool> StartUiServer()
{
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
if (backgroundService.PipeName != PipeUtils.MainPipe)
{
backgroundService.PipeName = PipeUtils.MainPipe;
await backgroundService.StartAsync(CancellationToken.None);
}
return await BlazorUiManager.Start(Config);
}
private static void InitLogger(bool runAsService, bool runElevated)
{
var config = new LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : "";
LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt");
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
_loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile);
_loggingRule.RuleName = "ColorControl";
// Rules for mapping loggers to targets
config.AddRule(_loggingRule);
// Apply config
LogManager.Configuration = config;
}
private static void LoadConfig()
{
ConfigFilename = Path.Combine(DataDir, "Settings.json");
try
{
if (File.Exists(ConfigFilename))
{
var data = File.ReadAllText(ConfigFilename);
Config = JsonConvert.DeserializeObject<Config>(data);
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new Config();
var minLogLevel = LogLevel.FromString(Config.LogLevel);
if (minLogLevel != LogLevel.Trace)
{
_loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
}
}
public static IServiceProvider ServiceProvider { get; private set; }
private static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
//services.AddSingleton<NvService>();
services.RegisterSharedServices();
services.AddSingleton<MainWorker>();
services.AddSingleton<NotifyIconManager>();
services.AddSingleton<LgService>();
services.AddSingleton<GameService>();
services.AddSingleton<SamsungService>();
services.AddSingleton<AmdService>();
services.AddSingleton<PowerEventDispatcher>();
services.AddSingleton<ProcessEventDispatcher>();
services.AddSingleton<WindowMessageDispatcher>();
services.AddSingleton<KeyboardShortcutDispatcher>();
services.AddSingleton<DisplayChangeEventDispatcher>();
services.AddSingleton<MainForm>();
services.AddSingleton<LogWindow>();
services.AddSingleton<BrowserWindow>();
services.AddTransient<ColorProfileWindow>();
services.AddSingleton<RestartDetector>();
services.AddSingleton<InfoPanel>();
services.AddSingleton<OptionsPanel>();
services.AddSingleton<ElevationService>();
services.AddSingleton<UpdateManager>();
services.AddSingleton<OptionsService>();
services.AddSingleton<LoggingService>();
services.AddSingleton<AmdPanel>();
services.AddSingleton<NvPanel>();
services.AddSingleton<LgPanel>();
services.AddSingleton<SamsungPanel>();
services.AddSingleton<GamePanel>();
});
}
private static async Task RunService(string[] args)
{
var startTime = DateTime.Now;
Logger.Debug("RUNNING SERVICE");
AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime);
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ColorControl Service";
})
.ConfigureServices(services =>
{
services.RegisterSharedServices();
services.AddSingleton<PowerEventDispatcher>();
services.AddHostedService<ColorControlBackgroundService>();
})
.Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
await host.RunAsync();
}
private static void RegisterSharedServices(this IServiceCollection services)
{
services.AddSingleton(AppContext);
services.AddSingleton<SessionSwitchDispatcher>();
services.AddSingleton<WinApiAdminService>();
services.AddSingleton<WinApiService>();
services.AddSingleton<WinElevatedProcessManager>();
services.AddTransient<RpcClientService>();
services.AddTransient<RpcServerService>();
services.AddSingleton<NvService>();
services.AddSingleton<WolService>();
services.AddSingleton<ColorControlBackgroundService>();
services.AddSingleton<ServiceManager>();
services.AddSingleton<ColorProfileService>();
RpcServerService.ServiceTypes.Add(typeof(NvService));
RpcServerService.ServiceTypes.Add(typeof(GameService));
RpcServerService.ServiceTypes.Add(typeof(OptionsService));
RpcServerService.ServiceTypes.Add(typeof(LoggingService));
RpcServerService.ServiceTypes.Add(typeof(SamsungService));
RpcServerService.ServiceTypes.Add(typeof(LgService));
RpcServerService.ServiceTypes.Add(typeof(AmdService));
RpcServerService.ServiceTypes.Add(typeof(ColorProfileService));
}
public static int EnumThreadWindows(IntPtr handle, IntPtr param)
{
NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero);
return 1;
}
private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception");
}
private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
GlobalHandleException(e.Exception, "Exception in thread");
}
private static void GlobalHandleException(Exception exception, string type)
{
if (UserExit)
{
return;
}
var trace = exception.ToLogString(Environment.StackTrace);
var message = $"{type}: {trace}";
Logger.Error(message);
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void SetTheme(bool useDarkMode)
{
DarkModeUtils.SetDarkMode(useDarkMode);
WinApi.RefreshImmersiveColorPolicyState();
DarkModeUtils.InitWpfTheme();
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor);
_mainForm?.SetTheme(useDarkMode);
}
internal static NotifyIcon GetNotifyIcon()
{
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
return notifyIconManager.NotifyIcon;
}
}
}
+69 -77
View File
@@ -1,77 +1,69 @@
{
"type":"register",
"id":"register_0",
"payload":{
"forcePairing":false,
"pairingType":"PROMPT",
"client-key":"CLIENTKEYGOESHERE",
"manifest":{
"manifestVersion":1,
"appVersion":"1.1",
"signed":{
"created":"20140509",
"appId":"com.lge.test",
"vendorId":"com.lge",
"localizedAppNames":{
"":"LG Remote App",
"ko-KR":"리모컨 앱",
"zxx-XX":"ЛГ Rэмotэ AПП"
},
"localizedVendorNames":{
"":"LG Electronics"
},
"permissions":[
"TEST_SECURE",
"CONTROL_INPUT_TEXT",
"CONTROL_MOUSE_AND_KEYBOARD",
"READ_INSTALLED_APPS",
"READ_LGE_SDX",
"READ_NOTIFICATIONS",
"SEARCH",
"WRITE_SETTINGS",
"WRITE_NOTIFICATION_ALERT",
"CONTROL_POWER",
"READ_CURRENT_CHANNEL",
"READ_RUNNING_APPS",
"READ_UPDATE_INFO",
"UPDATE_FROM_REMOTE_APP",
"READ_LGE_TV_INPUT_EVENTS",
"READ_TV_CURRENT_TIME"
],
"serial":"2f930e2d2cfe083771f68e4fe7bb07"
},
"permissions":[
"LAUNCH",
"LAUNCH_WEBAPP",
"APP_TO_APP",
"CLOSE",
"TEST_OPEN",
"TEST_PROTECTED",
"CONTROL_AUDIO",
"CONTROL_DISPLAY",
"CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_RECORDING",
"CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV",
"CONTROL_POWER",
"CONTROL_TV_SCREEN",
"READ_APP_STATUS",
"READ_CURRENT_CHANNEL",
"READ_INPUT_DEVICE_LIST",
"READ_NETWORK_STATE",
"READ_RUNNING_APPS",
"READ_TV_CHANNEL_LIST",
"WRITE_NOTIFICATION_TOAST",
"READ_POWER_STATE",
"READ_COUNTRY_INFO",
"READ_SETTINGS"
],
"signatures":[
{
"signatureVersion":1,
"signature":"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw=="
}
]
}
}
}
{
"type": "register",
"id": "register_0",
"payload": {
"forcePairing": false,
"pairingType": "PROMPT",
"client-key": "CLIENTKEYGOESHERE",
"manifest": {
"manifestVersion": 1,
"appVersion": "1.0",
"signed": {
"appId": "com.colorcontrol.app",
"created": "20260426",
"localizedAppNames": {
"": "ColorControl"
},
"localizedVendorNames": {
"": "ColorControl"
},
"permissions": [
"CONTROL_POWER",
"READ_POWER_STATE",
"READ_CURRENT_CHANNEL",
"READ_RUNNING_APPS",
"READ_NETWORK_STATE",
"READ_INPUT_DEVICE_LIST",
"READ_TV_CHANNEL_LIST",
"READ_SETTINGS",
"READ_COUNTRY_INFO",
"WRITE_NOTIFICATION_TOAST",
"CONTROL_AUDIO",
"CONTROL_DISPLAY",
"CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV",
"CONTROL_TV_SCREEN",
"LAUNCH",
"LAUNCH_WEBAPP",
"APP_TO_APP",
"CLOSE"
],
"serial": "colorcontrol-webos26"
},
"permissions": [
"LAUNCH",
"LAUNCH_WEBAPP",
"APP_TO_APP",
"CLOSE",
"CONTROL_AUDIO",
"CONTROL_DISPLAY",
"CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV",
"CONTROL_POWER",
"CONTROL_TV_SCREEN",
"READ_APP_STATUS",
"READ_CURRENT_CHANNEL",
"READ_INPUT_DEVICE_LIST",
"READ_NETWORK_STATE",
"READ_RUNNING_APPS",
"READ_TV_CHANNEL_LIST",
"WRITE_NOTIFICATION_TOAST",
"READ_POWER_STATE",
"READ_COUNTRY_INFO",
"READ_SETTINGS"
]
}
}
}
@@ -0,0 +1,77 @@
{
"type":"register",
"id":"register_0",
"payload":{
"forcePairing":false,
"pairingType":"PROMPT",
"client-key":"CLIENTKEYGOESHERE",
"manifest":{
"manifestVersion":1,
"appVersion":"1.1",
"signed":{
"created":"20140509",
"appId":"com.lge.test",
"vendorId":"com.lge",
"localizedAppNames":{
"":"LG Remote App",
"ko-KR":"리모컨 앱",
"zxx-XX":"ЛГ Rэмotэ AПП"
},
"localizedVendorNames":{
"":"LG Electronics"
},
"permissions":[
"TEST_SECURE",
"CONTROL_INPUT_TEXT",
"CONTROL_MOUSE_AND_KEYBOARD",
"READ_INSTALLED_APPS",
"READ_LGE_SDX",
"READ_NOTIFICATIONS",
"SEARCH",
"WRITE_SETTINGS",
"WRITE_NOTIFICATION_ALERT",
"CONTROL_POWER",
"READ_CURRENT_CHANNEL",
"READ_RUNNING_APPS",
"READ_UPDATE_INFO",
"UPDATE_FROM_REMOTE_APP",
"READ_LGE_TV_INPUT_EVENTS",
"READ_TV_CURRENT_TIME"
],
"serial":"2f930e2d2cfe083771f68e4fe7bb07"
},
"permissions":[
"LAUNCH",
"LAUNCH_WEBAPP",
"APP_TO_APP",
"CLOSE",
"TEST_OPEN",
"TEST_PROTECTED",
"CONTROL_AUDIO",
"CONTROL_DISPLAY",
"CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_RECORDING",
"CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV",
"CONTROL_POWER",
"CONTROL_TV_SCREEN",
"READ_APP_STATUS",
"READ_CURRENT_CHANNEL",
"READ_INPUT_DEVICE_LIST",
"READ_NETWORK_STATE",
"READ_RUNNING_APPS",
"READ_TV_CHANNEL_LIST",
"WRITE_NOTIFICATION_TOAST",
"READ_POWER_STATE",
"READ_COUNTRY_INFO",
"READ_SETTINGS"
],
"signatures":[
{
"signatureVersion":1,
"signature":"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw=="
}
]
}
}
}
+1
View File
@@ -0,0 +1 @@
[{"Name":"MR20GA","ImageFileName":"LG_MR20GA.jpg","Buttons":[{"Name":null,"Button":23,"Preset":null,"Top":32.506480223994814,"Left":16.7759},{"Name":null,"Button":24,"Preset":null,"Top":39.65128022399482,"Left":17.6379},{"Name":null,"Button":12,"Preset":null,"Top":13.376880223994814,"Left":19.3621},{"Name":null,"Button":13,"Preset":null,"Top":13.376880223994814,"Left":41.7759},{"Name":null,"Button":22,"Preset":null,"Top":7.038780223994813,"Left":22.8103},{"Name":null,"Button":14,"Preset":null,"Top":13.376880223994814,"Left":64.6207},{"Name":null,"Button":15,"Preset":null,"Top":17.986380223994814,"Left":19.3621},{"Name":null,"Button":16,"Preset":null,"Top":17.986380223994814,"Left":41.7759},{"Name":null,"Button":17,"Preset":null,"Top":17.986380223994814,"Left":65.0517},{"Name":null,"Button":18,"Preset":null,"Top":22.365480223994815,"Left":19.3621},{"Name":null,"Button":19,"Preset":null,"Top":22.365480223994815,"Left":41.7759},{"Name":null,"Button":20,"Preset":null,"Top":22.365480223994815,"Left":65.0517},{"Name":null,"Button":21,"Preset":null,"Top":26.629280223994815,"Left":41.7759},{"Name":null,"Button":32,"Preset":null,"Top":26.744580223994816,"Left":64.6207},{"Name":null,"Button":25,"Preset":null,"Top":32.39128022399481,"Left":42.6379},{"Name":null,"Button":0,"Preset":null,"Top":44.49128022399481,"Left":12.465499999999999},{"Name":null,"Button":26,"Preset":null,"Top":44.49128022399481,"Left":72.3793},{"Name":null,"Button":29,"Preset":null,"Top":32.506480223994814,"Left":67.2069},{"Name":null,"Button":30,"Preset":null,"Top":39.42078022399482,"Left":67.6379},{"Name":null,"Button":4,"Preset":null,"Top":48.17888022399482,"Left":42.2069},{"Name":null,"Button":5,"Preset":null,"Top":61.89228022399482,"Left":43.069},{"Name":null,"Button":6,"Preset":null,"Top":55.093180223994814,"Left":17.2069},{"Name":null,"Button":7,"Preset":null,"Top":55.20848022399482,"Left":68.931},{"Name":null,"Button":2,"Preset":null,"Top":54.97798022399481,"Left":43.069},{"Name":null,"Button":1,"Preset":null,"Top":65.0036802239948,"Left":12.8966},{"Name":null,"Button":40,"Preset":null,"Top":64.7732802239948,"Left":72.8103},{"Name":null,"Button":42,"Preset":null,"Top":70.1894802239948,"Left":15.482800000000001},{"Name":null,"Button":41,"Preset":null,"Top":69.95898022399481,"Left":71.5172},{"Name":null,"Button":35,"Preset":null,"Top":79.6389802239948,"Left":44.3621},{"Name":null,"Button":31,"Preset":null,"Top":26.744580223994816,"Left":18.931},{"Name":null,"Button":36,"Preset":null,"Top":79.86948022399481,"Left":66.3448},{"Name":null,"Button":8,"Preset":null,"Top":75.2599802239948,"Left":15.0517},{"Name":null,"Button":9,"Preset":null,"Top":74.7989802239948,"Left":33.5862},{"Name":null,"Button":10,"Preset":null,"Top":75.14468022399481,"Left":52.9828},{"Name":null,"Button":11,"Preset":null,"Top":75.02948022399481,"Left":71.0862}]},{"Name":"MR23GN","ImageFileName":"LG_MR23GN.jpg","Buttons":[{"Name":null,"Button":22,"Preset":null,"Top":3.2806882676774514,"Left":30.569000000000003},{"Name":null,"Button":12,"Preset":null,"Top":8.671588267677452,"Left":16.3448},{"Name":null,"Button":13,"Preset":null,"Top":8.671588267677452,"Left":43.931},{"Name":null,"Button":14,"Preset":null,"Top":8.671588267677452,"Left":71.0862},{"Name":null,"Button":15,"Preset":null,"Top":13.581088267677451,"Left":16.3448},{"Name":null,"Button":16,"Preset":null,"Top":13.773588267677452,"Left":43.931},{"Name":null,"Button":17,"Preset":null,"Top":13.869888267677451,"Left":71.5172},{"Name":null,"Button":18,"Preset":null,"Top":18.77938826767745,"Left":16.3448},{"Name":null,"Button":19,"Preset":null,"Top":18.68318826767745,"Left":43.931},{"Name":null,"Button":20,"Preset":null,"Top":18.68318826767745,"Left":71.0862},{"Name":null,"Button":21,"Preset":null,"Top":23.88148826767745,"Left":43.931},{"Name":null,"Button":40,"Preset":null,"Top":23.78518826767745,"Left":16.3448},{"Name":null,"Button":32,"Preset":null,"Top":24.073988267677453,"Left":70.6552},{"Name":null,"Button":23,"Preset":null,"Top":29.272288267677453,"Left":15.482800000000001},{"Name":null,"Button":24,"Preset":null,"Top":37.069788267677446,"Left":15.913799999999998},{"Name":null,"Button":25,"Preset":null,"Top":33.02668826767745,"Left":43.069},{"Name":null,"Button":29,"Preset":null,"Top":29.368588267677453,"Left":71.0862},{"Name":null,"Button":24,"Preset":null,"Top":37.16608826767745,"Left":71.0862},{"Name":null,"Button":0,"Preset":null,"Top":42.171888267677446,"Left":15.482800000000001},{"Name":null,"Button":2,"Preset":null,"Top":51.894688267677445,"Left":43.069},{"Name":null,"Button":6,"Preset":null,"Top":51.50958826767745,"Left":15.0517},{"Name":null,"Button":7,"Preset":null,"Top":51.894688267677445,"Left":70.2241},{"Name":null,"Button":4,"Preset":null,"Top":45.15608826767745,"Left":42.6379},{"Name":null,"Button":5,"Preset":null,"Top":57.86308826767745,"Left":42.6379},{"Name":null,"Button":26,"Preset":null,"Top":60.94358826767745,"Left":71.0862},{"Name":null,"Button":1,"Preset":null,"Top":60.94358826767745,"Left":15.051Line truncated
@@ -0,0 +1 @@
[{"Name":"DefaultRC","ImageFileName":"Samsung_BN59-01315B.jpg","Buttons":[{"Name":null,"Button":26,"Preset":null,"Top":10.623333333333333,"Left":16.0},{"Name":null,"Button":13,"Preset":null,"Top":4.25,"Left":66.2604},{"Name":null,"Button":27,"Preset":null,"Top":10.623333333333333,"Left":42.2249},{"Name":null,"Button":28,"Preset":null,"Top":10.623333333333333,"Left":66.2604},{"Name":null,"Button":0,"Preset":null,"Top":4.373333333333333,"Left":16.0},{"Name":null,"Button":22,"Preset":null,"Top":35.456633333333336,"Left":16.4852},{"Name":null,"Button":23,"Preset":null,"Top":43.873333333333335,"Left":16.7811},{"Name":null,"Button":24,"Preset":null,"Top":34.790033333333334,"Left":41.3373},{"Name":null,"Button":12,"Preset":null,"Top":79.87333333333333,"Left":11.1598},{"Name":null,"Button":8,"Preset":null,"Top":69.70663333333333,"Left":14.414200000000001},{"Name":null,"Button":7,"Preset":null,"Top":63.20663333333333,"Left":41.3373},{"Name":null,"Button":10,"Preset":null,"Top":49.540033333333334,"Left":41.3373},{"Name":null,"Button":9,"Preset":null,"Top":69.70663333333333,"Left":66.1893},{"Name":null,"Button":14,"Preset":null,"Top":56.206633333333336,"Left":65.5976},{"Name":null,"Button":16,"Preset":null,"Top":79.87333333333333,"Left":31.278100000000002},{"Name":null,"Button":1,"Preset":null,"Top":56.623333333333335,"Left":41.3373},{"Name":null,"Button":3,"Preset":null,"Top":63.20663333333333,"Left":15.3018},{"Name":null,"Button":4,"Preset":null,"Top":63.20663333333333,"Left":67.3728},{"Name":null,"Button":2,"Preset":null,"Top":69.70663333333333,"Left":41.3373},{"Name":null,"Button":29,"Preset":null,"Top":15.841666666666667,"Left":16.0},{"Name":null,"Button":30,"Preset":null,"Top":15.841666666666667,"Left":40.929},{"Name":null,"Button":31,"Preset":null,"Top":15.841666666666667,"Left":66.2604},{"Name":null,"Button":32,"Preset":null,"Top":21.508366666666664,"Left":16.0},{"Name":null,"Button":33,"Preset":null,"Top":21.508366666666664,"Left":41.3373},{"Name":null,"Button":34,"Preset":null,"Top":21.508366666666664,"Left":66.2604},{"Name":null,"Button":25,"Preset":null,"Top":27.008366666666664,"Left":41.3373},{"Name":null,"Button":17,"Preset":null,"Top":75.09166666666667,"Left":12.526599999999998},{"Name":null,"Button":18,"Preset":null,"Top":75.09166666666667,"Left":30.573999999999998},{"Name":null,"Button":19,"Preset":null,"Top":75.09166666666667,"Left":50.1006},{"Name":null,"Button":20,"Preset":null,"Top":75.09166666666667,"Left":68.1479},{"Name":null,"Button":11,"Preset":null,"Top":42.041965520561135,"Left":40.7273}]},{"Name":"ServiceRC","ImageFileName":"Samsung_AA81-00243A.jpg","Buttons":[{"Name":null,"Button":16,"Preset":null,"Top":71.10894129883307,"Left":22.3828},{"Name":null,"Button":45,"Preset":null,"Top":35.282841298833084,"Left":15.2984},{"Name":null,"Button":0,"Preset":null,"Top":7.5743412988330805,"Left":20.748},{"Name":null,"Button":13,"Preset":null,"Top":7.79084129883308,"Left":67.8869},{"Name":null,"Button":15,"Preset":null,"Top":17.965041298833082,"Left":73.3365},{"Name":null,"Button":9,"Preset":null,"Top":35.39104129883308,"Left":74.9714},{"Name":null,"Button":7,"Preset":null,"Top":27.05684129883308,"Left":44.4537},{"Name":null,"Button":1,"Preset":null,"Top":19.480341298833082,"Left":44.9986},{"Name":null,"Button":3,"Preset":null,"Top":26.94864129883308,"Left":25.6526},{"Name":null,"Button":4,"Preset":null,"Top":26.94864129883308,"Left":64.3447},{"Name":null,"Button":2,"Preset":null,"Top":34.41694129883308,"Left":45.5436},{"Name":null,"Button":12,"Preset":null,"Top":17.423841298833082,"Left":15.8433},{"Name":null,"Button":26,"Preset":null,"Top":51.30174129883308,"Left":22.3828},{"Name":null,"Button":27,"Preset":null,"Top":50.97704129883308,"Left":46.361},{"Name":null,"Button":28,"Preset":null,"Top":51.08524129883308,"Left":68.9768},{"Name":null,"Button":29,"Preset":null,"Top":57.90414129883308,"Left":22.6553},{"Name":null,"Button":30,"Preset":null,"Top":57.68764129883308,"Left":45.8161},{"Name":null,"Button":31,"Preset":null,"Top":57.79594129883308,"Left":68.7044},{"Name":null,"Button":32,"Preset":null,"Top":64.61474129883307,"Left":22.1104},{"Name":null,"Button":33,"Preset":null,"Top":64.50654129883307,"Left":45.2711},{"Name":null,"Button":34,"Preset":null,"Top":64.29004129883307,"Left":68.9768},{"Name":null,"Button":25,"Preset":null,"Top":70.89244129883308,"Left":45.8161}]}]
File diff suppressed because it is too large. Load diff
+76 -108
View File
@@ -37,6 +37,12 @@ namespace ColorControl.Services.AMD
LoadConfig();
}
protected override void AfterPresetsLoaded()
{
// Force disable display preset on presets
_presets.ForEach(p => p.IsDisplayPreset = false);
}
public static async Task<bool> ExecutePresetAsync(string idOrName)
{
try
@@ -66,6 +72,42 @@ namespace ColorControl.Services.AMD
base.InstallEventHandlers();
SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut);
var _ = ApplyAmdPresetOnStartup();
}
private async Task ApplyAmdPresetOnStartup(int attempts = 5)
{
var startUpParams = _globalContext.StartUpParams;
var presetIdOrName = !string.IsNullOrEmpty(startUpParams.AmdPresetIdOrName) ? startUpParams.AmdPresetIdOrName : _globalContext.Config.AmdPresetId_ApplyOnStartup.ToString();
if (!string.IsNullOrEmpty(presetIdOrName))
{
var preset = GetPresetByIdOrName(presetIdOrName);
if (preset == null)
{
if (string.IsNullOrEmpty(startUpParams.AmdPresetIdOrName))
{
_globalContext.Config.AmdPresetId_ApplyOnStartup = 0;
}
}
else
{
if (HasDisplaysAttached())
{
await ApplyPreset(preset);
}
else
{
attempts--;
if (attempts > 0)
{
await Task.Delay(2000);
await ApplyAmdPresetOnStartup(attempts);
}
}
}
}
}
private void LoadConfig()
@@ -95,11 +137,6 @@ namespace ColorControl.Services.AMD
return [$"{_presets.Count} presets"];
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
public void GlobalSave()
{
SavePresets();
@@ -141,14 +178,14 @@ namespace ColorControl.Services.AMD
return _currentDisplay;
}
public override async Task<bool> ApplyPreset(AmdPreset preset)
public override Task<bool> ApplyPreset(AmdPreset preset)
{
SetCurrentDisplay(preset);
if (string.IsNullOrEmpty(_currentDisplay.DisplayName))
{
Logger.Error("Could not set current display");
return false;
return Task.FromResult(false);
}
var result = true;
@@ -212,7 +249,7 @@ namespace ColorControl.Services.AMD
PresetApplied();
return result;
return Task.FromResult(result);
}
public bool SetDithering(ADLDitherState ditherState)
@@ -409,50 +446,6 @@ namespace ColorControl.Services.AMD
}
return displays.Select(d => new AmdDisplayInfo(d, null, null)).ToList();
//var list = new List<AmdDisplayInfo>();
//foreach (var display in displays)
//{
// var values = new List<string>
// {
// "Current settings"
// };
// var screen = GetScreenForDisplay(display);
// var name = GetFullDisplayName(display);
// values.Add(name);
// var colorDepth = ADLColorDepth.UNKNOWN;
// ADLWrapper.GetDisplayColorDepth(display, ref colorDepth);
// var pixelFormat = ADLPixelFormat.UNKNOWN;
// ADLWrapper.GetDisplayPixelFormat(display, ref pixelFormat);
// var colorSettings = $"{colorDepth.GetDescription()}, {pixelFormat.GetDescription()}";
// values.Add(colorSettings);
// var displayConfig = CCD.GetDisplayConfig(screen?.DeviceName);
// values.Add($"{displayConfig.RefreshRate}Hz");
// values.Add($"{displayConfig.GetResolutionDesc()}");
// var ditherState = GetDithering(display);
// values.Add(ditherState.GetDescription());
// var hdrEnabled = IsHDREnabled();
// values.Add(hdrEnabled ? "Yes" : "No");
// var infoLine = string.Format("{0}: {1}, {2}Hz, {3}, HDR: {4}", name, colorSettings, displayConfig.RefreshRate, displayConfig.GetResolutionDesc(), hdrEnabled ? "Yes" : "No");
// var displayInfo = new AmdDisplayInfo(display, values, infoLine);
// list.Add(displayInfo);
//}
//return list;
}
public List<AmdDisplayInfo> GetDisplayInfos()
@@ -472,68 +465,13 @@ namespace ColorControl.Services.AMD
}
return list;
//List<ADLDisplayInfo> displays;
//try
//{
// displays = GetDisplays();
//}
//catch (Exception e)
//{
// Logger.Error("Error while getting displays: " + e.ToLogString());
// return null;
//}
//var list = new List<AmdDisplayInfo>();
//foreach (var display in displays)
//{
// var values = new List<string>
// {
// "Current settings"
// };
// var screen = GetScreenForDisplay(display);
// var name = GetFullDisplayName(display);
// values.Add(name);
// var colorDepth = ADLColorDepth.UNKNOWN;
// ADLWrapper.GetDisplayColorDepth(display, ref colorDepth);
// var pixelFormat = ADLPixelFormat.UNKNOWN;
// ADLWrapper.GetDisplayPixelFormat(display, ref pixelFormat);
// var colorSettings = $"{colorDepth.GetDescription()}, {pixelFormat.GetDescription()}";
// values.Add(colorSettings);
// var displayConfig = CCD.GetDisplayConfig(screen?.DeviceName);
// values.Add($"{displayConfig.RefreshRate}Hz");
// values.Add($"{displayConfig.GetResolutionDesc()}");
// var ditherState = GetDithering(display);
// values.Add(ditherState.GetDescription());
// var hdrEnabled = IsHDREnabled();
// values.Add(hdrEnabled ? "Yes" : "No");
// var infoLine = string.Format("{0}: {1}, {2}Hz, {3}, HDR: {4}", name, colorSettings, displayConfig.RefreshRate, displayConfig.GetResolutionDesc(), hdrEnabled ? "Yes" : "No");
// var displayInfo = new AmdDisplayInfo(display, values, infoLine);
// list.Add(displayInfo);
//}
//return list;
}
protected override void Initialize()
{
if (!ADLWrapper.Initialize())
{
//throw new InvalidOperationException("Cannot load AMD API");
throw new InvalidOperationException("Cannot load AMD API");
}
}
@@ -626,6 +564,36 @@ namespace ColorControl.Services.AMD
SetShortcuts(SHORTCUTID_AMDQA, Config.QuickAccessShortcut);
SaveConfig();
return true;
}
public bool UpdatePreset(AmdPreset specPreset)
{
ValidatePreset(specPreset);
var currentPreset = _presets.FirstOrDefault(p => p.id == specPreset.id);
if (currentPreset != null)
{
currentPreset.name = specPreset.name;
currentPreset.shortcut = specPreset.shortcut;
currentPreset.Update(specPreset);
SavePresets(currentPreset);
return true;
}
var newPreset = new AmdPreset(specPreset);
newPreset.name = specPreset.name;
newPreset.shortcut = specPreset.shortcut;
_presets.Add(newPreset);
SavePresets(newPreset);
return true;
}
}
@@ -0,0 +1,399 @@
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, bool isHdrColorProfile = false)
{
return CCD.AddDisplayColorProfile(displayName, profileName, setAsDefault, isHdrColorProfile);
}
public bool RemoveDisplayColorProfile(string displayName, string profileName, bool isHdrColorProfile = false)
{
return CCD.RemoveDisplayColorProfile(displayName, profileName, isHdrColorProfile);
}
public bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true, bool isHdrColorProfile = false)
{
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, fixMaxTML, isHdrColorProfile);
}
public string GetDisplayDefaultColorProfile(string displayName, bool isHdrColorProfile)
{
return CCD.GetDisplayDefaultColorProfile(displayName, isHdrColorProfile);
}
public List<string> GetDisplayColorProfiles(string displayName, bool isHdrColorProfile = false)
{
if (displayName != null && !displayName.StartsWith("\\\\"))
{
displayName = GetDisplayNameByDisplayId(displayName);
}
return CCD.GetDisplayColorProfiles(displayName, isHdrColorProfile);
}
public List<ColorProfileDto> GetAllDisplayColorProfiles(string displayName)
{
if (displayName != null && !displayName.StartsWith("\\\\"))
{
displayName = GetDisplayNameByDisplayId(displayName);
}
var display = GetDisplayByGdiName(displayName);
if (display == null)
{
return [];
}
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(display.DisplayName);
return GetAllDisplayColorProfiles(display, advancedColor.isSupported);
}
public List<ColorProfileDto> GetAllDisplayColorProfiles(Display display, bool isHdrSupported = false)
{
var colorProfiles = GetDisplayColorProfiles(display.DisplayName, false).Select(p => new ColorProfileDto { Name = p, IsHDRProfile = false }).ToList();
if (isHdrSupported)
{
colorProfiles = colorProfiles.Union(GetDisplayColorProfiles(display.DisplayName, true).Select(p => new ColorProfileDto { Name = p, IsHDRProfile = true })).ToList();
}
return colorProfiles;
}
public List<string> GetAllColorProfileNames()
{
if (!_winApiService.IsAdministrator())
{
return _rpcClientService.Call<List<string>>(nameof(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 Display GetDisplayByGdiName(string displayName)
{
return Display.GetDisplays().FirstOrDefault(d => d.DisplayName == displayName);
}
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, string displayName = null)
{
var isAssociatedAsHdr = CCD.IsColorProfileAssociatedAsHdr(displayName, name);
var data = MHC2Wrapper.LoadProfile(name, isAssociatedAsHdr);
return new ColorProfileDto
{
Name = name,
RenderingIntent = data.RenderingIntent,
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,
ShadowDetailRange = data.ShadowDetailRange,
RedGain = data.RedGain,
GreenGain = data.GreenGain,
BlueGain = data.BlueGain,
WhiteLuminance = data.WhiteLuminance,
};
}
public bool UpdateColorProfile(ColorProfileDto colorProfile, string displayName = null)
{
var command = new GenerateProfileCommand
{
RenderingIntent = colorProfile.RenderingIntent,
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,
ShadowDetailRange = colorProfile.ShadowDetailRange,
RedGain = colorProfile.RedGain,
GreenGain = colorProfile.GreenGain,
BlueGain = colorProfile.BlueGain,
ColorGamut = colorProfile.ColorGamut,
Gamma = colorProfile.Gamma,
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
ToneMappingFromLuminance = colorProfile.ToneMappingFromLuminance,
ToneMappingToLuminance = colorProfile.ToneMappingToLuminance,
HdrBrightnessMultiplier = colorProfile.HdrBrightnessMultiplier,
HdrGammaMultiplier = colorProfile.HdrGammaMultiplier,
};
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, command.IsHDRProfile);
}
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, _globalContext.Config.SetMinTmlAndMaxTml, colorProfile.IsHDRProfile);
}
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
});
}
var colorProfiles = GetAllDisplayColorProfiles(d, advancedColor.isSupported);
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 = colorProfiles,
DefaultSdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, false),
DefaultHdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, true),
UsePerUserSettings = CCD.GetUsePerUserDisplayProfiles(d.DisplayName)
};
})
.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;
}
}
}
+334 -119
View File
@@ -3,136 +3,351 @@ using ColorControl.Shared.Contracts;
using ColorControl.Shared.Forms;
using ColorControl.Shared.Native;
using NWin32;
using NWin32.NativeTypes;
using Shared.Native;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using static ColorControl.Shared.Native.WinApi;
namespace ColorControl.Services.Common
namespace ColorControl.Services.Common;
abstract class GraphicsService<T> : ServiceBase<T> where T : PresetBase, new()
{
abstract class GraphicsService<T> : ServiceBase<T> where T : PresetBase, new()
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private int _lastSetSDRBrightness = -1;
private int _lastReadSDRBrightness = -1;
internal class SafePhysicalMonitorHandle : SafeHandle
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private int _lastSetSDRBrightness = -1;
private int _lastReadSDRBrightness = -1;
public GraphicsService(GlobalContext globalContext) : base(globalContext)
public SafePhysicalMonitorHandle(IntPtr handle) : base(IntPtr.Zero, true)
{
this.handle = handle; // IntPtr.Zero may be a valid handle.
}
~GraphicsService()
public override bool IsInvalid => false; // The validity cannot be checked by the handle.
protected override bool ReleaseHandle()
{
Uninitialize();
}
public abstract bool HasDisplaysAttached(bool reinitialize = false);
public bool SetMode(string displayName, VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false)
{
var displayConfig = CCD.GetDisplayConfig(displayName);
if (refreshRate != null)
{
displayConfig.ApplyRefreshRate = true;
displayConfig.RefreshRate = refreshRate;
}
if (resolution != null)
{
displayConfig.ApplyResolution = true;
displayConfig.Resolution = resolution;
}
return SetMode(displayName, displayConfig, updateRegistry);
}
public bool SetMode(string displayName, DisplayConfig displayConfig, bool updateRegistry = false)
{
var currentDisplayConfig = CCD.GetDisplayConfig(displayName);
if (displayConfig.ApplyRefreshRate)
{
currentDisplayConfig.RefreshRate = displayConfig.RefreshRate;
}
if (displayConfig.ApplyResolution)
{
currentDisplayConfig.Resolution = displayConfig.Resolution;
}
if (displayConfig.Scaling != CCD.DisplayConfigScaling.Zero)
{
currentDisplayConfig.Scaling = displayConfig.Scaling;
}
if (displayConfig.Rotation != CCD.DisplayConfigRotation.Zero)
{
currentDisplayConfig.Rotation = displayConfig.Rotation;
}
return CCD.SetDisplayConfig(displayName, currentDisplayConfig, updateRegistry);
}
public void SetSDRBrightness(string displayName, int brightnessPercent)
{
var hmon = FormUtils.GetMonitorForDisplayName(displayName);
if (hmon == IntPtr.Zero)
{
hmon = NativeMethods.MonitorFromWindow(0, 1);
}
var brightness = 1 + (double)brightnessPercent / 20;
WinApi.DwmpSDRToHDRBoostPtr(hmon, brightness);
_lastSetSDRBrightness = brightnessPercent;
}
public int GetSDRBrightness(string displayName)
{
var whiteLevel = CCD.GetSDRWhiteLevel(displayName);
if (whiteLevel >= 1000)
{
var newBrightnessPercent = (int)(whiteLevel - 1000) / 50;
if (_lastSetSDRBrightness >= 0 && newBrightnessPercent == _lastReadSDRBrightness)
{
return _lastSetSDRBrightness;
}
_lastReadSDRBrightness = newBrightnessPercent;
return newBrightnessPercent;
}
return 0;
}
protected List<Rational> GetAvailableRefreshRatesV2(string displayName, int horizontal, int vertical)
{
var dxWrapper = new DXWrapper();
var modes = dxWrapper.GetModes(displayName, (uint)horizontal, (uint)vertical);
var refreshRates = modes.Select(m => m.RefreshRate).DistinctBy(r => r.ToString())
.Select(r => new Rational(r.Numerator, r.Denominator));
return refreshRates.ToList();
}
protected List<VirtualResolution> GetAvailableResolutionsInternalV2(string displayName, Rational refreshRate = null)
{
var dxWrapper = new DXWrapper();
var modes = dxWrapper.GetModes(displayName);
var refreshRates = modes.DistinctBy(m => $"{m.Resolution.width}x{m.Resolution.height}")
.Select(m => new VirtualResolution(m.Resolution.width, m.Resolution.height));
return refreshRates.ToList();
return true;
}
}
public GraphicsService(GlobalContext globalContext) : base(globalContext)
{
}
~GraphicsService()
{
Uninitialize();
}
public abstract bool HasDisplaysAttached(bool reinitialize = false);
public bool SetMode(string displayName, VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false)
{
var displayConfig = CCD.GetDisplayConfig(displayName);
if (refreshRate != null)
{
displayConfig.ApplyRefreshRate = true;
displayConfig.RefreshRate = refreshRate;
}
if (resolution != null)
{
displayConfig.ApplyResolution = true;
displayConfig.Resolution = resolution;
}
return SetMode(displayName, displayConfig, updateRegistry);
}
public bool SetMode(string displayName, DisplayConfig displayConfig, bool updateRegistry = false)
{
var currentDisplayConfig = CCD.GetDisplayConfig(displayName);
if (displayConfig.ApplyRefreshRate)
{
currentDisplayConfig.RefreshRate = displayConfig.RefreshRate;
}
if (displayConfig.ApplyResolution)
{
currentDisplayConfig.Resolution = displayConfig.Resolution;
}
if (displayConfig.Scaling != CCD.DisplayConfigScaling.Zero)
{
currentDisplayConfig.Scaling = displayConfig.Scaling;
}
if (displayConfig.Rotation != CCD.DisplayConfigRotation.Zero)
{
currentDisplayConfig.Rotation = displayConfig.Rotation;
}
if (displayConfig.Connectivity != DisplayConnectivity.Unchanged)
{
currentDisplayConfig.Connectivity = displayConfig.Connectivity;
}
return CCD.SetDisplayConfig(displayName, currentDisplayConfig, updateRegistry);
}
public void SetSDRBrightness(string displayName, int brightnessPercent)
{
var hmon = FormUtils.GetMonitorForDisplayName(displayName);
if (hmon == IntPtr.Zero)
{
hmon = NativeMethods.MonitorFromWindow(0, 1);
}
var brightness = 1 + (double)brightnessPercent / 20;
WinApi.DwmpSDRToHDRBoostPtr(hmon, brightness);
_lastSetSDRBrightness = brightnessPercent;
}
public int GetSDRBrightness(string displayName)
{
var whiteLevel = CCD.GetSDRWhiteLevel(displayName);
if (whiteLevel >= 1000)
{
var newBrightnessPercent = (int)(whiteLevel - 1000) / 50;
if (_lastSetSDRBrightness >= 0 && newBrightnessPercent == _lastReadSDRBrightness)
{
return _lastSetSDRBrightness;
}
_lastReadSDRBrightness = newBrightnessPercent;
return newBrightnessPercent;
}
return 0;
}
protected List<Rational> GetAvailableRefreshRatesV2(string displayName, int horizontal, int vertical)
{
var dxWrapper = new DXWrapper();
var modes = dxWrapper.GetModes(displayName, (uint)horizontal, (uint)vertical);
var refreshRates = modes.Select(m => m.RefreshRate).DistinctBy(r => r.ToString())
.Select(r => new Rational(r.Numerator, r.Denominator));
return refreshRates.ToList();
}
protected List<VirtualResolution> GetAvailableResolutionsInternalV2(string displayName, Rational refreshRate = null)
{
var dxWrapper = new DXWrapper();
var modes = dxWrapper.GetModes(displayName);
var refreshRates = modes.DistinctBy(m => $"{m.Resolution.width}x{m.Resolution.height}")
.Select(m => new VirtualResolution(m.Resolution.width, m.Resolution.height));
return refreshRates.ToList();
}
protected bool SetMonitorScaling()
{
var devMode = new DEVMODEW();
devMode.dmSize = (ushort)Marshal.SizeOf<DEVMODEW>();
devMode.dmFields = NativeConstants.DM_LOGPIXELS;
devMode.dmLogPixels = 96 * 96 / 100;
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<DEVMODEW>());
Marshal.StructureToPtr(devMode, ptr, false);
if (NativeMethods.ChangeDisplaySettingsExW(null, ptr, 0, 0, 0) == NativeConstants.DISP_CHANGE_SUCCESSFUL)
{
var result = NativeMethods.SystemParametersInfoW(0x009F, uint.MaxValue - 6, 0, NativeConstants.SPIF_SENDCHANGE | NativeConstants.SPIF_UPDATEINIFILE);
}
return true;
}
protected static IEnumerable<SafePhysicalMonitorHandle> EnumeratePhysicalMonitors(IntPtr monitorHandle, bool verbose = false)
{
if (!GetNumberOfPhysicalMonitorsFromHMONITOR(
monitorHandle,
out uint count))
{
Debug.WriteLine($"Failed to get the number of physical monitors. {WinError.GetMessage()}");
yield break;
}
if (count == 0)
{
yield break;
}
var physicalMonitors = new PHYSICAL_MONITOR[count];
try
{
if (!GetPhysicalMonitorsFromHMONITOR(
monitorHandle,
count,
physicalMonitors))
{
Debug.WriteLine($"Failed to get an array of physical monitors. {WinError.GetMessage()}");
yield break;
}
int monitorIndex = 0;
foreach (var physicalMonitor in physicalMonitors)
{
var handle = new SafePhysicalMonitorHandle(physicalMonitor.hPhysicalMonitor);
//Debug.WriteLine($"Description: {physicalMonitor.szPhysicalMonitorDescription}");
//Debug.WriteLine($"Handle: {physicalMonitor.hPhysicalMonitor}");
yield return handle;
monitorIndex++;
}
}
finally
{
// The physical monitor handles should be destroyed at a later stage.
}
}
public static GenericResult<VcpInfo> ReadDDC(string displayName, byte vcpCode)
{
// Do this via: GetVCPFeatureAndVCPFeatureReply: https://learn.microsoft.com/en-us/windows/win32/api/lowlevelmonitorconfigurationapi/nf-lowlevelmonitorconfigurationapi-getvcpfeatureandvcpfeaturereply
var handle = GetPhysicalMonitorHandle(displayName);
if (handle == -1)
{
return null;
}
try
{
var result = GetVCPFeatureAndVCPFeatureReply(handle, vcpCode, out var codeType, out var currentValue, out var maxValue);
if (!result)
{
var (errorCode, msg) = WinError.GetCodeMessage();
Logger.Warn("Error while reading VCP code of display: {0}", msg);
return GenericResult<VcpInfo>.FromError(msg);
}
return GenericResult<VcpInfo>.FromSuccess(new VcpInfo
{
Value = currentValue,
MaxValue = maxValue,
CodeType = codeType
});
}
finally
{
//first.DangerousRelease();
}
}
public static async Task<GenericBoolResult> WriteDDC(string displayName, byte vcpCode, uint value, DdcSetting.ValueChangeTypes changeType = DdcSetting.ValueChangeTypes.Fixed, bool validate = true)
{
var handle = GetPhysicalMonitorHandle(displayName);
if (handle == -1)
{
return GenericBoolResult.FromError("No monitor handle");
}
if (changeType != DdcSetting.ValueChangeTypes.Fixed)
{
var readResult = ReadDDC(displayName, vcpCode);
if (!readResult.HasResult)
{
return GenericBoolResult.FromError("Cannot apply increase or decrease");
}
var currentValue = readResult.Result.Value;
if (changeType == DdcSetting.ValueChangeTypes.Increase)
{
value += currentValue;
if (value > readResult.Result.MaxValue)
{
value = readResult.Result.MaxValue;
}
}
else if (changeType == DdcSetting.ValueChangeTypes.Decrease)
{
if (((int)currentValue - value) < 0)
{
value = 0;
}
else
{
value = currentValue - value;
}
}
}
var result = SetVCPFeature(handle, vcpCode, value);
if (!result)
{
var (errorCode, msg) = WinError.GetCodeMessage();
Logger.Warn("Error while writing VCP code of display: {0}", msg);
return GenericBoolResult.FromError(msg);
}
if (validate)
{
await Task.Delay(50);
var readResult = ReadDDC(displayName, vcpCode);
if (readResult.Result?.Value != value)
{
return GenericBoolResult.FromError("The monitor did not apply the specified value");
}
}
return GenericBoolResult.Success;
}
public static nint GetPhysicalMonitorHandle(string displayName)
{
if (displayName == null)
{
return -1;
}
var hMonitor = FormUtils.GetMonitorForDisplayName(displayName);
var physicalHandles = EnumeratePhysicalMonitors(hMonitor);
var first = physicalHandles.FirstOrDefault();
if (first == null)
{
return -1;
}
return first.DangerousGetHandle();
}
}
+199 -199
View File
@@ -18,253 +18,253 @@ namespace ColorControl.Services.Common;
public class NotifyIconManager
{
private readonly GlobalContext _globalContext;
private readonly ServiceManager _serviceManager;
private readonly WinApiAdminService _winApiAdminService;
private readonly WinApiService _winApiService;
private readonly Config _config;
private readonly GlobalContext _globalContext;
private readonly ServiceManager _serviceManager;
private readonly WinApiAdminService _winApiAdminService;
private readonly WinApiService _winApiService;
private readonly Config _config;
public NotifyIcon NotifyIcon { get; private set; }
public NotifyIcon NotifyIcon { get; private set; }
private ToolStripMenuItem _nvTrayMenu;
private ToolStripMenuItem _novideoTrayMenu;
private ToolStripMenuItem _amdTrayMenu;
private ToolStripMenuItem _lgTrayMenu;
private ToolStripMenuItem _samsungTrayMenu;
private ToolStripMenuItem _gameTrayMenu;
private ToolStripMenuItem _nvTrayMenu;
private ToolStripMenuItem _novideoTrayMenu;
private ToolStripMenuItem _amdTrayMenu;
private ToolStripMenuItem _lgTrayMenu;
private ToolStripMenuItem _samsungTrayMenu;
private ToolStripMenuItem _gameTrayMenu;
private ToolStripMenuItem _stopServiceAndExitMenuItem;
private ToolStripMenuItem _openWinFormsMenuItem;
private ToolStripMenuItem _openNewUiMenuItem;
private ToolStripMenuItem _openNewUiEmbeddedMenuItem;
private ToolStripMenuItem _stopServiceAndExitMenuItem;
private ToolStripMenuItem _openWinFormsMenuItem;
private ToolStripMenuItem _openNewUiMenuItem;
private ToolStripMenuItem _openNewUiEmbeddedMenuItem;
public NotifyIconManager(GlobalContext globalContext, ServiceManager serviceManager, WinApiAdminService winApiAdminService, WinApiService winApiService)
{
_globalContext = globalContext;
_serviceManager = serviceManager;
_winApiAdminService = winApiAdminService;
_winApiService = winApiService;
_config = globalContext.Config;
}
public NotifyIconManager(GlobalContext globalContext, ServiceManager serviceManager, WinApiAdminService winApiAdminService, WinApiService winApiService)
{
_globalContext = globalContext;
_serviceManager = serviceManager;
_winApiAdminService = winApiAdminService;
_winApiService = winApiService;
_config = globalContext.Config;
}
public void Build()
{
if (_nvTrayMenu != null)
{
return;
}
public void Build()
{
if (_nvTrayMenu != null)
{
return;
}
_nvTrayMenu = new ToolStripMenuItem("NVIDIA presets");
_novideoTrayMenu = new ToolStripMenuItem("Novideo sRGB");
_amdTrayMenu = new ToolStripMenuItem("AMD presets");
_lgTrayMenu = new ToolStripMenuItem("LG presets");
_samsungTrayMenu = new ToolStripMenuItem("Samsung presets");
_gameTrayMenu = new ToolStripMenuItem("Game Launcher");
NotifyIcon = new NotifyIcon()
{
Icon = Resources.AppIcon,
ContextMenuStrip = new ContextMenuStrip(),
Visible = _config.MinimizeToTray
};
_nvTrayMenu = new ToolStripMenuItem("NVIDIA presets");
_novideoTrayMenu = new ToolStripMenuItem("Novideo sRGB");
_amdTrayMenu = new ToolStripMenuItem("AMD presets");
_lgTrayMenu = new ToolStripMenuItem("LG presets");
_samsungTrayMenu = new ToolStripMenuItem("Samsung presets");
_gameTrayMenu = new ToolStripMenuItem("Game Launcher");
NotifyIcon = new NotifyIcon()
{
Icon = Resources.AppIcon,
ContextMenuStrip = new ContextMenuStrip(),
Visible = _config.MinimizeToTray || _config.UiType != UiType.WinForms
};
SetText();
SetText();
NotifyIcon.ContextMenuStrip.Items.AddRange(new ToolStripItem[] {
_nvTrayMenu,
_novideoTrayMenu,
_amdTrayMenu,
_lgTrayMenu,
_samsungTrayMenu,
_gameTrayMenu,
new ToolStripSeparator(),
new ToolStripMenuItem("Open", null, OpenForm),
_openWinFormsMenuItem = new ToolStripMenuItem("Open WinForms UI", null, OpenWinFormsUi),
_openNewUiMenuItem = new ToolStripMenuItem("Open web UI (browser)", null, OpenNewUi),
_openNewUiEmbeddedMenuItem = new ToolStripMenuItem("Open web UI (embedded)", null, OpenNewUiEmbedded),
new ToolStripSeparator(),
new ToolStripMenuItem("Restart", null, Restart),
_stopServiceAndExitMenuItem = new ToolStripMenuItem("Stop Service And Exit", null, StopServiceAndExit),
new ToolStripMenuItem("Exit", null, Exit)
});
NotifyIcon.ContextMenuStrip.Items.AddRange(new ToolStripItem[] {
_nvTrayMenu,
_novideoTrayMenu,
_amdTrayMenu,
_lgTrayMenu,
_samsungTrayMenu,
_gameTrayMenu,
new ToolStripSeparator(),
new ToolStripMenuItem("Open", null, OpenForm),
_openWinFormsMenuItem = new ToolStripMenuItem("Open WinForms UI", null, OpenWinFormsUi),
_openNewUiMenuItem = new ToolStripMenuItem("Open web UI (browser)", null, OpenNewUi),
_openNewUiEmbeddedMenuItem = new ToolStripMenuItem("Open web UI (embedded)", null, OpenNewUiEmbedded),
new ToolStripSeparator(),
new ToolStripMenuItem("Restart", null, Restart),
_stopServiceAndExitMenuItem = new ToolStripMenuItem("Stop Service And Exit", null, StopServiceAndExit),
new ToolStripMenuItem("Exit", null, Exit)
});
NotifyIcon.MouseDoubleClick += trayIcon_MouseDoubleClick;
NotifyIcon.ContextMenuStrip.Opened += trayIconContextMenu_Popup;
}
NotifyIcon.MouseDoubleClick += trayIcon_MouseDoubleClick;
NotifyIcon.ContextMenuStrip.Opened += trayIconContextMenu_Popup;
}
public void HideIcon()
{
if (NotifyIcon != null)
{
NotifyIcon.Visible = false;
}
}
public void HideIcon()
{
if (NotifyIcon != null)
{
NotifyIcon.Visible = false;
}
}
private void StopServiceAndExit(object sender, EventArgs e)
{
_winApiAdminService.StopService();
private void StopServiceAndExit(object sender, EventArgs e)
{
_winApiAdminService.StopService();
Program.Exit();
}
Program.Exit();
}
private void Exit(object sender, EventArgs e)
{
Program.Exit();
}
private void Exit(object sender, EventArgs e)
{
Program.Exit();
}
private void Restart(object sender, EventArgs e)
{
Program.Restart();
}
private void Restart(object sender, EventArgs e)
{
Program.Restart();
}
private void OpenForm(object sender, EventArgs e)
{
Program.OpenDefaultUi();
}
private void OpenForm(object sender, EventArgs e)
{
Program.OpenDefaultUi();
}
private void OpenWinFormsUi(object sender, EventArgs e)
{
Program.OpenMainForm();
}
private void OpenWinFormsUi(object sender, EventArgs e)
{
_ = Program.OpenMainForm();
}
private async void OpenNewUi(object sender, EventArgs e)
{
await Program.OpenNewUi();
}
private async void OpenNewUi(object sender, EventArgs e)
{
await Program.OpenNewUi();
}
private async void OpenNewUiEmbedded(object sender, EventArgs e)
{
await Program.OpenNewUiEmbedded();
}
private async void OpenNewUiEmbedded(object sender, EventArgs e)
{
await Program.OpenNewUiEmbedded();
}
private void trayIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
Program.OpenDefaultUi();
}
private void trayIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
Program.OpenDefaultUi();
}
private void trayIconContextMenu_Popup(object sender, EventArgs e)
{
_nvTrayMenu.Visible = _serviceManager.NvService != null;
if (_nvTrayMenu.Visible)
{
var presets = _serviceManager.NvService.GetPresets().Where(x => x.applyColorData || x.applyDithering || x.applyHDR || x.DisplayConfig.ApplyRefreshRate || x.DisplayConfig.ApplyResolution || x.applyDriverSettings || x.applyOther || x.applyOverclocking || x.ApplyColorEnhancements || x.applyHdmiSettings);
private void trayIconContextMenu_Popup(object sender, EventArgs e)
{
_nvTrayMenu.Visible = _serviceManager.NvService != null;
if (_nvTrayMenu.Visible)
{
var presets = _serviceManager.NvService.GetPresets().Where(x => x.applyColorData || x.applyDithering || x.applyHDR || x.DisplayConfig.ApplyRefreshRate || x.DisplayConfig.ApplyResolution || x.applyDriverSettings || x.applyOther || x.applyOverclocking || x.ApplyColorEnhancements || x.applyHdmiSettings);
UpdateTrayMenu(_nvTrayMenu, presets, TrayMenuItemNv_Click);
}
UpdateTrayMenu(_nvTrayMenu, presets, TrayMenuItemNv_Click);
}
_novideoTrayMenu.Visible = _serviceManager.NvService != null && MainWindow.IsInitialized();
if (_novideoTrayMenu.Visible)
{
MainWindow.UpdateContextMenu(_novideoTrayMenu);
}
_novideoTrayMenu.Visible = _serviceManager.NvService != null && MainWindow.IsCreated();
if (_novideoTrayMenu.Visible)
{
MainWindow.UpdateContextMenu(_novideoTrayMenu);
}
_amdTrayMenu.Visible = _serviceManager.AmdService != null;
if (_amdTrayMenu.Visible)
{
var presets = _serviceManager.AmdService.GetPresets().Where(x => x.applyColorData || x.applyDithering || x.applyHDR || x.DisplayConfig.ApplyRefreshRate || x.DisplayConfig.ApplyResolution);
_amdTrayMenu.Visible = _serviceManager.AmdService != null;
if (_amdTrayMenu.Visible)
{
var presets = _serviceManager.AmdService.GetPresets().Where(x => x.applyColorData || x.applyDithering || x.applyHDR || x.DisplayConfig.ApplyRefreshRate || x.DisplayConfig.ApplyResolution);
UpdateTrayMenu(_amdTrayMenu, presets, TrayMenuItemAmd_Click);
}
UpdateTrayMenu(_amdTrayMenu, presets, TrayMenuItemAmd_Click);
}
_lgTrayMenu.Visible = _serviceManager.LgService != null;
if (_lgTrayMenu.Visible)
{
var presets = _serviceManager.LgService.GetPresets().Where(x => !string.IsNullOrEmpty(x.appId) || x.steps.Any());
_lgTrayMenu.Visible = _serviceManager.LgService != null;
if (_lgTrayMenu.Visible)
{
var presets = _serviceManager.LgService.GetPresets().Where(x => !string.IsNullOrEmpty(x.appId) || x.Steps.Any());
_lgTrayMenu.DropDownItems.Clear();
_lgTrayMenu.DropDownItems.Clear();
UpdateTrayMenu(_lgTrayMenu, presets, TrayMenuItemLg_Click);
}
UpdateTrayMenu(_lgTrayMenu, presets, TrayMenuItemLg_Click);
}
_samsungTrayMenu.Visible = _serviceManager.SamsungService != null;
if (_samsungTrayMenu.Visible)
{
var presets = _serviceManager.SamsungService.GetPresets().Where(x => x.Steps.Any());
_samsungTrayMenu.Visible = _serviceManager.SamsungService != null;
if (_samsungTrayMenu.Visible)
{
var presets = _serviceManager.SamsungService.GetPresets().Where(x => x.Steps.Any());
_samsungTrayMenu.DropDownItems.Clear();
_samsungTrayMenu.DropDownItems.Clear();
UpdateTrayMenu(_samsungTrayMenu, presets, TrayMenuItemSamsung_Click);
}
UpdateTrayMenu(_samsungTrayMenu, presets, TrayMenuItemSamsung_Click);
}
_gameTrayMenu.Visible = _serviceManager.GameService != null;
if (_gameTrayMenu.Visible)
{
var presets = _serviceManager.GameService.GetPresets();
_gameTrayMenu.Visible = _serviceManager.GameService != null;
if (_gameTrayMenu.Visible)
{
var presets = _serviceManager.GameService.GetPresets();
_gameTrayMenu.DropDownItems.Clear();
_gameTrayMenu.DropDownItems.Clear();
UpdateTrayMenu(_gameTrayMenu, presets, TrayMenuItemGame_Click);
}
UpdateTrayMenu(_gameTrayMenu, presets, TrayMenuItemGame_Click);
}
_stopServiceAndExitMenuItem.Visible = _winApiService.IsServiceRunning();
}
_stopServiceAndExitMenuItem.Visible = _winApiService.IsServiceRunning();
}
private void UpdateTrayMenu(ToolStripMenuItem menu, IEnumerable<PresetBase> presets, EventHandler eventHandler)
{
menu.DropDownItems.Clear();
private void UpdateTrayMenu(ToolStripMenuItem menu, IEnumerable<PresetBase> presets, EventHandler eventHandler)
{
menu.DropDownItems.Clear();
foreach (var preset in presets.OrderBy(p => p.name))
{
var name = preset.GetTextForMenuItem();
var keys = Keys.None;
foreach (var preset in presets.OrderBy(p => p.name))
{
var name = preset.GetTextForMenuItem();
var keys = Keys.None;
if (!string.IsNullOrEmpty(preset.shortcut))
{
name += " " + preset.shortcut;
//keys = KeyboardShortcutDispatcher.ShortcutToKeys(preset.shortcut);
}
if (!string.IsNullOrEmpty(preset.shortcut))
{
name += " " + preset.shortcut;
//keys = KeyboardShortcutDispatcher.ShortcutToKeys(preset.shortcut);
}
var item = new ToolStripMenuItem(name, null, null, keys);
item.Tag = preset;
item.Click += eventHandler;
item.ForeColor = FormUtils.MenuItemForeColor;
menu.DropDownItems.Add(item);
}
}
var item = new ToolStripMenuItem(name, null, null, keys);
item.Tag = preset;
item.Click += eventHandler;
item.ForeColor = FormUtils.MenuItemForeColor;
menu.DropDownItems.Add(item);
}
}
private async void TrayMenuItemNv_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (NvPreset)item.Tag;
private async void TrayMenuItemNv_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (NvPreset)item.Tag;
await _serviceManager.NvService?.ApplyPresetUi(preset);
}
await _serviceManager.NvService?.ApplyPresetUi(preset);
}
private async void TrayMenuItemAmd_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (AmdPreset)item.Tag;
private async void TrayMenuItemAmd_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (AmdPreset)item.Tag;
await _serviceManager.AmdService?.ApplyPresetUi(preset);
}
await _serviceManager.AmdService?.ApplyPresetUi(preset);
}
private async void TrayMenuItemGame_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (GamePreset)item.Tag;
private async void TrayMenuItemGame_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (GamePreset)item.Tag;
await _serviceManager.GameService?.ApplyPresetUi(preset);
}
await _serviceManager.GameService?.ApplyPresetUi(preset);
}
private async void TrayMenuItemLg_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (LgPreset)item.Tag;
private async void TrayMenuItemLg_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (LgPreset)item.Tag;
await _serviceManager.LgService?.ApplyPresetUi(preset);
}
await _serviceManager.LgService?.ApplyPresetUi(preset);
}
private async void TrayMenuItemSamsung_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (SamsungPreset)item.Tag;
private async void TrayMenuItemSamsung_Click(object sender, EventArgs e)
{
var item = sender as ToolStripMenuItem;
var preset = (SamsungPreset)item.Tag;
await _serviceManager.SamsungService?.ApplyPresetUi(preset);
}
await _serviceManager.SamsungService?.ApplyPresetUi(preset);
}
internal void SetText(string text = null)
{
var fullText = $"{_globalContext.ApplicationTitleAdmin}{(text == null ? "" : $"\n{text}")}";
internal void SetText(string text = null)
{
var fullText = $"{_globalContext.ApplicationTitleAdmin}{(text == null ? "" : $"\n{text}")}";
FormUtils.SetNotifyIconText(NotifyIcon, fullText.Trim());
}
FormUtils.SetNotifyIconText(NotifyIcon, fullText.Trim());
}
}
+55 -7
View File
@@ -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,11 +28,13 @@ public class OptionsService
_serviceManager = serviceManager;
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
_winApiAdminService = winApiAdminService;
_updateManager = updateManager;
}
public Config GetConfig()
{
_globalContext.Config.AutoStart = _winApiService.TaskExists(Program.TS_TASKNAME);
_globalContext.Config.CurrentUiPort = BlazorUiManager.GetCurrentPort(Program.Config);
return _globalContext.Config;
}
@@ -46,24 +50,50 @@ public class OptionsService
return true;
}
public bool SetCurrentUiSettings(int port, bool allowRemoteConnections)
{
_globalContext.Config.CurrentUiPort = port;
_globalContext.Config.CurrentUiAllowRemoteConnections = allowRemoteConnections;
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);
@@ -88,7 +118,7 @@ public class OptionsService
public bool SetMinimizeToTray(bool value)
{
_globalContext.Config.MinimizeToTray = value;
Program.GetNotifyIcon().Visible = _globalContext.Config.MinimizeToTray;
Program.GetNotifyIcon().Visible = _globalContext.Config.MinimizeToTray || _globalContext.Config.UiType != UiType.WinForms;
return true;
}
@@ -126,12 +156,14 @@ public class OptionsService
{
_globalContext.Config.UiType = uiType;
var _ = Program.StartOrStopUiServer();
return true;
}
public bool SetUiPort(int uiPort)
{
if (uiPort is <= 0 or > 65535)
if (uiPort is < 0 or > 65535)
{
return false;
}
@@ -143,6 +175,17 @@ public class OptionsService
_globalContext.Config.UiPort = uiPort;
var _ = Program.StartOrStopUiServer();
return true;
}
public bool SetUiAllowRemoteConnections(bool value)
{
_globalContext.Config.UiAllowRemoteConnections = value;
var _ = Program.StartOrStopUiServer();
return true;
}
@@ -216,9 +259,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;
}
}
+373 -340
View File
@@ -15,344 +15,377 @@ 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 KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
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 virtual 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);
}
private void LoadServices()
{
_keyboardShortcutDispatcher ??= _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
}
protected void SetShortcuts(int quickAccessShortcutId = 0, string shortcut = null)
{
_quickAccessShortcutId = quickAccessShortcutId;
LoadServices();
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"":[]");
json = json.Replace(@"""steps"":[", @"""Steps"":[");
_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;
}
_presets ??= [];
AfterPresetsLoaded();
}
protected virtual void AfterPresetsLoaded()
{
}
protected void ValidatePreset(PresetBase preset)
{
if (!KeyboardShortcutDispatcher.ValidateShortcut(preset.shortcut, false))
{
throw new InvalidOperationException("Invalid shortcut");
}
}
protected void SavePresets(PresetBase updatedPreset = null)
{
if (updatedPreset != null)
{
LoadServices();
_keyboardShortcutDispatcher?.RegisterShortcut(updatedPreset.id, updatedPreset.shortcut);
}
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)
+118 -71
View File
@@ -19,10 +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)
{
_globalContext = globalContext;
@@ -35,40 +31,104 @@ 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 bool UpdateAvailable() => _globalContext.UpdateInfo?.UpdateAvailable == true;
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 Task<bool> RestartAfterUpdate()
{
Program.Restart();
return Task.FromResult(true);
}
}
@@ -3,6 +3,7 @@ using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts.Game;
using ColorControl.Shared.EventDispatcher;
using ColorControl.Shared.Services;
using Microsoft.Win32;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
@@ -86,9 +87,11 @@ namespace ColorControl.Services.GameLauncher
}
}
private async Task<bool> HandleStartedProcess(Process process)
private Task<bool> HandleStartedProcess(Process process)
{
GamePreset preset;
GamePreset preset = null;
var presets = _presets.Where(p => p.AutoSettings.AllowAutoApply);
if (_winApiService.IsAdministrator())
{
@@ -96,22 +99,23 @@ namespace ColorControl.Services.GameLauncher
if (mainModule == null)
{
return false;
return Task.FromResult(false);
}
var fileName = mainModule.FileName;
preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
}
else
{
var name = process.ProcessName.Replace("-Win64-Shipping", "");
preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
preset = presets.FirstOrDefault(p => !Path.GetDirectoryName(p.Path).Equals("") && fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
}
if (preset == null || !preset.AutoSettings.AllowAutoApply)
if (preset == null)
{
return false;
var name = process.ProcessName.Replace("-Win64-Shipping", "");
preset = presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
}
if (preset == null)
{
return Task.FromResult(false);
}
if (_lastStartedProcessId != process.Id)
@@ -133,10 +137,10 @@ namespace ColorControl.Services.GameLauncher
var _ = ExecutePreset(preset, process, suspended ? mainThreadId : 0);
return true;
return Task.FromResult(true);
}
return false;
return Task.FromResult(false);
}
protected override List<GamePreset> GetDefaultPresets()
@@ -144,11 +148,6 @@ namespace ColorControl.Services.GameLauncher
return new List<GamePreset>();
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
public void GlobalSave()
{
SavePresets();
@@ -300,29 +299,45 @@ namespace ColorControl.Services.GameLauncher
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
SaveConfig();
return true;
}
public bool UpdatePreset(GamePreset presetSpec)
public override List<GamePreset> GetPresets()
{
var preset = _presets.FirstOrDefault(x => x.id == presetSpec.id);
_presets.ForEach(p =>
{
p.PathExists = p.Path?.Contains(":\\") != true || File.Exists(p.Path);
});
return _presets;
}
public bool UpdatePreset(GamePreset specPreset)
{
ValidatePreset(specPreset);
var preset = _presets.FirstOrDefault(x => x.id == specPreset.id);
if (preset != null)
{
preset.Update(presetSpec);
preset.Update(specPreset);
}
else
{
preset = new GamePreset(presetSpec);
preset = new GamePreset(specPreset);
_presets.Add(preset);
}
SavePresets(preset);
return true;
}
public string SelectPath()
{
var fileInfo = Utils.SelectFile();
var fileInfo = Utils.SelectFileFromNonUiThread(_globalContext.SynchronizationContext);
if (fileInfo == null)
{
@@ -331,5 +346,54 @@ namespace ColorControl.Services.GameLauncher
return fileInfo.FullName;
}
public List<GameApp> GetMruApps(bool skipExistingPresets = false)
{
var key = Registry.ClassesRoot.OpenSubKey(@"Local Settings\Software\Microsoft\Windows\Shell\MuiCache");
if (key == null)
{
return new List<GameApp>();
}
var names = key.GetValueNames();
var apps = new List<GameApp>();
var winFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
foreach (var name in names)
{
var parts = name.Split('.');
if (parts.Last() != "FriendlyAppName")
{
continue;
}
var path = name.Replace("." + parts.Last(), "");
if (skipExistingPresets && _presets.Any(p => p.Path.Equals(path, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
if (!path.StartsWith(winFolder) && Path.Exists(path))
{
var friendlyName = key.GetValue(name)?.ToString();
var app = new GameApp
{
Path = path,
Filename = Path.GetFileName(path),
FriendlyName = friendlyName
};
apps.Add(app);
}
}
apps = apps.DistinctBy(a => a.Path).OrderBy(a => a.FriendlyName).ToList();
return apps;
}
}
}
+46 -23
View File
@@ -69,7 +69,9 @@ namespace ColorControl.Services.LG
Series2020,
Series2021,
Series2022,
Series2023
Series2023,
Series2024,
Series2025
}
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
@@ -98,11 +100,15 @@ namespace ColorControl.Services.LG
public bool PowerOnAfterManualPowerOff { get; set; }
public bool PowerOnByWindows { get; set; }
public bool PowerOffByWindows { get; set; }
public bool PowerOnAfterSessionUnlock { get; set; }
public bool PowerOffAfterSessionLock { get; set; }
public bool TriggersEnabled { get; set; }
public int HDMIPortNumber { get; set; }
public bool UseSecureConnection { get; set; } = true;
public bool UseNewHandshake { get; set; }
private List<string> _actionsForGameBar;
public List<string> ActionsOnGameBar
@@ -226,20 +232,25 @@ namespace ColorControl.Services.LG
AddGenericPictureAction("lowLevelAdjustment", minValue: -30, maxValue: 30, category: "other", title: "Fine Tune Dark Areas", fromModelYear: ModelYear.Series2019);
AddGenericPictureAction("blackStabilizer", minValue: -30, maxValue: 30, category: "other", title: "Black Stabilizer", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("whiteStabilizer", minValue: -30, maxValue: 30, category: "other", title: "White Stabilizer", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("blueLight", typeof(BlueLight), category: "other", title: "Reduce Blue Light", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("blueLight", typeof(OffToLevel2), category: "other", title: "Reduce Blue Light", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("darkMode", typeof(OffToLevel2), category: "other", title: "Dark room Mode", fromModelYear: ModelYear.Series2022);
AddGenericPictureAction("noiseReduction", typeof(OffToHigh), title: "Noise Reduction");
AddGenericPictureAction("mpegNoiseReduction", typeof(OffToHigh), title: "MPEG Noise Reduction");
AddHdmiPictureAction("gameMode_hdmi", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI1", fromModelYear: ModelYear.Series2021);
AddHdmiPictureAction("gameMode_hdmi", typeof(OffToOn), category: "other", title: "Game Optimizer HDMIX", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("gameOptimization", typeof(OffToOn), category: "other", title: "VRR & G-Sync", fromModelYear: ModelYear.Series2021);
AddGenericPictureAction("inputOptimization", typeof(InputOptimization), category: "other", title: "Prevent Input Delay(Input Lag)", fromModelYear: ModelYear.Series2021);
AddHdmiPictureAction("gameOptimizationHDMI", typeof(OffToOn), category: "other");
AddHdmiPictureAction("gameOptimizationHDMI", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2019);
AddGenericPictureAction("freesync", typeof(OffToOn), category: "other", title: "AMD FreeSync Premium", fromModelYear: ModelYear.Series2020);
AddHdmiPictureAction("freesyncOLEDHDMI", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
AddGenericPictureAction("enableQFT", typeof(OffToOn), category: "other", title: "Quick Frame Transport", fromModelYear: ModelYear.Series2025);
AddGenericPictureAction("enable144HzBooster", typeof(OffToOn), category: "other", title: "144Hz/165Hz Mode", fromModelYear: ModelYear.Series2024);
AddGenericPictureAction("enableALLM", typeof(OffToOn), category: "other", title: "ALLM", fromModelYear: ModelYear.Series2022);
AddHdmiPictureAction("444BypassHDMI", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMIX", fromModelYear: ModelYear.Series2023);
AddGenericPictureAction("444BypassHDMINone", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through Non-HDMI", fromModelYear: ModelYear.Series2023);
AddGenericPictureAction("qmsVrr", typeof(OffToOn), category: "other", title: "QMS-VRR", fromModelYear: ModelYear.Series2023);
AddGenericPictureAction("enableDolbyVisionPC", typeof(OffToOn), category: "other", title: "Dolby Vision PC", fromModelYear: ModelYear.Series2024);
AddGenericPictureAction("masterLuminanceLevel", typeof(MasterLuminanceLevel), category: "other");
AddHdmiPictureAction("colorimetryHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020);
@@ -266,10 +277,10 @@ namespace ColorControl.Services.LG
AddInvokableAction("turnScreenOff", TurnScreenOffAction);
AddInvokableAction("turnScreenOn", TurnScreenOnAction);
AddInternalPresetAction(new LgPreset("InStart", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "inStart" }));
AddInternalPresetAction(new LgPreset("EzAdjust", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "ezAdjust" }));
AddInternalPresetAction(LgPresets.InStartPreset);
AddInternalPresetAction(LgPresets.EzAdjustPreset);
//AddInternalPresetAction(new LgPreset("PictureCheck", "com.webos.app.factorywin", null, new { id = "executeFactory", irKey = "pCheck" }));
AddInternalPresetAction(new LgPreset("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true }));
AddInternalPresetAction(LgPresets.SoftwareUpdatePreset);
AddSetDeviceConfigAction("HDMI_1_icon", typeof(HdmiIcon), "HDMI 1 icon");
AddSetDeviceConfigAction("HDMI_2_icon", typeof(HdmiIcon), "HDMI 2 icon");
@@ -302,6 +313,7 @@ namespace ColorControl.Services.LG
private void UpdateOptions(LgDeviceOptions options)
{
UseSecureConnection = options.UseSecureConnection;
UseNewHandshake = options.UseNewHandshake;
TurnScreenOffOnScreenSaver = options.TurnScreenOffOnScreenSaver;
TurnScreenOnAfterScreenSaver = options.TurnScreenOnAfterScreenSaver;
ScreenSaverMinimalDuration = options.ScreenSaverMinimalDuration;
@@ -315,6 +327,8 @@ namespace ColorControl.Services.LG
PowerOffByWindows = options.PowerOffByWindows;
PowerOnByWindows = options.PowerOnByWindows;
PowerOnAfterStartup = options.PowerOnAfterStartup;
PowerOnAfterSessionUnlock = options.PowerOnAfterSessionUnlock;
PowerOffAfterSessionLock = options.PowerOffAfterSessionLock;
TriggersEnabled = options.TriggersEnabled;
HDMIPortNumber = options.HDMIPortNumber;
}
@@ -489,21 +503,28 @@ namespace ColorControl.Services.LG
try
{
DisposeConnection();
_lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries, UseSecureConnection);
await DisposeConnection();
_lgTvApi = await LgTvApi.CreateLgTvApi(IpAddress, retries, UseSecureConnection, UseNewHandshake);
//Test();
//_lgTvApi.Test3();
if (_lgTvApi != null && !Utils.ConsoleOpened)
{
var info = await _lgTvApi.GetSystemInfo("modelName");
if (info != null)
try
{
ModelName = info.modelName;
SetModelYear();
}
var info = await _lgTvApi.GetSystemInfo("modelName");
if (info != null)
{
ModelName = info.modelName;
SetModelYear();
}
Logger.Debug($"LG TV with ip-address {IpAddress} has model name {ModelName}");
Logger.Debug($"LG TV with ip-address {IpAddress} has model name {ModelName}");
}
catch (Exception ex)
{
Logger.Warn($"Could not get model name for LG TV with ip-address {IpAddress}: {ex.Message}");
}
await _lgTvApi.SubscribeVolume(VolumeChanged);
await _lgTvApi.SubscribePowerState(PowerStateChanged);
@@ -566,6 +587,8 @@ namespace ColorControl.Services.LG
'1' => ModelYear.Series2021,
'2' => ModelYear.Series2022,
'3' => ModelYear.Series2023,
'4' => ModelYear.Series2024,
'5' => ModelYear.Series2025,
_ => ModelYear.None
};
@@ -681,11 +704,11 @@ namespace ColorControl.Services.LG
return !_lgTvApi?.ConnectionClosed ?? false;
}
private void DisposeConnection()
private async ValueTask DisposeConnection()
{
if (_lgTvApi != null)
{
_lgTvApi.Dispose();
await _lgTvApi.DisposeAsync();
_lgTvApi = null;
}
}
@@ -694,7 +717,7 @@ namespace ColorControl.Services.LG
{
var hasApp = !string.IsNullOrEmpty(preset.appId);
var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
var hasWOL = preset.Steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
if (hasWOL)
{
@@ -757,7 +780,7 @@ namespace ColorControl.Services.LG
}
}
if (preset.steps.Any())
if (preset.Steps.Any())
{
if (hasApp)
{
@@ -790,7 +813,7 @@ namespace ColorControl.Services.LG
{
LgWebOsMouseService mouse = null;
foreach (var step in preset.steps)
foreach (var step in preset.Steps)
{
var keySpec = step.Split(':');
@@ -1087,7 +1110,7 @@ namespace ColorControl.Services.LG
var actions = GetInvokableActions();
return actions.Where(a =>
a.Category == "picture" && (a.EnumType != null && a.EnumType != typeof(PictureMode) || a.MinValue >= 0 && a.MaxValue > 0) ||
a.Name == "audioVolume"
a.Name == "audioVolume" || (a.Category == "other" && (a.MinValue < a.MaxValue || a.EnumType != null))
)
.ToList();
}
@@ -1188,9 +1211,9 @@ namespace ColorControl.Services.LG
return await _lgTvApi.GetSystemSettings2("picture");
}
private async Task<bool> WakeAction(Dictionary<string, object> _)
private Task<bool> WakeAction(Dictionary<string, object> _)
{
return Wake();
return Task.FromResult(Wake());
}
private async Task<bool> TurnScreenOffAction(Dictionary<string, object> _)
+70 -40
View File
@@ -30,6 +30,8 @@
{
components = new System.ComponentModel.Container();
scLgController = new System.Windows.Forms.SplitContainer();
lblLgPresetConnectedDisplaysRegex = new System.Windows.Forms.Label();
edtLgPresetConnectedDisplaysRegex = new System.Windows.Forms.TextBox();
btnLgDeviceOptionsSettings = new System.Windows.Forms.Button();
btnLgSettings = new System.Windows.Forms.Button();
chkLgQuickAccess = new System.Windows.Forms.CheckBox();
@@ -95,6 +97,7 @@
mnuLgSettings = new System.Windows.Forms.ContextMenuStrip(components);
miTestPowerOffOn = new System.Windows.Forms.ToolStripMenuItem();
miNvSettings = new System.Windows.Forms.ToolStripMenuItem();
toolTipLgPreset = new System.Windows.Forms.ToolTip(components);
((System.ComponentModel.ISupportInitialize)scLgController).BeginInit();
scLgController.Panel1.SuspendLayout();
scLgController.SuspendLayout();
@@ -112,6 +115,8 @@
//
// scLgController.Panel1
//
scLgController.Panel1.Controls.Add(lblLgPresetConnectedDisplaysRegex);
scLgController.Panel1.Controls.Add(edtLgPresetConnectedDisplaysRegex);
scLgController.Panel1.Controls.Add(btnLgDeviceOptionsSettings);
scLgController.Panel1.Controls.Add(btnLgSettings);
scLgController.Panel1.Controls.Add(chkLgQuickAccess);
@@ -167,6 +172,28 @@
scLgController.SplitterWidth = 5;
scLgController.TabIndex = 43;
//
// lblLgPresetConnectedDisplaysRegex
//
lblLgPresetConnectedDisplaysRegex.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetConnectedDisplaysRegex.AutoSize = true;
lblLgPresetConnectedDisplaysRegex.Location = new System.Drawing.Point(475, 404);
lblLgPresetConnectedDisplaysRegex.Margin = new System.Windows.Forms.Padding(6, 0, 6, 0);
lblLgPresetConnectedDisplaysRegex.Name = "lblLgPresetConnectedDisplaysRegex";
lblLgPresetConnectedDisplaysRegex.Size = new System.Drawing.Size(148, 15);
lblLgPresetConnectedDisplaysRegex.TabIndex = 67;
lblLgPresetConnectedDisplaysRegex.Text = "Connected Displays Regex:";
//
// edtLgPresetConnectedDisplaysRegex
//
edtLgPresetConnectedDisplaysRegex.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtLgPresetConnectedDisplaysRegex.Enabled = false;
edtLgPresetConnectedDisplaysRegex.Location = new System.Drawing.Point(636, 398);
edtLgPresetConnectedDisplaysRegex.Margin = new System.Windows.Forms.Padding(6, 5, 6, 5);
edtLgPresetConnectedDisplaysRegex.Name = "edtLgPresetConnectedDisplaysRegex";
edtLgPresetConnectedDisplaysRegex.Size = new System.Drawing.Size(254, 23);
edtLgPresetConnectedDisplaysRegex.TabIndex = 66;
toolTipLgPreset.SetToolTip(edtLgPresetConnectedDisplaysRegex, "The preset will be executed only if the supplied Regular Expression matches the device name of at least one of the displays that are connected at the moment the trigger is fired.");
//
// btnLgDeviceOptionsSettings
//
btnLgDeviceOptionsSettings.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
@@ -183,7 +210,7 @@
// btnLgSettings
//
btnLgSettings.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
btnLgSettings.Location = new System.Drawing.Point(807, 284);
btnLgSettings.Location = new System.Drawing.Point(807, 240);
btnLgSettings.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnLgSettings.Name = "btnLgSettings";
btnLgSettings.Size = new System.Drawing.Size(88, 27);
@@ -197,7 +224,7 @@
chkLgQuickAccess.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
chkLgQuickAccess.AutoSize = true;
chkLgQuickAccess.Enabled = false;
chkLgQuickAccess.Location = new System.Drawing.Point(341, 320);
chkLgQuickAccess.Location = new System.Drawing.Point(341, 276);
chkLgQuickAccess.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkLgQuickAccess.Name = "chkLgQuickAccess";
chkLgQuickAccess.Size = new System.Drawing.Size(96, 19);
@@ -231,7 +258,7 @@
//
edtLgPresetDescription.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
edtLgPresetDescription.Enabled = false;
edtLgPresetDescription.Location = new System.Drawing.Point(100, 489);
edtLgPresetDescription.Location = new System.Drawing.Point(100, 481);
edtLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtLgPresetDescription.Multiline = true;
edtLgPresetDescription.Name = "edtLgPresetDescription";
@@ -242,7 +269,7 @@
//
lblLgPresetDescription.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetDescription.AutoSize = true;
lblLgPresetDescription.Location = new System.Drawing.Point(6, 494);
lblLgPresetDescription.Location = new System.Drawing.Point(6, 486);
lblLgPresetDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetDescription.Name = "lblLgPresetDescription";
lblLgPresetDescription.Size = new System.Drawing.Size(70, 15);
@@ -276,7 +303,7 @@
//
btnLgPresetEditTriggerConditions.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnLgPresetEditTriggerConditions.Enabled = false;
btnLgPresetEditTriggerConditions.Location = new System.Drawing.Point(804, 347);
btnLgPresetEditTriggerConditions.Location = new System.Drawing.Point(804, 303);
btnLgPresetEditTriggerConditions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnLgPresetEditTriggerConditions.Name = "btnLgPresetEditTriggerConditions";
btnLgPresetEditTriggerConditions.Size = new System.Drawing.Size(88, 27);
@@ -289,7 +316,7 @@
//
edtLgPresetTriggerConditions.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtLgPresetTriggerConditions.Enabled = false;
edtLgPresetTriggerConditions.Location = new System.Drawing.Point(601, 349);
edtLgPresetTriggerConditions.Location = new System.Drawing.Point(601, 305);
edtLgPresetTriggerConditions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtLgPresetTriggerConditions.Name = "edtLgPresetTriggerConditions";
edtLgPresetTriggerConditions.ReadOnly = true;
@@ -300,10 +327,10 @@
//
lblLgPresetExcludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetExcludedProcesses.AutoSize = true;
lblLgPresetExcludedProcesses.Location = new System.Drawing.Point(475, 414);
lblLgPresetExcludedProcesses.Location = new System.Drawing.Point(475, 370);
lblLgPresetExcludedProcesses.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetExcludedProcesses.Name = "lblLgPresetExcludedProcesses";
lblLgPresetExcludedProcesses.Size = new System.Drawing.Size(112, 15);
lblLgPresetExcludedProcesses.Size = new System.Drawing.Size(111, 15);
lblLgPresetExcludedProcesses.TabIndex = 52;
lblLgPresetExcludedProcesses.Text = "Excluded processes:";
//
@@ -311,7 +338,7 @@
//
edtLgPresetExcludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtLgPresetExcludedProcesses.Enabled = false;
edtLgPresetExcludedProcesses.Location = new System.Drawing.Point(601, 411);
edtLgPresetExcludedProcesses.Location = new System.Drawing.Point(601, 367);
edtLgPresetExcludedProcesses.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtLgPresetExcludedProcesses.Name = "edtLgPresetExcludedProcesses";
edtLgPresetExcludedProcesses.Size = new System.Drawing.Size(289, 23);
@@ -321,7 +348,7 @@
//
lblLgPresetIncludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetIncludedProcesses.AutoSize = true;
lblLgPresetIncludedProcesses.Location = new System.Drawing.Point(475, 384);
lblLgPresetIncludedProcesses.Location = new System.Drawing.Point(475, 340);
lblLgPresetIncludedProcesses.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetIncludedProcesses.Name = "lblLgPresetIncludedProcesses";
lblLgPresetIncludedProcesses.Size = new System.Drawing.Size(110, 15);
@@ -332,7 +359,7 @@
//
edtLgPresetIncludedProcesses.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtLgPresetIncludedProcesses.Enabled = false;
edtLgPresetIncludedProcesses.Location = new System.Drawing.Point(601, 381);
edtLgPresetIncludedProcesses.Location = new System.Drawing.Point(601, 337);
edtLgPresetIncludedProcesses.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtLgPresetIncludedProcesses.Name = "edtLgPresetIncludedProcesses";
edtLgPresetIncludedProcesses.Size = new System.Drawing.Size(289, 23);
@@ -342,7 +369,7 @@
//
lblLgPresetTriggerCondition.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetTriggerCondition.AutoSize = true;
lblLgPresetTriggerCondition.Location = new System.Drawing.Point(475, 353);
lblLgPresetTriggerCondition.Location = new System.Drawing.Point(475, 309);
lblLgPresetTriggerCondition.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetTriggerCondition.Name = "lblLgPresetTriggerCondition";
lblLgPresetTriggerCondition.Size = new System.Drawing.Size(63, 15);
@@ -355,7 +382,7 @@
cbxLgPresetTrigger.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
cbxLgPresetTrigger.Enabled = false;
cbxLgPresetTrigger.FormattingEnabled = true;
cbxLgPresetTrigger.Location = new System.Drawing.Point(601, 318);
cbxLgPresetTrigger.Location = new System.Drawing.Point(601, 274);
cbxLgPresetTrigger.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
cbxLgPresetTrigger.Name = "cbxLgPresetTrigger";
cbxLgPresetTrigger.Size = new System.Drawing.Size(289, 23);
@@ -365,10 +392,10 @@
//
lblLgPresetTrigger.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetTrigger.AutoSize = true;
lblLgPresetTrigger.Location = new System.Drawing.Point(475, 321);
lblLgPresetTrigger.Location = new System.Drawing.Point(475, 277);
lblLgPresetTrigger.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetTrigger.Name = "lblLgPresetTrigger";
lblLgPresetTrigger.Size = new System.Drawing.Size(46, 15);
lblLgPresetTrigger.Size = new System.Drawing.Size(47, 15);
lblLgPresetTrigger.TabIndex = 45;
lblLgPresetTrigger.Text = "Trigger:";
//
@@ -389,7 +416,7 @@
mnuLgExpert.ImageScalingSize = new System.Drawing.Size(20, 20);
mnuLgExpert.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuLgOLEDMotionPro, mnuLgSetSvcMenuFlag, miLgExpertSeparator1 });
mnuLgExpert.Name = "mnuLgButtons";
mnuLgExpert.Size = new System.Drawing.Size(328, 76);
mnuLgExpert.Size = new System.Drawing.Size(328, 54);
mnuLgExpert.Opening += mnuLgExpert_Opening;
//
// mnuLgOLEDMotionPro
@@ -423,14 +450,14 @@
// miLgFullServiceMenuEnable
//
miLgFullServiceMenuEnable.Name = "miLgFullServiceMenuEnable";
miLgFullServiceMenuEnable.Size = new System.Drawing.Size(180, 22);
miLgFullServiceMenuEnable.Size = new System.Drawing.Size(112, 22);
miLgFullServiceMenuEnable.Text = "Enable";
miLgFullServiceMenuEnable.Click += miLgFullServiceMenuEnable_Click;
//
// miLgFullServiceMenuDisable
//
miLgFullServiceMenuDisable.Name = "miLgFullServiceMenuDisable";
miLgFullServiceMenuDisable.Size = new System.Drawing.Size(180, 22);
miLgFullServiceMenuDisable.Size = new System.Drawing.Size(112, 22);
miLgFullServiceMenuDisable.Text = "Disable";
miLgFullServiceMenuDisable.Click += miLgFullServiceMenuDisable_Click;
//
@@ -492,7 +519,7 @@
cbxLgApps.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
cbxLgApps.Enabled = false;
cbxLgApps.FormattingEnabled = true;
cbxLgApps.Location = new System.Drawing.Point(100, 380);
cbxLgApps.Location = new System.Drawing.Point(100, 336);
cbxLgApps.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
cbxLgApps.Name = "cbxLgApps";
cbxLgApps.Size = new System.Drawing.Size(233, 23);
@@ -509,7 +536,7 @@
lvLgPresets.MultiSelect = false;
lvLgPresets.Name = "lvLgPresets";
lvLgPresets.ShowItemToolTips = true;
lvLgPresets.Size = new System.Drawing.Size(888, 126);
lvLgPresets.Size = new System.Drawing.Size(888, 82);
lvLgPresets.TabIndex = 8;
lvLgPresets.UseCompatibleStateImageBehavior = false;
lvLgPresets.View = System.Windows.Forms.View.Details;
@@ -522,7 +549,7 @@
//
label5.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
label5.AutoSize = true;
label5.Location = new System.Drawing.Point(6, 384);
label5.Location = new System.Drawing.Point(6, 340);
label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label5.Name = "label5";
label5.Size = new System.Drawing.Size(32, 15);
@@ -535,7 +562,7 @@
cbxLgPresetDevice.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
cbxLgPresetDevice.Enabled = false;
cbxLgPresetDevice.FormattingEnabled = true;
cbxLgPresetDevice.Location = new System.Drawing.Point(100, 348);
cbxLgPresetDevice.Location = new System.Drawing.Point(100, 304);
cbxLgPresetDevice.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
cbxLgPresetDevice.Name = "cbxLgPresetDevice";
cbxLgPresetDevice.Size = new System.Drawing.Size(370, 23);
@@ -545,7 +572,7 @@
//
btnDeleteLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnDeleteLg.Enabled = false;
btnDeleteLg.Location = new System.Drawing.Point(289, 284);
btnDeleteLg.Location = new System.Drawing.Point(289, 240);
btnDeleteLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnDeleteLg.Name = "btnDeleteLg";
btnDeleteLg.Size = new System.Drawing.Size(88, 27);
@@ -558,7 +585,7 @@
//
btnApplyLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnApplyLg.Enabled = false;
btnApplyLg.Location = new System.Drawing.Point(6, 284);
btnApplyLg.Location = new System.Drawing.Point(6, 240);
btnApplyLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnApplyLg.Name = "btnApplyLg";
btnApplyLg.Size = new System.Drawing.Size(88, 27);
@@ -581,7 +608,7 @@
//
lblLgPresetDevice.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblLgPresetDevice.AutoSize = true;
lblLgPresetDevice.Location = new System.Drawing.Point(6, 351);
lblLgPresetDevice.Location = new System.Drawing.Point(6, 307);
lblLgPresetDevice.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblLgPresetDevice.Name = "lblLgPresetDevice";
lblLgPresetDevice.Size = new System.Drawing.Size(45, 15);
@@ -591,7 +618,7 @@
// btnAddLg
//
btnAddLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnAddLg.Location = new System.Drawing.Point(195, 284);
btnAddLg.Location = new System.Drawing.Point(195, 240);
btnAddLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnAddLg.Name = "btnAddLg";
btnAddLg.Size = new System.Drawing.Size(88, 27);
@@ -604,7 +631,7 @@
//
edtShortcutLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtShortcutLg.Enabled = false;
edtShortcutLg.Location = new System.Drawing.Point(100, 411);
edtShortcutLg.Location = new System.Drawing.Point(100, 367);
edtShortcutLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtShortcutLg.Name = "edtShortcutLg";
edtShortcutLg.ReadOnly = true;
@@ -638,7 +665,7 @@
//
btnLgRefreshApps.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnLgRefreshApps.Enabled = false;
btnLgRefreshApps.Location = new System.Drawing.Point(341, 378);
btnLgRefreshApps.Location = new System.Drawing.Point(341, 334);
btnLgRefreshApps.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnLgRefreshApps.Name = "btnLgRefreshApps";
btnLgRefreshApps.Size = new System.Drawing.Size(88, 27);
@@ -651,7 +678,7 @@
//
label2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
label2.AutoSize = true;
label2.Location = new System.Drawing.Point(6, 415);
label2.Location = new System.Drawing.Point(6, 371);
label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label2.Name = "label2";
label2.Size = new System.Drawing.Size(55, 15);
@@ -662,7 +689,7 @@
//
label1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(6, 321);
label1.Location = new System.Drawing.Point(6, 277);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(42, 15);
@@ -684,7 +711,7 @@
//
edtNameLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
edtNameLg.Enabled = false;
edtNameLg.Location = new System.Drawing.Point(100, 318);
edtNameLg.Location = new System.Drawing.Point(100, 274);
edtNameLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtNameLg.Name = "edtNameLg";
edtNameLg.Size = new System.Drawing.Size(233, 23);
@@ -694,7 +721,7 @@
//
btnSetShortcutLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnSetShortcutLg.Enabled = false;
btnSetShortcutLg.Location = new System.Drawing.Point(384, 284);
btnSetShortcutLg.Location = new System.Drawing.Point(384, 240);
btnSetShortcutLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnSetShortcutLg.Name = "btnSetShortcutLg";
btnSetShortcutLg.Size = new System.Drawing.Size(88, 27);
@@ -707,7 +734,7 @@
//
edtStepsLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
edtStepsLg.Enabled = false;
edtStepsLg.Location = new System.Drawing.Point(100, 440);
edtStepsLg.Location = new System.Drawing.Point(100, 432);
edtStepsLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtStepsLg.Multiline = true;
edtStepsLg.Name = "edtStepsLg";
@@ -719,7 +746,7 @@
btnLgAddButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
btnLgAddButton.ContextMenuStrip = mnuLgButtons;
btnLgAddButton.Enabled = false;
btnLgAddButton.Location = new System.Drawing.Point(807, 439);
btnLgAddButton.Location = new System.Drawing.Point(807, 431);
btnLgAddButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnLgAddButton.Name = "btnLgAddButton";
btnLgAddButton.Size = new System.Drawing.Size(88, 27);
@@ -771,7 +798,7 @@
//
btnCloneLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
btnCloneLg.Enabled = false;
btnCloneLg.Location = new System.Drawing.Point(100, 284);
btnCloneLg.Location = new System.Drawing.Point(100, 240);
btnCloneLg.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnCloneLg.Name = "btnCloneLg";
btnCloneLg.Size = new System.Drawing.Size(88, 27);
@@ -784,7 +811,7 @@
//
lblStepsLg.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
lblStepsLg.AutoSize = true;
lblStepsLg.Location = new System.Drawing.Point(6, 445);
lblStepsLg.Location = new System.Drawing.Point(6, 437);
lblStepsLg.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblStepsLg.Name = "lblStepsLg";
lblStepsLg.Size = new System.Drawing.Size(38, 15);
@@ -807,7 +834,7 @@
clbLgPower.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
clbLgPower.ColumnWidth = 240;
clbLgPower.FormattingEnabled = true;
clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Use secure connection" });
clbLgPower.Items.AddRange(new object[] { "Power on after startup", "Power on after resume from standby", "Power off on shutdown", "Power off on standby", "Power off when screensaver activates", "Power on when screensaver deactivates", "Power on even after manual power off", "Allow triggers to be fired for this device", "Use Windows power settings to power on", "Use Windows power settings to power off", "Power off when locking the pc", "Power on when unlocking the pc", "Use secure connection", "Enable webOS 26 compatibility" });
clbLgPower.Location = new System.Drawing.Point(6, 70);
clbLgPower.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
clbLgPower.MultiColumn = true;
@@ -833,19 +860,19 @@
mnuLgSettings.ImageScalingSize = new System.Drawing.Size(20, 20);
mnuLgSettings.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miTestPowerOffOn, miNvSettings });
mnuLgSettings.Name = "mnuLgButtons";
mnuLgSettings.Size = new System.Drawing.Size(168, 48);
mnuLgSettings.Size = new System.Drawing.Size(169, 48);
//
// miTestPowerOffOn
//
miTestPowerOffOn.Name = "miTestPowerOffOn";
miTestPowerOffOn.Size = new System.Drawing.Size(167, 22);
miTestPowerOffOn.Size = new System.Drawing.Size(168, 22);
miTestPowerOffOn.Text = "Test power off/on";
miTestPowerOffOn.Click += miTestPowerOffOn_Click;
//
// miNvSettings
//
miNvSettings.Name = "miNvSettings";
miNvSettings.Size = new System.Drawing.Size(167, 22);
miNvSettings.Size = new System.Drawing.Size(168, 22);
miNvSettings.Text = "Settings";
miNvSettings.Click += miNvSettings_Click;
//
@@ -935,5 +962,8 @@
private System.Windows.Forms.ToolStripMenuItem mnuLgSetSvcMenuFlag;
private System.Windows.Forms.ToolStripMenuItem miLgFullServiceMenuEnable;
private System.Windows.Forms.ToolStripMenuItem miLgFullServiceMenuDisable;
private System.Windows.Forms.Label lblLgPresetConnectedDisplaysRegex;
private System.Windows.Forms.TextBox edtLgPresetConnectedDisplaysRegex;
private System.Windows.Forms.ToolTip toolTipLgPreset;
}
}
File diff suppressed because it is too large. Load diff
+5 -2
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Microsoft ResX Schema
Version 2.0
@@ -48,7 +48,7 @@
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
@@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolTipLgPreset.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>471, 16</value>
</metadata>
<metadata name="mnuLgExpert.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
+166 -27
View File
@@ -26,7 +26,8 @@ namespace ColorControl.Services.LG
{
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static string LgListAppsJson = "listApps.json";
public const string LgListAppsJson = "listApps.json";
public const string RemotesFilename = "LgRemotes.json";
public override string ServiceName => "LG";
@@ -53,6 +54,7 @@ namespace ColorControl.Services.LG
private readonly PowerEventDispatcher _powerEventDispatcher;
private readonly SessionSwitchDispatcher _sessionSwitchDispatcher;
private readonly ProcessEventDispatcher _processEventDispatcher;
private readonly DisplayChangeEventDispatcher _displayChangeEventDispatcher;
private readonly ServiceManager _serviceManager;
private LgDevice _selectedDevice;
private string _rcButtonsFilename;
@@ -64,20 +66,32 @@ namespace ColorControl.Services.LG
private RestartDetector _restartDetector;
private readonly WinApiService _winApiService;
private readonly WinApiAdminService _winApiAdminService;
private SynchronizationContext _syncContext;
private LgGameBar _gameBarForm;
private List<LgRcDto> _remotes;
private string _remotesFilename;
public ProcessChangedEventArgs CurrentProcessEvent => _processEventDispatcher?.MonitorContext;
public static readonly int SHORTCUTID_LGQA = -202;
public static readonly int SHORTCUTID_GAMEBAR = -101;
public LgService(GlobalContext globalContext, PowerEventDispatcher powerEventDispatcher, SessionSwitchDispatcher sessionSwitchDispatcher, ProcessEventDispatcher processEventDispatcher, ServiceManager serviceManager, RestartDetector restartDetector, WinApiService winApiService, WinApiAdminService winApiAdminService) : base(globalContext)
public LgService(
GlobalContext globalContext,
PowerEventDispatcher powerEventDispatcher,
SessionSwitchDispatcher sessionSwitchDispatcher,
ProcessEventDispatcher processEventDispatcher,
DisplayChangeEventDispatcher displayChangeEventDispatcher,
ServiceManager serviceManager,
RestartDetector restartDetector,
WinApiService winApiService,
WinApiAdminService winApiAdminService) : base(globalContext)
{
_allowPowerOn = globalContext.StartUpParams.RunningFromScheduledTask;
_powerEventDispatcher = powerEventDispatcher;
_sessionSwitchDispatcher = sessionSwitchDispatcher;
_processEventDispatcher = processEventDispatcher;
_displayChangeEventDispatcher = displayChangeEventDispatcher;
_serviceManager = serviceManager;
LgPreset.GetDeviceName += LgPreset_GetDeviceName;
LgPreset.GetAppName += LgPreset_GetAppName;
@@ -85,12 +99,79 @@ namespace ColorControl.Services.LG
_restartDetector = restartDetector;
_winApiService = winApiService;
_winApiAdminService = winApiAdminService;
_syncContext = globalContext.SynchronizationContext;
LgTvApiCore.SyncContext = _syncContext;
LoadConfig();
LoadPresets();
LoadRemoteControlButtons();
LoadRemotes();
}
private void LoadRemotes()
{
_remotesFilename = Path.Combine(_dataPath, RemotesFilename);
try
{
var remotesExists = File.Exists(_remotesFilename);
if (remotesExists)
{
try
{
var json = File.ReadAllText(_remotesFilename);
_remotes = JsonConvert.DeserializeObject<List<LgRcDto>>(json);
}
catch (Exception ex1)
{
Logger.Error($"Error while loading remotes, reverting to default remotes: {ex1.Message}");
}
}
}
catch (Exception ex)
{
Logger.Error($"General error while loading remotes, reverting to empty list: {ex.Message}");
}
_remotes ??= [];
var defaultRemotes = GetDefaultRemotes();
_remotes.AddRange(defaultRemotes.Where(r => !_remotes.Any(sr => sr.Name == r.Name)));
}
protected static List<LgRcDto> GetDefaultRemotes()
{
var json = Utils.GetResourceFile("LgRemotes.json");
return JsonConvert.DeserializeObject<List<LgRcDto>>(json);
}
public List<LgRcDto> GetRemotes()
{
return _remotes;
}
public bool UpdateRemote(LgRcDto remoteSpec)
{
var remote = _remotes.FirstOrDefault(r => r.Name == remoteSpec.Name);
if (remote != null)
{
remote.Update(remoteSpec);
}
else
{
_remotes.Add(remoteSpec);
}
GlobalSave();
return true;
}
public void DeleteRemote(string remoteName)
{
_remotes.RemoveAll(r => r.Name == remoteName);
}
private string LgPreset_GetAppName(string appId)
@@ -167,6 +248,7 @@ namespace ColorControl.Services.LG
SaveConfig();
SavePresets();
SaveRemoteControlButtons();
Utils.WriteObject(_remotesFilename, _remotes);
SendConfigToService();
}
@@ -289,17 +371,12 @@ namespace ColorControl.Services.LG
preset.shortcut = shortcut;
if (appId == null)
{
preset.steps.Add(step == null ? name : step);
preset.Steps.Add(step == null ? name : step);
}
return preset;
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
private void SaveConfig()
{
Config.PowerOnAfterStartup = Devices?.Any(d => d.PowerOnAfterStartup) ?? false;
@@ -312,7 +389,7 @@ namespace ColorControl.Services.LG
Utils.WriteObject(_rcButtonsFilename, _remoteControlButtons);
}
public async Task RefreshDevices(bool connect = true, bool afterStartUp = false)
public async Task<bool> RefreshDevices(bool connect = true, bool afterStartUp = false)
{
Devices = Config.Devices;
@@ -320,7 +397,7 @@ namespace ColorControl.Services.LG
{
var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress);
var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
var pnpDevices = Utils.GetPnpDevices(Config.DeviceSearchKey);
var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new LgDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList();
var autoIpAddresses = pnpDevices.Select(d => d.IpAddress);
@@ -350,9 +427,7 @@ namespace ColorControl.Services.LG
tempDevice.Wake();
await Task.Delay(4000);
await RefreshDevices();
return;
return await RefreshDevices();
}
foreach (var device in Devices)
@@ -369,9 +444,16 @@ namespace ColorControl.Services.LG
}
else
{
var _ = SelectedDevice.Connect();
var connectTask = SelectedDevice.Connect(afterStartUp ? 3 : 1);
if (!afterStartUp)
{
return await connectTask;
}
}
}
return true;
}
private void Device_PictureSettingsChangedEvent(object sender, EventArgs e)
@@ -464,10 +546,7 @@ namespace ColorControl.Services.LG
SetShortcuts(SHORTCUTID_LGQA, Config.QuickAccessShortcut);
var keyboardShortcutDispatcher = _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
if (keyboardShortcutDispatcher != null)
{
keyboardShortcutDispatcher.RegisterShortcut(SHORTCUTID_GAMEBAR, Config.GameBarShortcut);
}
keyboardShortcutDispatcher?.RegisterShortcut(SHORTCUTID_GAMEBAR, Config.GameBarShortcut);
_powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Startup, PowerStateChanged);
_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged);
@@ -477,8 +556,16 @@ namespace ColorControl.Services.LG
_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_MonitorOn, PowerModeChanged);
_sessionSwitchDispatcher.RegisterEventHandler(SessionSwitchDispatcher.Event_SessionSwitch, SessionSwitched);
_sessionSwitchDispatcher.RegisterAsyncEventHandler(SessionSwitchDispatcher.Event_SessionSwitch, SessionSwitchedAsync);
_processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged);
_displayChangeEventDispatcher.RegisterAsyncEventHandler(DisplayChangeEventDispatcher.Event_DisplayChanged, DisplayChanged);
}
private async Task DisplayChanged(object sender, DisplayChangedEventArgs e, CancellationToken ct)
{
await ExecutePresetsForEvent(PresetTriggerType.DisplayChange);
}
private async Task PowerStateChanged(object sender, PowerStateChangedEventArgs e, CancellationToken token)
@@ -517,6 +604,20 @@ namespace ColorControl.Services.LG
}
}
private async Task SessionSwitchedAsync(object sender, SessionSwitchEventArgs e, CancellationToken token)
{
if (e.Reason == SessionSwitchReason.SessionLock)
{
var devices = Devices.Where(d => d.PowerOffAfterSessionLock);
PowerOffDevices(devices, PowerOnOffState.StandBy);
}
else if (e.Reason == SessionSwitchReason.SessionUnlock)
{
var devices = Devices.Where(d => d.PowerOnAfterSessionUnlock).ToList();
await PowerOnDevicesTask(devices);
}
}
private async Task PowerModeResume(object sender, PowerStateChangedEventArgs e, CancellationToken _)
{
Logger.Debug($"PowerModeChanged: {e.State}");
@@ -540,7 +641,7 @@ namespace ColorControl.Services.LG
{
case PowerOnOffState.StandBy:
{
ExecutePresetsForEvent(PresetTriggerType.Standby);
_ = ExecutePresetsForEvent(PresetTriggerType.Standby);
var devices = Devices.Where(d => d.PowerOffOnStandby);
PowerOffDevices(devices, PowerOnOffState.StandBy);
@@ -548,7 +649,7 @@ namespace ColorControl.Services.LG
}
case PowerOnOffState.ShutDown:
{
ExecutePresetsForEvent(PresetTriggerType.Shutdown);
_ = ExecutePresetsForEvent(PresetTriggerType.Shutdown);
var devices = Devices.Where(d => d.PowerOffOnShutdown);
@@ -631,8 +732,14 @@ namespace ColorControl.Services.LG
return;
}
var applicableDevices = Devices.Where(d => d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver || d.TriggersEnabled && _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) &&
((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))).ToList();
var applicableDevices = Devices.Where(d =>
d.PowerOffOnScreenSaver
|| d.PowerOnAfterScreenSaver
|| (d.TriggersEnabled
// only these trigger types are ever triggered by a process changed event
&& _presets.Any(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch || t.Trigger == PresetTriggerType.ScreensaverStart || t.Trigger == PresetTriggerType.ScreensaverStop)
&& ((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase)))))
.ToList();
if (!applicableDevices.Any())
{
@@ -1061,11 +1168,14 @@ namespace ColorControl.Services.LG
PowerOnAfterScreenSaver = d.PowerOnAfterScreenSaver,
PowerOnAfterStartup = d.PowerOnAfterStartup,
PowerOnByWindows = d.PowerOnByWindows,
PowerOffAfterSessionLock = d.PowerOffAfterSessionLock,
PowerOnAfterSessionUnlock = d.PowerOnAfterSessionUnlock,
ScreenSaverMinimalDuration = d.ScreenSaverMinimalDuration,
TriggersEnabled = d.TriggersEnabled,
TurnScreenOffOnScreenSaver = d.TurnScreenOffOnScreenSaver,
TurnScreenOnAfterScreenSaver = d.TurnScreenOnAfterScreenSaver,
UseSecureConnection = d.UseSecureConnection
UseSecureConnection = d.UseSecureConnection,
UseNewHandshake = d.UseNewHandshake,
},
IsConnected = d.IsConnected(),
IsSelected = d == SelectedDevice
@@ -1097,6 +1207,8 @@ namespace ColorControl.Services.LG
}
}
SaveConfig();
return true;
}
@@ -1144,16 +1256,22 @@ namespace ColorControl.Services.LG
SetShortcuts(SHORTCUTID_LGQA, Config.QuickAccessShortcut);
SaveConfig();
return true;
}
public bool UpdatePreset(LgPreset specPreset)
{
ValidatePreset(specPreset);
var currentPreset = _presets.FirstOrDefault(p => p.id == specPreset.id);
if (currentPreset != null)
{
currentPreset.Update(specPreset);
SavePresets(currentPreset);
return true;
}
@@ -1162,6 +1280,8 @@ namespace ColorControl.Services.LG
_presets.Add(newPreset);
SavePresets(newPreset);
return true;
}
@@ -1220,9 +1340,28 @@ namespace ColorControl.Services.LG
return true;
}
public bool RemoveDevice(LgDeviceDto deviceDto)
{
if (deviceDto == null)
{
return false;
}
var device = MatchDevice(deviceDto);
if (device?.IsCustom != true)
{
return false;
}
RemoveCustomDevice(device);
return true;
}
private LgDevice MatchDevice(LgDeviceDto deviceDto)
{
return Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress);
return Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress && d.IpAddress == deviceDto.IpAddress) ??
Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress);
}
}
}
+44 -43
View File
@@ -3,49 +3,50 @@ using System.Collections.Generic;
namespace ColorControl.Services.LG
{
class LgServiceConfig
{
public bool PowerOnAfterStartup { get; set; }
public int PowerOnDelayAfterResume { get; set; }
public int PowerOnRetries { get; set; }
public string PreferredMacAddress { get; set; }
public string DeviceSearchKey { get; set; }
public List<LgDevice> Devices { get; set; }
public bool ShowRemoteControl { get; set; }
public bool ShowAdvancedActions { get; set; }
public string GameBarShortcut { get; set; }
public int GameBarTop { get; set; }
public int GameBarLeft { get; set; }
public int ShutdownDelay { get; set; }
public bool SetSelectedDeviceByPowerOn { get; set; }
public string QuickAccessShortcut { get; set; }
public int DefaultButtonDelay { get; set; }
public int GameBarShowingTime { get; set; }
class LgServiceConfig
{
public bool PowerOnAfterStartup { get; set; }
public int PowerOnDelayAfterResume { get; set; }
public int PowerOnRetries { get; set; }
public string PreferredMacAddress { get; set; }
public string DeviceSearchKey { get; set; }
public List<LgDevice> Devices { get; set; }
public bool ShowRemoteControl { get; set; }
public bool ShowAdvancedActions { get; set; }
public string GameBarShortcut { get; set; }
public int GameBarTop { get; set; }
public int GameBarLeft { get; set; }
public int ShutdownDelay { get; set; }
public bool SetSelectedDeviceByPowerOn { get; set; }
public string QuickAccessShortcut { get; set; }
public int DefaultButtonDelay { get; set; }
public int GameBarShowingTime { get; set; }
public LgServiceConfig()
{
PowerOnDelayAfterResume = 5000;
ShutdownDelay = 1000;
DeviceSearchKey = "[LG]";
PowerOnRetries = 10;
Devices = new List<LgDevice>();
GameBarTop = 100;
GameBarLeft = 100;
DefaultButtonDelay = 200;
GameBarShowingTime = 5000;
}
public LgServiceConfig()
{
PowerOnDelayAfterResume = 5000;
ShutdownDelay = 1000;
DeviceSearchKey = "[LG]";
PowerOnRetries = 10;
Devices = new List<LgDevice>();
GameBarTop = 100;
GameBarLeft = 100;
DefaultButtonDelay = 200;
GameBarShowingTime = 5000;
}
internal void Update(LgServiceConfigDto config)
{
PowerOnAfterStartup = config.PowerOnAfterStartup;
ShutdownDelay = config.ShutdownDelay;
DeviceSearchKey = config.DeviceSearchKey;
PowerOnRetries = config.PowerOnRetries;
DefaultButtonDelay = config.DefaultButtonDelay;
GameBarShowingTime = config.GameBarShowingTime;
ShowAdvancedActions = config.ShowAdvancedActions;
QuickAccessShortcut = config.QuickAccessShortcut;
SetSelectedDeviceByPowerOn = config.SetSelectedDeviceByPowerOn;
}
}
internal void Update(LgServiceConfigDto config)
{
PowerOnAfterStartup = config.PowerOnAfterStartup;
ShutdownDelay = config.ShutdownDelay;
DeviceSearchKey = config.DeviceSearchKey;
PowerOnRetries = config.PowerOnRetries;
DefaultButtonDelay = config.DefaultButtonDelay;
GameBarShowingTime = config.GameBarShowingTime;
ShowAdvancedActions = config.ShowAdvancedActions;
QuickAccessShortcut = config.QuickAccessShortcut;
SetSelectedDeviceByPowerOn = config.SetSelectedDeviceByPowerOn;
GameBarShortcut = config.GameBarShortcut;
}
}
}
@@ -69,7 +69,7 @@ namespace ColorControl.Services.LG
if (preset != null)
{
if (preset.steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active)
if (preset.Steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active)
{
Logger.Debug("Executing WOL instead of POWER");
File diff suppressed because it is too large. Load diff
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff. Show more