Compare commits

...
134 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
Vincent Maas 18f32e1d46 fix some final issues 2024-08-02 14:52:25 +02:00
Vincent Maas 4ed670c01b add pages for new UI, fix issues 2024-08-02 13:47:03 +02:00
Vincent Maas 9a9424f1af fix some issues and add test Blazor frontend 2024-05-10 13:35:35 +02:00
Vincent Maas 27bc0505b7 fix shutdown-event, add disable error popup setting, use better display id for Nvidia, fix refresh rate issues and improve background color 2024-04-21 11:48:04 +02:00
Vincent Maas 4f4ce54ade fix amd/nvidia refresh rate and resolution issues 2024-04-14 17:12:03 +02:00
Vincent Maas b9db10262b before refreshrate overhaul 2024-04-06 13:29:02 +02:00
Vincent Maas b8d64c7322 Merge branch 'master' of https://github.com/Maassoft/ColorControl 2024-04-02 21:49:05 +02:00
Vincent Maas fbd25f46b7 remove elevated form and app context provider 2024-04-02 21:49:02 +02:00
Maassoft 0c86466d7f Merge pull request #349 from WildFireFlum/feature/add-cs3-full-service-menu
Support enabling full service menu on LG CS3VA
2024-04-02 19:49:14 +02:00
Ynon Flum fd5a2aa7fe Add option to enable full service menu on LG CS3VA 2024-04-02 00:29:28 +03:00
Vincent Maas bc162af76e remove all old logic from main form and fix issues 2024-03-23 17:38:37 +01:00
Vincent Maas dd3a996172 use worker to start services and remove startup code from main form 2024-03-16 13:22:27 +01:00
Vincent Maas ab2ca92827 fix scaling issue after .NET 8 update 2024-03-12 22:35:46 +01:00
Vincent Maas f08dcb7661 update to .NET 8, fix conditions, add Nvidia RTX settings, add LG actions, improve Game Bar 2024-03-10 17:07:55 +01:00
Vincent Maas b1120c8d49 add creation of SDR color profile, add NVIDIA HDR10+ support, fix MinTML/MaxTML, add LG TPC/GSR actions, add LG/Samsung trigger events 2024-02-11 14:38:53 +01:00
Vincent Maas c958e6cb04 remove Nvapi dependency when reading EDID, replace debug version of lcms2.dll with release version 2023-12-29 14:20:14 +01:00
Vincent Maas 5611c1bea7 add Nvidia digital vibrance/hue control, add creation of custom color profile, add support for LG A2/A3 service menu unlocker 2023-12-28 21:48:33 +01:00
Vincent Maas 6b3b0b4b17 implemented lazy loading of Info panel, fixed some Nvidia issues, add support for 2022 series OLED full service menu activation 2023-12-09 14:26:26 +01:00
Vincent Maas bc565abd38 fix auto-start not working, add support for color profiles, add B3 as supported models for service menu unlock 2023-11-25 11:47:52 +01:00
Vincent Maas db19a9ad68 add Nvidia scaling setting 2023-11-05 15:30:22 +01:00
Vincent Maas 9fd8e8f9bb fix Novideo, add Game launcher auto-apply, add some LG actions, move winapi/admin functions to designated services 2023-10-28 14:53:17 +02:00
Vincent Maas 6eae7db459 fix Nvidia error, add HDMI settings, add LG action 2023-08-26 13:33:14 +02:00
Vincent Maas 2c5b08af72 fix modules, add raw input, add service menu to Samsung controller 2023-07-15 14:31:52 +02:00
Vincent Maas 3c5c2b3e4e fix error with dark mode, add clone functionality of current settings, add SDR brightness setting 2023-07-01 17:37:49 +02:00
Vincent Maas c6f9bedcd2 fix crash due to fetching resource from wrong assembly 2023-06-28 14:01:08 +02:00
Vincent Maas de3a1a6e72 uncomment code 2023-06-28 10:44:29 +02:00
Vincent Maas 966b8336de fixed final content type issues and message form width 2023-06-28 10:35:40 +02:00
Vincent Maas bc102334c8 fix xaml, merge latest Nvinspector changes 2023-06-27 22:34:09 +02:00
Vincent Maas 4186d4add5 move shared code to shared project, fix most xaml issues 2023-06-27 19:24:27 +02:00
Vincent Maas e0d1ba13f0 add Nvidia content type setting, start creating base Xaml window 2023-06-27 09:59:24 +02:00
Vincent Maas 21bd8e2080 add missing samsung presets to game launcher 2023-06-25 15:11:08 +02:00
Vincent Maas 3086058ebe fix package version 2023-06-25 14:48:12 +02:00
Vincent Maas 464da6ccb8 fix final issues 2023-06-25 14:36:07 +02:00
Vincent Maas 7e1eb8bfa8 fix final Samsung issues 2023-06-24 14:29:01 +02:00
Vincent Maas 04d447a48b fix most dark mode issues of logging 2023-06-24 10:58:10 +02:00
Vincent Maas 3ef21278cd improve samsung controller, add NLogViewer 2023-06-19 16:29:26 +02:00
Vincent Maas 3d7abd991c add samsung service, fix some issues 2023-06-13 21:58:00 +02:00
Vincent Maas 97b931f433 add licensing 2023-05-27 14:46:23 +02:00
Vincent Maas 7543cca15d fixed #224, added HDMI signalling override and reboot actions, fixed #228, fixed #222 2023-05-22 22:19:23 +02:00
Vincent Maas 58e03313e0 add some 2023 oled settings 2023-05-12 19:25:41 +02:00
Vincent Maas fa013b056f fix issues 2023-05-06 15:38:30 +02:00
Vincent Maas 8c186679cf add dark mode, fix wol 2023-05-06 15:22:24 +02:00
Vincent Maas 047ba98067 fix auto-update, add async handlers, remove old wait for task code, final fix of combase32 crash, readd some LG actions, extend screen saver functionality 2023-04-28 14:35:58 +02:00
Vincent Maas db23e67742 fix crash and nvidia error 2023-04-21 20:11:20 +02:00
Vincent Maas 52fd635fda fix LG device settings not saving, upgrade to .NET 7 2023-04-15 10:37:08 +02:00
Vincent Maas 6318d37247 add modules and make them configurable, enable page up/down shortcuts 2023-04-01 10:29:31 +02:00
Vincent Maas dd083a5e9f improve message dialogs 2023-03-12 16:58:21 +01:00
Vincent Maas 4b6d687e4c move Nvidia dithering test to separate panel 2023-03-04 19:35:39 +01:00
Vincent Maas dbf5670bc3 move LG UI to separate panel 2023-03-04 14:30:13 +01:00
Vincent Maas dd64ba491b move game launcher to separate panel 2023-03-04 13:15:21 +01:00
Vincent Maas 04c6be729f move Amd UI to separate panel 2023-03-04 12:00:55 +01:00
Vincent Maas dc6da0e60d move Nvidia UI to separate panel 2023-03-03 18:59:56 +01:00
Vincent Maas 7fcf154601 add Nvidia overclocking options, integrate NvAPIWrapper/NvidiaML to enable and extend overclocking (curve), some other fixes 2023-03-03 14:40:56 +01:00
Vincent Maas 1c5095777c add some missing LG settings, add Nvidia driver settings, fix game launcher not executing LG presets 2023-02-09 20:00:04 +01:00
Vincent Maas 0322e7243d Merge branch 'master' of https://github.com/Maassoft/ColorControl 2023-01-19 19:09:08 +01:00
Vincent Maas 57a6a36d9b fix LG connection issues, improve color temperature setting, add rBAR driver settings 2023-01-19 19:08:59 +01:00
Maassoft db1ca339fe Add CLI and uninstallation info 2023-01-17 19:05:17 +01:00
Vincent Maas c85b33127b add check on connection before executing LG actions, update AMD API, improve NVIDIA dithering with multiple displays, add NVIDIA driver settings , fix some crashes 2023-01-07 14:56:04 +01:00
Vincent Maas 43f038e9ee add auto update, add LG expert actions and auto set current device 2022-12-04 10:13:53 +01:00
Maassoft cb09d5c540 Merge pull request #142 from Shraymonks/fix-readme-url
Fix lgtv.js url in readme
2022-11-22 20:01:32 +01:00
Raymond Ha d29f607ab0 Fix lgtv.js url in readme 2022-11-21 13:10:23 -08:00
Vincent Maas dc2f7f493c fixed several issues, add pre-launch/finalize steps to game presets 2022-11-13 15:06:24 +01:00
Vincent Maas 452206729b fix WOL issues, add Windows Service 2022-10-28 14:25:14 +02:00
Vincent Maas a8d41f67c9 fix screensaver issue, add eye comfort mode, improve elevated process and add priority/affinity settings to game launcher 2022-09-11 16:40:55 +02:00
Vincent Maas 84cb7e6446 add auto volume and total power on hours action 2022-07-24 15:14:44 +02:00
Vincent Maas 6c21245b98 fix typo 2022-07-16 15:16:09 +02:00
Vincent Maas a1121c165f fix some issues, add LG aspect ratio/zoom actions 2022-07-16 15:04:07 +02:00
Vincent Maas 5836f88ef0 restructured code (wip), add game launcher and quick access 2022-06-27 19:07:33 +02:00
Vincent Maas 41cf8ae3e2 add/improve some picture settings, allow enabling OLED Motion Pro for C2/G2, add NVIDIA resolution and driver settings 2022-05-21 11:30:28 +02:00
Vincent Maas c1ace0bdd5 add sorting of list views, improve refresh rate/color depth apply order, fix manually turned off tv's 2022-05-01 10:07:22 +02:00
Vincent Maas 66e682ba3d fixed corrupt json file issues, retrieving apps and added HDMI port selection and new power setting 2022-04-03 14:58:14 +02:00
Vincent Maas 0de3deebf7 fixed some nvidia/lg controller issues 2022-03-26 15:19:55 +01:00
Vincent Maas 0c1859f848 add spanish translations to restart detector 2022-02-27 15:23:46 +01:00
Vincent Maas b8910a3b64 fixed several issues, add some LG settings 2022-02-26 14:35:38 +01:00
Vincent Maas b2e7c529a0 fix/add various LG/NVIDIA issues 2022-02-05 11:53:49 +01:00
Vincent Maas 977060e927 fix dithering issues 2022-01-02 15:24:33 +01:00
Vincent Maas f609471fd2 add label to set device config call, add EzAdjust service menu expert button 2021-12-31 14:48:28 +01:00
Vincent Maas a1a94d27db add hdmi 1-4 icon actions 2021-12-29 11:47:47 +01:00
Vincent Maas e12faaba27 remove asp.net framework dependency 2021-12-27 17:50:10 +01:00
Vincent Maas 6f5f7c4dd3 add game bar and some other functions, upgrade to .NET 6 2021-12-27 14:37:14 +01:00
Vincent Maas e1800b8d50 add some LG actions, fix IP address edit box 2021-09-26 10:19:06 +02:00
Vincent Maas 7779533c1e Merge branch 'master' of https://github.com/Maassoft/ColorControl 2021-09-12 14:46:14 +02:00
Vincent Maas 74cefc00f7 add firmware downgrade functionality 2021-09-12 14:46:07 +02:00
Maassoft a8d33b6a30 Update readme (work in progress) 2021-09-05 20:07:07 +02:00
Vincent Maas f83869da04 add turn screen off/on actions 2021-09-04 14:25:48 +02:00
Vincent Maas 815710c081 fix shortcuts and refresh of apps 2021-08-30 19:17:34 +02:00
Vincent Maas 805c516371 fix F13-F24 keys 2021-08-29 16:14:17 +02:00
Vincent Maas b85d929890 fixed process monitor issue, improve shortcuts and Chrome fix 2021-08-29 15:28:18 +02:00
Vincent Maas dd9ce932fc made loading presets code more robust 2021-08-18 11:15:56 +02:00
Vincent Maas 3667243fc7 fixed some issues, added check for updates 2021-08-16 13:59:01 +02:00
Vincent Maas 92f4ab8ef5 add triggers, fixed some bugs 2021-08-13 15:11:33 +02:00
Vincent Maas 90ca095954 add nvidia/amd presets to steps in LG controller 2021-07-04 16:18:59 +02:00
Vincent Maas fa63d8dc4f fixed some issues 2021-06-20 13:59:12 +02:00
Vincent Maas fc134b146a extend eligible models for motion pro, fix bug when adding WOL action 2021-06-11 20:07:51 +02:00
Vincent Maas 3abf106496 extended expert-button, added new actions 2021-06-06 15:58:21 +02:00
Vincent Maas a17534abcb use modelName for detection if eligible for motion pro 2021-05-30 17:17:46 +02:00
Vincent Maas 4614886954 add LG expert button to test new way of changing settings 2021-05-30 10:31:35 +02:00
Vincent Maas 26a91dc7e9 manually add device, add device setting per preset, add remote control image 2021-05-02 17:38:38 +02:00
Vincent Maas cac5180c76 add name to NVIDIA/AMD presets, add CLI 2021-04-18 15:15:55 +02:00
Vincent Maas 0c877623c0 add screensaver trigger, focus main window when already started, fix some bugs 2021-04-03 16:27:59 +02:00
Vincent Maas a33543acd5 add support for AMD GPU's, improve WOL, refactor code 2021-03-20 14:35:58 +01:00
Vincent Maas 7f6175f8ef add WOL to steps, fix saving configs 2021-02-14 15:17:30 +01:00
Vincent Maas fd51bcca99 add alternate WOL function 2021-02-07 10:45:53 +01:00
Vincent Maas 5268e6679e add better logging of network devices when sending WOL 2021-02-05 19:56:19 +01:00
Vincent Maas 91fd44d698 try to wake device first when lookup fails 2021-02-02 19:43:14 +01:00
Vincent Maas 48b13b1c64 improve checking on attached displays 2021-02-01 19:26:03 +01:00
Vincent Maas d2aacbb8e0 fix crash on startup when no displays are active, set thread execution state when powering off tv on standby 2021-01-30 15:23:06 +01:00
Vincent Maas 04d52d6062 add apply on startup, improve remote controller, fixed some bugs 2021-01-24 15:35:00 +01:00
Vincent Maas 13420d2855 Merge branch 'master' of https://github.com/Maassoft/ColorControl 2021-01-11 19:44:04 +01:00
Vincent Maas 43a4660cc5 add some buttons to control LG TV (volume/mute)
add preliminary remote control form
2021-01-11 19:43:53 +01:00
Maassoft 8683cd5be0 Update README.md 2021-01-01 14:32:02 +01:00
Vincent Maas 038a70cc81 add testing/customization of dithering 2020-12-29 19:24:25 +01:00
Vincent Maas bc872606a8 add device filter 2020-12-04 13:53:53 +01:00
Vincent Maas 067b6d7b99 fix crash on startup when scaling is not 100% 2020-11-26 22:30:24 +01:00
871 changed files with 137493 additions and 8450 deletions

No files matched your search

+2 -1
View File
@@ -337,4 +337,5 @@ ASALocalRun/
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
healthchecksdb
*.clef
+165
View File
@@ -0,0 +1,165 @@
using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
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
{
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(new AppState { SelectedTheme = useDarkMode ? "dark" : "light" });
builder.Services.AddTransient<RpcUiClientService>();
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();
// 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.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
//app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
//app.UsePathBase("/");
app.MapRazorComponents<Components.App>()
.AddInteractiveServerRenderMode();
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;
}
}
}
+92
View File
@@ -0,0 +1,92 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<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>
<Content Update="Components\Pages\Generic\ConfirmModal.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<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>
<Content Update="Components\Pages\Info.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<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>
<Content Update="Components\Pages\Nvidia\NvidiaSettings.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Nvidia\NvidiaPreset.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Samsung\SamsungDevicePage.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Generic\StepsInput.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<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>
+80
View File
@@ -0,0 +1,80 @@
@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 _rpcClientService
@inject NavigationManager _navigationManager
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
@inject JSHelper _jsHelper
@implements IDisposable
@attribute [StreamRendering]
<!DOCTYPE html>
<html lang="en" data-bs-theme="@AppState.SelectedTheme">
<head>
<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 @rendermode="InteractiveServer" />
<script>
addEventListener('focus', (event) => {
var isModalShown = document.getElementsByClassName("modal show").length > 0;
DotNet.invokeMethodAsync("ColorControl.UI", "RefreshSummary", isModalShown);
});
</script>
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<script src="@Assets["_framework/blazor.web.js"]"></script>
<NotificationsComponent></NotificationsComponent>
</body>
</html>
@code {
private Config? Config;
protected override async Task OnInitializedAsync()
{
Config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
var newTheme = Config?.UseDarkMode == true ? "dark" : "light";
if (newTheme != AppState.SelectedTheme)
{
AppState.SelectedTheme = Config?.UseDarkMode == true ? "dark" : "light";
_navigationManager.Refresh();
}
AppState.OnChange += StateHasChanged;
}
public void Dispose()
{
AppState.OnChange -= StateHasChanged;
}
[JSInvokable]
public static async Task RefreshSummary(bool isModalShown)
{
if (Blazor.CurrentComponent is SummaryComponentBase summaryComponent && !isModalShown)
{
await summaryComponent.RefreshExternal();
}
}
}
@@ -0,0 +1,21 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<TopMenu />
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
@@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(0, 40, 90) 0%, #101047 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -0,0 +1,71 @@
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">ColorControl Web App</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
@if (modules != null)
{
@foreach (var module in modules.Where(m => m.IsActive))
{
<div class="nav-item px-3">
<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 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 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 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>
</div>
@code {
private Config? config;
private List<ModuleDto>? modules;
protected override async Task OnInitializedAsync()
{
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
modules = config?.Modules?.Select(m => new ModuleDto { DisplayName = m.DisplayName, IsActive = m.IsActive, Link = m.DisplayName.Split(' ')[0] }).ToList();
}
}
@@ -0,0 +1,109 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.bi-list-logging-nav-menu {
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-card-text" viewBox="0 0 16 16"><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"/><path d="M3 5.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5M3 8a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 8m0 2.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5"/></svg>');
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
@@ -0,0 +1,29 @@
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services
@inherits LayoutComponentBase
@inject AppState AppState
@inject NavigationManager _navigationManager;
@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> *@
<a href="https://github.com/Maassoft/ColorControl" target="_blank">GitHub</a>
</div>
@code {
private string ThemeTransitionName = "Dark";
protected override void OnInitialized()
{
ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme";
}
private void ChangeTheme()
{
AppState.SetTheme(AppState.SelectedTheme == "light" ? "dark" : "light");
ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme";
_navigationManager.Refresh();
}
}
@@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
@@ -0,0 +1,388 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Services
@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-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>
<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 (!Preset.IsDisplayPreset)
{
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" type="text" @bind="Preset.name" required />
</div>
<div class="form-check">
<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>
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
}
<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="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)
{
<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>
</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>
</div>
</div>
}
@if (resolutions != null)
{
<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.DisplayConfig.ApplyResolution" id="applyResolution">
}
<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>
</h2>
<div id="resolutionSettings" class="accordion-collapse collapse @(Preset.IsDisplayPreset ? "show" : "")" data-bs-parent="#presetAccordion">
<div class="accordion-body">
@if (resolutions != null)
{
<div class="mb-2">
<label class="form-label" for="activeResolution">Active resolution</label>
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution">
@foreach (var resolution in resolutions)
{
<option value="@resolution.GetActiveString()">@resolution.GetActiveString()</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="virtualResolution">Virtual resolution</label>
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution">
@foreach (var resolution in resolutions)
{
<option value="@resolution.GetVirtualString()">@resolution.GetVirtualString()</option>
}
</select>
</div>
}
<div class="mb-2">
<label class="form-label" for="scaling">Scaling/Aspect ratio</label>
<select class="form-select" id="scaling" @bind="Preset.DisplayConfig.Scaling">
@foreach (var scaling in Enum.GetValues<Shared.Native.CCD.DisplayConfigScaling>())
{
<option value="@scaling.ToString()">@scaling.ToString()</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="rotation">Rotation</label>
<select class="form-select" id="rotation" @bind="Preset.DisplayConfig.Rotation">
@foreach (var rotation in Enum.GetValues<Shared.Native.CCD.DisplayConfigRotation>())
{
<option value="@rotation.ToString()">@rotation.ToString()</option>
}
</select>
</div>
</div>
</div>
</div>
}
@if (refreshRates != null)
{
<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.DisplayConfig.ApplyRefreshRate" id="applyRefreshRate">
}
<button class="accordion-button collapsed py-2 @(Preset.DisplayConfig.ApplyRefreshRate ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#refreshRateSettings">
Refresh Rate
</button>
</span>
</h2>
<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>
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate">
@foreach (var refreshRate in refreshRates)
{
<option value="@refreshRate.ToString()">@refreshRate.ToString() Hz</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.applyColorData" id="applyColorData">
}
<button class="accordion-button collapsed py-2 @(Preset.applyColorData ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#colorSettings">
Color settings
</button>
</span>
</h2>
<div id="colorSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="bitDepth">Bit depth</label>
<select class="form-select" id="bitDepth" @bind="Preset.colorDepth">
@foreach (var bitDepth in Enum.GetValues<ADLColorDepth>())
{
<option value="@bitDepth.ToString()">@bitDepth.GetDescription()</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="colorFormat">Format</label>
<select class="form-select" id="colorFormat" @bind="Preset.pixelFormat">
@foreach (var format in Enum.GetValues<ADLPixelFormat>())
{
<option value="@format.ToString()">@format.ToString()</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.applyDithering" id="applyDithering">
}
<button class="accordion-button collapsed py-2 @(Preset.applyDithering ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#ditherSettings">
Dithering
</button>
</span>
</h2>
<div id="ditherSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="state">State</label>
<select class="form-select" id="state" @bind="Preset.ditherState">
@foreach (var state in Enum.GetValues<ADLDitherState>())
{
<option value="@state.ToString()">@state.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.applyHDR" id="applyHDR">
}
<button class="accordion-button collapsed py-2 @(Preset.applyHDR ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#hdrSettings">
HDR
</button>
</span>
</h2>
<div id="hdrSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.HDREnabled" id="hdrEnabled">
<label class="form-check-label" for="hdrEnabled">
HDR enabled
</label>
</div>
@if (!Preset.IsDisplayPreset) {
<div class="mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.toggleHDR" id="toggleHdr">
<label class="form-check-label" for="toggleHdr">
Toggle HDR
</label>
</div>
}
<NullableRangeInput TValue="int" @bind-Value="Preset.SDRBrightness" Id="sdrBrightness" Label="SDR brightness" Unit="%" />
</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">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
</div>
</div>
</div>
</div>
@code {
public AmdPreset? Preset { get; set; }
[Parameter]
public AmdPreset? PresetParam { get; set; }
[Parameter]
public Func<AmdPreset, Task>? AfterApply { get; set; }
private string? selectedActiveResolution;
private string? selectedVirtualResolution;
private string? selectedRefreshRate;
private List<VirtualResolution>? resolutions;
private List<Rational>? refreshRates;
private List<AmdPreset>? displayPresets;
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
{
Preset = PresetParam == null ? null : PresetParam.CloneWithId();
if (Preset != null && PresetParam != null)
{
resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("AmdService", "GetAvailableResolutionsV2", Preset);
refreshRates = await _rpcClientService.CallAsync<List<Rational>>("AmdService", "GetAvailableRefreshRatesV2", Preset);
}
selectedActiveResolution = Preset?.DisplayConfig.Resolution?.GetActiveString();
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
if (Preset?.IsDisplayPreset == false)
{
displayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var newResolution = resolutions?.FirstOrDefault(r => r.GetActiveString() == selectedActiveResolution);
if (newResolution != null)
{
if (!string.IsNullOrEmpty(selectedVirtualResolution) && selectedActiveResolution != selectedVirtualResolution)
{
newResolution = new VirtualResolution(newResolution.ActiveWidth, newResolution.ActiveHeight);
var parts = selectedVirtualResolution.Split('x');
newResolution.VirtualWidth = uint.Parse(parts[0]);
newResolution.VirtualHeight = uint.Parse(parts[1]);
}
Preset.DisplayConfig.Resolution = newResolution;
}
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
if (newRefreshRate != null)
{
Preset.DisplayConfig.RefreshRate = newRefreshRate;
}
if (Preset.IsDisplayPreset)
{
Preset.UpdateAutoApplySettings(PresetParam);
await ApplyDisplayPreset();
return;
}
var result = await _rpcClientService.CallAsync<bool>("AmdService", "UpdatePreset", Preset);
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
}
private async Task ApplyDisplayPreset()
{
var result = await _rpcClientService.CallAsync<bool>("AmdService", "ApplyPreset", Preset);
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
}
}
@@ -0,0 +1,64 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" 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">AMD settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Config = null"></button>
</div>
<div class="modal-body">
<form class="row g-1">
@if (Config == null)
{
<div>Loading...</div>
}
else
{
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => Config = null">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
private AmdServiceConfig? Config { get; set; }
private async Task ApplyClick(MouseEventArgs e)
{
if (Config == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("AmdService", "UpdateConfig", Config);
if (result)
{
Config = null;
}
}
private async Task OnFocus(FocusEventArgs e)
{
if (Config != null)
{
return;
}
Config = await _rpcClientService.CallAsync<AmdServiceConfig>("AmdService", "GetConfig");
}
}
@@ -0,0 +1,234 @@
@page "/AMD"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@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 Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
<PageTitle>AMD controller</PageTitle>
<AmdSettings />
<div class="d-flex align-items-center">
<h1>AMD controller</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
Settings
</button>
</div>
</div>
<h2>Displays</h2>
<AmdPresetPage PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@if (DisplayPresets == null)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
@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>
}
@if (Presets == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Presets</h2>
<div class="d-flex ms-auto">
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
@foreach (var order in Enum.GetValues<PresetOrder>())
{
<option value="@order">Order by: @order.GetDescription()</option>
}
</select>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
Add
</button>
</div>
</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)
{
<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">@(preset.primaryDisplay ? "Primary display" : preset.displayName)</small>
@if (preset.ShowInQuickAccess)
{
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
}
</h5>
</div>
<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>
</span>
}
</div>
</div>
</div>
}
@code {
private List<AmdPreset>? Presets;
private List<AmdPreset>? DisplayPresets;
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");
}
protected override async Task OnAfterRenderAsync(bool firstTime)
{
if (firstTime)
{
PresetOrder = await _localStorage.TryGet<PresetOrder>("AmdPresetOrder");
Presets = Presets?.OrderPresetsBy(PresetOrder);
StateHasChanged();
}
}
private async Task PresetOnClick(AmdPreset preset)
{
await _rpcClientService.CallAsync<bool>("AmdService", "ApplyPresetWithId", preset.id);
}
private void PresetOnEditClick(AmdPreset preset)
{
CurrentPreset = preset;
}
private void PresetOnCloneClick(AmdPreset preset)
{
CurrentPreset = new AmdPreset(preset);
CurrentPreset.name = preset.name;
CurrentPreset.SetCloneValues();
}
private void PresetOnDeleteClick(AmdPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnDeleteConfirmed()
{
if (CurrentPreset == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("AmdService", "DeletePreset", CurrentPreset.id);
await RefreshPresets();
StateHasChanged();
}
private void DisplayPresetOnClick(AmdPreset preset)
{
CurrentPreset = preset;
}
private void AddPresetOnClick()
{
CurrentPreset = new AmdPreset();
}
private async Task AfterPresetApplied(AmdPreset preset)
{
await RefreshPresets();
StateHasChanged();
}
private async Task PresetOrderChanged()
{
await _localStorage.SetAsync("AmdPresetOrder", PresetOrder);
await RefreshPresets();
}
private async Task RefreshPresets()
{
Presets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetPresets");
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
}
@@ -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();
}
}
}
@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
@@ -0,0 +1,242 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Game
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common
@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-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>
<StartProgramInput @bind-Path="Preset.Path" @bind-Parameters="Preset.Parameters" @bind-Path:after="PathChanged"></StartProgramInput>
<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">
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>
}
</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;
[Parameter]
public GamePreset? PresetParam { get; set; }
[Parameter]
public Func<GamePreset, Task>? AfterApply { get; set; }
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; }
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
{
if (PresetParam == null)
{
return;
}
Preset = PresetParam.CloneWithId();
if (Preset.id == 0)
{
RecentApps = await _rpcClientService.CallAsync<List<GameApp>>("GameService", "GetMruApps", true);
RecentAppFilename = "";
if (RecentApps != null)
{
RecentApps.Insert(0, new GameApp { Filename = "", FriendlyName = "Choose a recent app to fill values" });
}
}
else
{
RecentApps = null;
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
IsVisible = false;
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
}
private void RecentAppSelected()
{
if (RecentApps == null || Preset == null)
{
return;
}
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);
}
}
}
@@ -0,0 +1,61 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Game
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" tabindex="-1">
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="row g-1">
@if (Config == null)
{
<div>Loading...</div>
}
else
{
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Config.ApplyExternallyLaunched" id="applyExternallyLaunched">
<label class="form-check-label" for="applyExternallyLaunched">
Apply presets when launching games externally
</label>
</div>
}
</form>
</div>
<div class="modal-footer">
<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>
</div>
</div>
</div>
@code {
private GameServiceConfig? Config { get; set; }
protected override async Task OnInitializedAsync()
{
Config = await _rpcClientService.CallAsync<GameServiceConfig>("GameService", "GetConfig");
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Config == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdateConfig", Config);
}
}
@@ -0,0 +1,178 @@
@page "/Game"
@using ColorControl.Shared.Contracts;
@using ColorControl.Shared.Contracts.Game;
@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 Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
<PageTitle>Game launcher</PageTitle>
<GameSettings />
<div class="d-flex align-items-center">
<h1>Game launcher</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
Settings
</button>
</div>
</div>
<div class="mb-2 d-flex align-items-center">
<h2>Presets</h2>
<div class="d-flex ms-auto">
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
@foreach (var order in Enum.GetValues<PresetOrder>())
{
<option value="@order">Order by: @order.GetDescription()</option>
}
</select>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
Add
</button>
</div>
</div>
<GamePresetPage PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@if (Presets == null)
{
<div>Loading...</div>
}
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)
{
<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">
@preset.name
@if (preset.ShowInQuickAccess)
{
<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 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>
</span>
}
</div>
</div>
</div>
}
@code {
private List<GamePreset>? Presets;
private GamePreset? CurrentPreset;
private PresetOrder PresetOrder;
protected override async Task OnInitializedAsync()
{
await RefreshPresets();
}
protected override async Task OnAfterRenderAsync(bool firstTime)
{
if (firstTime)
{
PresetOrder = await _localStorage.TryGet<PresetOrder>("GamePresetOrder");
Presets = Presets?.OrderPresetsBy(PresetOrder);
StateHasChanged();
}
}
private async Task PresetOnClick(GamePreset preset)
{
await _rpcClientService.CallAsync<bool>("GameService", "ApplyPresetWithId", preset.id);
}
private void PresetOnEditClick(GamePreset preset)
{
CurrentPreset = preset;
}
private void PresetOnCloneClick(GamePreset preset)
{
CurrentPreset = new GamePreset(preset);
CurrentPreset.SetCloneValues();
}
private void PresetOnDeleteClick(GamePreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnDeleteConfirmed()
{
if (CurrentPreset == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("GameService", "DeletePreset", CurrentPreset.id);
await RefreshPresets();
StateHasChanged();
}
private void AddPresetOnClick()
{
CurrentPreset = new GamePreset();
}
private async Task AfterPresetApplied(GamePreset preset)
{
await RefreshPresets();
StateHasChanged();
}
private async Task PresetOrderChanged()
{
await _localStorage.SetAsync("GamePresetOrder", PresetOrder);
await RefreshPresets();
}
private async Task RefreshPresets()
{
Presets = await _rpcClientService.CallAsync<List<GamePreset>>("GameService", "GetPresets");
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
}
@@ -0,0 +1,50 @@
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick" NextModalId="@NextModalId"></FieldsModal>
@code {
[Parameter]
public string? Title { get; set; }
[Parameter]
public string? Message { get; set; }
[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 void OnParametersSet()
{
if (Message != null)
{
Fields = [new FieldDefinition { FieldType = FieldType.Label, Label = Message }];
if (ExtraFields != null)
{
Fields.AddRange(ExtraFields);
}
}
}
private async Task ConfirmClick()
{
if (OnConfirm != null)
{
await OnConfirm();
}
}
}
@@ -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;
}
}
@@ -0,0 +1,122 @@
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
@if (Fields == null)
{
return;
}
@foreach (var field in Fields)
{
if (field.FieldType == FieldType.Text)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-label" for="@fieldId">@field.Label</label>
<input class="form-control" type="text" @bind="field.StringValue" id="@fieldId">
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
}
if (field.FieldType == FieldType.Label)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-label" for="@fieldId">@field.Label</label>
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
}
else if (field.FieldType == FieldType.DropDown)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-label" for="@fieldId">@field.Label</label>
<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))
{
<option value="@item.ToString()">@((item as IConvertible).GetDescription())</option>
}
}
else if (field.Values != null)
{
@foreach (var item in field.Values)
{
<option value="@item.ToString()">@item.ToString()</option>
}
}
</select>
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
}
else if (field.FieldType == FieldType.Shortcut)
{
<ShortcutInput Label="@field.Label" @bind-Shortcut="field.StringValue"></ShortcutInput>
}
else if (field.FieldType == FieldType.CheckBox)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="field.BoolValue" id="@fieldId">
<label class="form-check-label" for="@fieldId">
@field.Label
</label>
</div>
}
else if (field.FieldType == FieldType.Numeric && (field.MaxValue - field.MinValue) <= 50)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-label" for="@fieldId">@field.Label</label>
<input class="form-control" type="number" @bind="field.IntValue" min="@((int)field.MinValue)" max="@((int)field.MaxValue)" id="@fieldId">
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
}
else if (field.FieldType == FieldType.Numeric && (field.MaxValue - field.MinValue) > 50)
{
var fieldId = $"fld_{Random.Shared.Next()}";
<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);
}
}
@@ -0,0 +1,134 @@
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@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-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="OnClose"></button>
</div>
<div class="modal-body">
@if (IsVisible)
{
<FieldDefs Fields="Fields"></FieldDefs>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClose">Cancel</button>
@if (SecondaryCaption != null)
{
<button type="button" class="btn btn-secondary" @onclick="SecondaryClick">@SecondaryCaption</button>
}
<button class="btn btn-primary" type="button" disabled="@IsNextDisabled" data-bs-dismiss="@Dismiss" @onclick="ConfirmClick">
@if (IsNextDisabled)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">@ConfirmCaption</span>
</button>
</div>
</div>
</div>
</div>
@code {
[Parameter]
public string? ModalId { get; set; }
[Parameter]
public string? NextModalId { get; set; }
[Parameter]
public string? ConfirmCaption { get; set; } = "Yes";
[Parameter]
public string? SecondaryCaption { get; set; }
[Parameter]
public string? Dismiss { get; set; } = "modal";
[Parameter]
public string? Title { get; set; }
[Parameter]
public string? Message { get; set; }
[Parameter]
public List<FieldDefinition>? Fields { get; set; }
[Parameter]
public Func<Task>? OnConfirm { get; set; }
[Parameter]
public Func<Task>? OnSecondary { get; set; }
[Parameter]
public Func<Task>? OnShow { get; set; }
private bool IsVisible { get; set; }
private bool IsNextDisabled { get; set; }
private async Task ConfirmClick(MouseEventArgs e)
{
if (OnConfirm == null)
{
return;
}
var dismissing = Dismiss == "modal";
IsNextDisabled = Dismiss == null;
try
{
await OnConfirm();
}
finally
{
IsNextDisabled = false;
}
if (dismissing)
{
IsVisible = false;
Dismiss = "modal";
}
}
private async Task SecondaryClick(MouseEventArgs e)
{
if (OnSecondary == null)
{
return;
}
await OnSecondary();
}
private async Task ModalFocus(FocusEventArgs e)
{
if (!IsVisible)
{
IsVisible = true;
if (OnShow != null)
{
await OnShow();
//StateHasChanged();
}
}
}
private async Task OnClose(MouseEventArgs e)
{
IsVisible = false;
if (!string.IsNullOrEmpty(NextModalId))
{
await jsHelper.OpenModal(NextModalId);
}
}
}
@@ -0,0 +1,53 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
<div class="row mx-0 mb-2">
<label class="form-label ps-0">@Label</label>
@if (Labels != null)
{
var index = 1;
var checkValue = 1;
foreach (var label in Labels)
{
var name = $"check{index}";
var checkIndex = index;
<div class="form-check form-check-inline col-@(Col)">
<input class="form-check-input" type="checkbox" role="switch" id="@name" checked="@((Value & checkValue) == checkValue)" @onchange="(ChangeEventArgs e) => CheckOnChange(e, checkIndex)">
<label class="form-check-label" for="@name">@label</label>
</div>
index++;
checkValue *= 2;
}
}
</div>
@code {
[Parameter]
public List<string>? Labels { get; set; }
[Parameter]
public string? Label { get; set; }
[Parameter]
public uint Value { get; set; }
[Parameter]
public EventCallback<uint> ValueChanged { get; set; }
[Parameter]
public int Col { get; set; } = 4;
private async Task CheckOnChange(ChangeEventArgs e, int index)
{
var checkValue = (uint)Math.Pow(2, index - 1);
Value += (uint)((e.Value is bool boolValue && boolValue) ? checkValue : -checkValue);
await ValueChanged.InvokeAsync(Value);
}
}
@@ -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);
}
}
@@ -0,0 +1,140 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
@if (Label != null)
{
<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? Shortcut { get; set; }
[Parameter]
public EventCallback<string?> ShortcutChanged { get; set; }
private Dictionary<string, bool> Modifiers = new() {
{ "Shift", false },
{ "Control", false },
{ "Alt", false },
{ "Win", false }
};
protected override void OnParametersSet()
{
LoadModifiers();
}
private async Task ShortcutOnChange(ChangeEventArgs e)
{
Shortcut = e.Value?.ToString();
LoadModifiers();
await ShortcutChanged.InvokeAsync(Shortcut);
}
private void ShortcutOnKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Tab")
{
return;
}
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
Shortcut = text;
}
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;
}
}
}
@@ -0,0 +1,651 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@typeparam TPreset where TPreset : PresetBase
@inject RpcUiClientService _rpcClientService
@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 || LgPresets != null || CustomItems != null)
{
<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-3 dropdown-menu-end" style="width: 21rem; height: 430px">
<ul class="nav nav-tabs" id="presetTab" role="tablist">
@{
var linkActive = "active";
}
@if (NvPresets != null && NvPresetsEnabled)
{
<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)
{
<option value="@preset.id">@preset.IdOrName</option>
}
</select>
</div>
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddNvPresetOnClick()" disabled="@(NvPresetsToAdd.Count == 0)">Add item(s)</button>
</div>
tabActive = "";
}
@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>
}
</div>
</div>
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-click" type="button" role="tab" aria-controls="nav-click" aria-selected="true">Visual steps</button>
<button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-raw" type="button" role="tab" aria-controls="nav-raw" aria-selected="false">Raw text</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-click" role="tabpanel" aria-labelledby="nav-click-tab" tabindex="0">
<div class="btn-group btn-group-sm d-inline-flex flex-wrap" role="group" aria-label="Small button group">
@foreach (var step in PresetSteps)
{
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" @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>
<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++;
}
</div>
</div>
<div class="tab-pane fade" id="nav-raw" role="tabpanel" aria-labelledby="nav-raw-tab" tabindex="0">
<textarea class="form-control" id="rawStepText" rows="3" @onchange="RawTextOnChange" @onblur="RawTextOnBlur" value="@RawText"></textarea>
</div>
</div>
</div>
}
@code {
[Parameter]
public string? Label { get; set; }
[Parameter]
public List<string>? Steps { get; set; }
[Parameter]
public EventCallback<List<string>?> StepsChanged { get; set; }
[Parameter]
public List<CustomStepItem>? CustomItems { get; set; }
[Parameter]
public List<ActionStepItem>? ActionItems { get; set; }
[Parameter]
public bool NvPresetsEnabled { get; set; } = true;
[Parameter]
public bool SamsungPresetsEnabled { get; set; } = true;
[Parameter]
public bool LgPresetsEnabled { get; set; } = true;
private PresetStep? CurrentPresetStep;
public class ActionStepItem
{
public string Name { get; private set; }
public List<InvokableActionDto<TPreset>> Actions { get; private set; }
public string? SelectedActionName { get; set; }
public Dictionary<string, List<InvokableActionDto<TPreset>>> GroupedActions;
public ActionStepItem(string name, List<InvokableActionDto<TPreset>> actions)
{
Name = name;
Actions = actions;
GroupedActions = actions.GroupBy(a => a.Category).ToDictionary(k => k.Key, v => v.ToList());
}
public string GetStepString(List<FieldDefinition>? fields)
{
if (string.IsNullOrEmpty(SelectedActionName))
{
return "";
}
var action = Actions.FirstOrDefault(a => a.Name == SelectedActionName);
if (action == null)
{
return "";
}
var field = fields?.FirstOrDefault();
if (field == null || field.Value == null)
{
return $"{action.Name}";
}
return $"{action.Name}({field.Value?.ToString()})";
}
}
private List<NvPreset>? NvPresets { get; set; }
private List<SamsungPreset>? SamsungPresets { get; set; }
private List<LgPreset>? LgPresets { get; set; }
private int? SamsungPresetToAddId { get; set; }
private int? LgPresetToAddId { get; set; }
private List<PresetStep> PresetSteps = new();
private string? RawText;
private List<FieldDefinition>? ActionFields;
private List<FieldDefinition>? ActionFieldsEdit;
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];
}
}
}
protected override async Task OnInitializedAsync()
{
var config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
if (config.HasModule(Module.NvModule))
{
NvPresets = (await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetPresets"))?.OrderBy(p => p.name).ToList();
}
if (config.HasModule(Module.SamsungModule))
{
SamsungPresets = (await _rpcClientService.CallAsync<List<SamsungPreset>>("SamsungService", "GetPresets"))?.OrderBy(p => p.name).ToList();
}
if (config.HasModule(Module.LgModule))
{
LgPresets = (await _rpcClientService.CallAsync<List<LgPreset>>("LgService", "GetPresets"))?.OrderBy(p => p.name).ToList();
}
}
private async Task RemoveOnClick(int index)
{
if (PresetSteps.Count > index)
{
PresetSteps.RemoveAt(index);
}
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()
{
NvPresetsToAdd.ForEach(id => AddPreset(NvPresets, "NvPreset", id));
AddPreset(SamsungPresets, "SamsungPreset", SamsungPresetToAddId);
AddPreset(LgPresets, "LgPreset", LgPresetToAddId);
if (CustomItems != null && Steps != null)
{
foreach (var customItem in CustomItems)
{
if (string.IsNullOrWhiteSpace(customItem.SelectedItem))
{
continue;
}
PresetSteps.Add(PresetStep.Parse(customItem.SelectedItem));
//customItem.SelectedItem = null;
}
}
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));
//customItem.SelectedItem = null;
}
}
AddStartProgram();
await EmitSteps();
}
private void AddPreset<T>(List<T>? presets, string type, int? id) where T : PresetBase
{
var preset = presets?.FirstOrDefault(p => p.id == id);
if (preset == null || Steps == null || preset.id == 0)
{
return;
}
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)
{
Steps.Clear();
Steps.AddRange(PresetSteps.Select(ps => ps.UpdatedRaw));
RawText = string.Join(", ", PresetSteps);
await StepsChanged.InvokeAsync(Steps);
}
StateHasChanged();
}
private async Task RawTextOnBlur(FocusEventArgs e)
{
var newSteps = new List<string>();
Utils.ParseWords(newSteps, RawText);
PresetSteps.Clear();
PresetSteps.AddRange(newSteps.Select(s => PresetStep.Parse(s)));
await EmitSteps();
}
private void RawTextOnChange(ChangeEventArgs e)
{
RawText = e.Value?.ToString();
}
private void AdvancedItemChanged(ActionStepItem customItem)
{
var action = customItem.Actions.FirstOrDefault(a => a.Name == customItem.SelectedActionName);
if (action == null)
{
ActionFields = null;
return;
}
ActionFields = [CreateField(action)];
}
private FieldDefinition CreateField(InvokableActionDto<TPreset> action)
{
var fieldType = FieldType.Numeric;
var strValue = default(string);
var subLabel = default(string);
var label = action.Title;
if (action.EnumType != null)
{
fieldType = FieldType.DropDown;
strValue = Enum.GetNames(action.EnumType).First();
subLabel = "Note: not all values are available on all models.";
}
else if (action.MinValue == action.MaxValue)
{
fieldType = FieldType.Label;
label = $"This will execute {action.Title}";
}
var field = new FieldDefinition
{
FieldType = fieldType,
Label = label,
SubLabel = subLabel,
ValueType = action.EnumType,
MinValue = action.MinValue,
MaxValue = action.MaxValue,
};
if (fieldType == FieldType.Numeric)
{
field.IntValue = action.CurrentValue;
}
else
{
field.StringValue = strValue;
}
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);
}
}
@@ -0,0 +1,65 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@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">
@foreach (var type in Enum.GetValues<PresetTriggerType>())
{
<option value="@type.ToString()">@type.GetDescription()</option>
}
</select>
</div>
<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" />
</div>
<div class="mb-2">
<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>
}
}
}
@code {
[Parameter]
public List<PresetTrigger>? Triggers { get; set; }
private List<string> ConditionLabels = Utils.GetDescriptions<PresetConditionType>(fromValue: 1).ToList();
public void ConditionsChanged(uint value, PresetTrigger trigger)
{
trigger.Conditions = (PresetConditionType)value;
}
}
@@ -0,0 +1,89 @@
@page "/"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
<PageTitle>Home</PageTitle>
<h1>Color Control</h1>
<h2>Modules</h2>
@if (Modules == null)
{
<div>Loading...</div>
}
else
{
<div class="row">
@foreach (var module in Modules.Where(m => m.IsActive))
{
<div class="col-lg-4 col-xl mb-3">
<div class="card h-100">
<div class="card-header">
<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>
<ul>
@foreach (var info in module.Info)
{
<li>@info</li>
}
</ul>
</div>
</div>
</div>
}
</div>
}
@code {
private Config? Config;
private List<ModuleDto>? Modules;
private InfoDto? Info;
private bool IsUpdateAvailable;
protected override async Task OnInitializedAsync()
{
Config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
Modules = await _rpcClientService.CallAsync<List<ModuleDto>>("OptionsService", "GetModules");
if (Modules != null)
{
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) });
}
}
}
}
+120
View File
@@ -0,0 +1,120 @@
@page "/info"
@using ColorControl.Shared.Common;
@using ColorControl.UI.Services;
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject NavigationManager _navigationManager;
@inject IHttpContextAccessor _httpContextAccessor
<PageTitle>Logging</PageTitle>
<h1>Information</h1>
@if (info == null)
{
<div>Loading...</div>
}
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>
<li class="list-group-item">Newtonsoft.Json by James Newton-King</li>
<li class="list-group-item">NLog by Jarek Kowalski, Kim Christensen, Julian Verdurmen</li>
<li class="list-group-item">NvAPIWrapper.Net by Soroush Falahati</li>
<li class="list-group-item">NWin32 by zmjack</li>
<li class="list-group-item">TaskScheduler by David Hall</li>
<li class="list-group-item">NVIDIA Profile Inspector by Orbmu2k</li>
<li class="list-group-item">NvidiaML wrapper by LibreHardwareMonitor</li>
<li class="list-group-item">Novideo sRGB by ledoge</li>
<li class="list-group-item">NLogViewer by dojo90</li>
<li class="list-group-item">WPFDarkTheme by AngryCarrot789</li>
<li class="list-group-item">Little-CMS by mm2</li>
<li class="list-group-item">MHC2 by dantmnf</li>
</ul>
}
@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();
}
}
@@ -0,0 +1,247 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.UI.Services
@using Microsoft.AspNetCore.Components.Web
@using ColorControl.Shared.Common
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<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>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="row g-1">
@if (Device == 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="Device.Name" />
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Device.IsCustom" id="isCustom">
<label class="form-check-label" for="isCustom">
Is custom device
</label>
</div>
<div class="mb-2">
<label class="form-label" for="ipAddress">IP-address</label>
<input class="form-control" id="ipAddress" type="text" @bind="Device.IpAddress" disabled="@(!Device.IsCustom)" />
</div>
<div class="mb-2">
<label class="form-label" for="macAddress">MAC-address</label>
<input class="form-control" id="macAddress" type="text" @bind="Device.MacAddress" disabled="@(!Device.IsCustom)" />
</div>
@if (!Device.IsConnected)
{
<div class="mb-2">
<button class="btn btn-primary me-2" type="button" disabled="@IsTestingConnection" @onclick="TestConnectionClick">
@if (IsTestingConnection)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">Test connection</span>
</button>
@if (IsTestConnectionSuccessful != null)
{
if (IsTestConnectionSuccessful == true)
{
<span class="text-success">Connection test was successful!</span>
}
else
{
<span class="text-danger">Connection failed!</span>
}
}
</div>
}
<div class="accordion accordion-flush" id="accordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
<div id="deviceOptions" class="accordion-collapse collapse" data-bs-parent="#accordion">
<div class="accordion-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterStartup" @bind="Device.Options.PowerOnAfterStartup">
<label class="form-check-label" for="checkPowerOnAfterStartup">Power on after startup</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterResume" @bind="Device.Options.PowerOnAfterResume">
<label class="form-check-label" for="checkPowerOnAfterResume">Power on after resume from standby</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnShutdown" @bind="Device.Options.PowerOffOnShutdown">
<label class="form-check-label" for="checkPowerOffOnShutdown">Power off on shutdown</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnStandby" @bind="Device.Options.PowerOffOnStandby">
<label class="form-check-label" for="checkPowerOffOnStandby">Power off on standby</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnScreensaverStart" @bind="Device.Options.PowerOffOnScreenSaver">
<label class="form-check-label" for="checkPowerOffOnScreensaverStart">Power off when screensaver activates</label>
<button class="btn btn-sm btn-primary ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#screenSaverSettings" aria-expanded="false" aria-controls="screenSaverSettings">
Settings
</button>
</div>
<div class="collapse mb-3" id="screenSaverSettings">
<div class="card card-body">
<div class="mb-2">
<label class="form-label" for="minScreenSaverDuration">Minimal screen saver duration (seconds)</label>
<input class="form-control" id="minScreenSaverDuration" type="number" @bind="Device.Options.ScreenSaverMinimalDuration" />
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOffInsteadOfPowerOff" @bind="Device.Options.TurnScreenOffOnScreenSaver">
<label class="form-check-label" for="checkTurnScreenOffInsteadOfPowerOff">Turn screen off instead of power off</label>
<div class="form-text">NOTE: turning the screen off might not work correctly on all models</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOnInsteadOfPowerOn" @bind="Device.Options.TurnScreenOnAfterScreenSaver">
<label class="form-check-label" for="checkTurnScreenOnInsteadOfPowerOn">Turn screen on instead of power on</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPerformActionOnManualScreenSaver" @bind="Device.Options.HandleManualScreenSaver">
<label class="form-check-label" for="checkPerformActionOnManualScreenSaver">Perform action even on manually executed screen saver</label>
</div>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnOnScreensaverEnd" @bind="Device.Options.PowerOnAfterScreenSaver">
<label class="form-check-label" for="checkPowerOnOnScreensaverEnd">Power on when screensaver deactivates</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterManualPowerOff" @bind="Device.Options.PowerOnAfterManualPowerOff">
<label class="form-check-label" for="checkPowerOnAfterManualPowerOff">Power on even after manual power off</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkAllowTriggers" @bind="Device.Options.TriggersEnabled">
<label class="form-check-label" for="checkAllowTriggers">Allow triggers to be fired for this device</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOn" @bind="Device.Options.PowerOnByWindows">
<label class="form-check-label" for="checkWindowsPowerOn">Use Windows power settings to power on</label>
</div>
<div class="form-check form-switch">
<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>
</div>
}
</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>
</div>
</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;
[Parameter]
public LgDeviceDto? Device { get; set; }
[Parameter]
public Func<LgDeviceDto, Task>? AfterApply { get; set; }
private bool IsTestingConnection;
private bool? IsTestConnectionSuccessful;
protected override void OnParametersSet()
{
if (_device != Device)
{
_device = Device;
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Device == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdateDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
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;
try
{
IsTestConnectionSuccessful = await _rpcClientService.CallWithOptionsAsync<bool>("LgService", "TestDevice", new RpcUiClientOptions { Timeout = 120000 }, Device);
}
finally
{
IsTestingConnection = false;
}
}
}
@@ -0,0 +1,117 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal ModalId="executeActionModal" Fields="Fields" Title="@Title" ConfirmCaption="Apply" OnConfirm="ConfirmClick"></FieldsModal>
@code {
[Parameter]
public InvokableActionDto<LgPreset>? Action { get; set; }
[Parameter]
public List<LgDeviceDto>? Devices { get; set; }
[Parameter]
public Func<Task>? OnConfirm { get; set; }
public string? Title { get; set; }
public List<FieldDefinition>? Fields { get; set; }
private FieldDefinition? Field;
private FieldDefinition? DeviceField;
protected override void OnParametersSet()
{
if (Action == null)
{
return;
}
Title = $"Execute action {Action.Title}";
Field = CreateField(Action);
if (Devices?.Any() == true)
{
DeviceField = FieldDefinition.CreateDropDownField("Device", Devices.Select(d => d.ToString()).ToList());
DeviceField.Value = Devices.FirstOrDefault(d => d.IsSelected)?.ToString();
Fields = [DeviceField, Field];
}
else
{
Fields = [Field];
}
}
private FieldDefinition CreateField(InvokableActionDto<LgPreset> action)
{
var fieldType = FieldType.Numeric;
var strValue = default(string);
var subLabel = default(string);
var label = action.Title;
if (action.EnumType != null)
{
fieldType = FieldType.DropDown;
strValue = Enum.GetNames(action.EnumType).First();
subLabel = "Note: not all values are available on all models.";
}
else if (action.MinValue == action.MaxValue)
{
fieldType = FieldType.Label;
label = $"This will execute {action.Title}. Press Apply to continue.";
}
var field = new FieldDefinition
{
FieldType = fieldType,
Label = label,
SubLabel = subLabel,
ValueType = action.EnumType,
MinValue = action.MinValue,
MaxValue = action.MaxValue,
};
if (fieldType == FieldType.Numeric)
{
field.IntValue = action.CurrentValue;
}
else
{
field.StringValue = strValue;
}
return field;
}
private async Task ConfirmClick()
{
if (Action == null || Field == null)
{
return;
}
if (Devices != null && DeviceField != null)
{
Action.SelectedDevice = Devices.FirstOrDefault(d => d.ToString() == DeviceField.Value?.ToString());
}
var parameters = new List<string> { Field.StringValue };
await _rpcClientService.CallAsync<bool>("LgService", "ExecuteInvokableAction", Action, parameters);
if (OnConfirm != null)
{
await OnConfirm();
}
}
}
@@ -0,0 +1,181 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.LG.LgPreset>
@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-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
{
<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>
@if (Devices != null)
{
<div class="mb-2">
<label class="form-label" for="device">Device</label>
<select class="form-select" id="device" @bind="Preset.DeviceMacAddress">
<option value="">Globally selected device</option>
@foreach (var device in Devices)
{
<option value="@device.MacAddress">@device.Name</option>
}
</select>
</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>
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
<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">
Advanced settings
</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" 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" />
</div>
</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="button" class="btn btn-primary" data-bs-dismiss="@(!IsValid() ? "" : "modal")" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
private LgPreset? Preset;
[Parameter]
public LgPreset? PresetParam { get; set; }
[Parameter]
public List<LgDeviceDto>? Devices { get; set; }
[Parameter]
public Func<LgPreset, Task>? AfterApply { get; set; }
private List<CustomStepItem>? CustomItems;
private List<ActionStepItem>? CustomActions;
private string WasValidated = "";
private bool IsVisible;
protected override void OnParametersSet()
{
if (PresetParam == null)
{
return;
}
Preset = PresetParam.CloneWithId();
}
protected override async Task OnInitializedAsync()
{
CustomItems = [new CustomStepItem("RC buttons", Enum.GetValues<ButtonType>().Select(b =>
{
var result = b.ToString();
if (result[0] == '_')
{
result = result.Substring(1);
}
return result;
}).ToList())];
var actions = await _rpcClientService.CallAsync<List<InvokableActionDto<LgPreset>>>("LgService", "GetInvokableActions");
if (actions != null)
{
CustomActions = [new ActionStepItem("Actions", actions.Where(a => !a.Advanced).ToList())];
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdatePreset", Preset);
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name);
}
}
@@ -0,0 +1,34 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal Title="Lg settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
@code {
private LgServiceConfigDto? Config { get; set; }
private List<FieldDefinition>? Fields { get; set; }
protected async Task ModalOnShow()
{
Config = await _rpcClientService.CallAsync<LgServiceConfigDto>("LgService", "GetConfig");
Fields = LgFields.GetSettingsFields(Config);
StateHasChanged();
}
private async Task ApplyClick()
{
if (Config == null)
{
return;
}
FieldDefinition.UpdateObject(Fields, Config);
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdateConfig", Config);
}
}
@@ -0,0 +1,398 @@
@page "/LG"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.LG
@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 Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
<PageTitle>LG controller</PageTitle>
<LgSettings />
<LgExecuteActionModal Action="@CurrentInvokableAction" Devices="Devices" />
<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
</button>
<ul class="dropdown-menu">
@foreach (var button in Enum.GetValues<ButtonType>())
{
<li>
<a class="dropdown-item" @onclick="() => ButtonOnClick(button)">@button.GetDescription()</a>
</li>
}
</ul>
</div>
@if (ActionGroups != null)
{
<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">
Expert
</button>
<ul class="dropdown-menu">
@foreach (var group in ActionGroups)
{
<li class="dropstart">
<a class="dropdown-item dropdown-toggle" data-bs-toggle="dropdown" href="#">@group.Name</a>
<ul class="dropdown-menu dropdown-submenu">
@foreach (var action in group.Actions)
{
<li><a class="dropdown-item" @onclick="() => ActionOnclick(action)" data-bs-toggle="modal" data-bs-target="#executeActionModal">@action.Title</a></li>
}
</ul>
</li>
}
</ul>
</div>
}
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
Settings
</button>
</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="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" />
@if (Devices == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Devices</h2>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" disabled="@IsRefreshDisabled" @onclick="RefreshDevicesOnClick">
@if (IsRefreshDisabled)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#lgDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
</div>
<div class="row mb-3">
@if (!Devices.Any())
{
<span>No devices found</span>
}
@foreach (var device in Devices)
{
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@device.Name
@if (device.IsConnected)
{
<span class="ms-2 badge text-bg-success" title="Device is connected">Connected</span>
}
else
{
<span class="ms-2 badge text-bg-danger" title="Device is disconnected">Disconnected</span>
}
@if (device.IsSelected)
{
<span class="ms-2 badge text-bg-primary" title="Global device">Primary</span>
}
</span>
<div class="d-flex ms-auto">
@if (!device.IsSelected)
{
<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="#lgDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
</div>
<div class="card-body">
<small class="text-muted">@device.IpAddress (@(device.MacAddress ?? "No MAC-address entered"))</small>
</div>
</div>
</div>
}
</div>
}
@if (Presets == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Presets</h2>
<div class="d-flex ms-auto">
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
@foreach (var order in Enum.GetValues<PresetOrder>())
{
<option value="@order">Order by: @order.GetDescription()</option>
}
</select>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
Add
</button>
</div>
</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)
{
<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>
@if (preset.Triggers.Any(t => t.Trigger != PresetTriggerType.None))
{
<div><small class="text-muted">Trigger: @preset.Triggers[0].ToString()</small></div>
}
<div><small class="text-muted">@preset.shortcut</small></div>
</div>
<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>
</span>
}
</div>
</div>
</div>
}
@code {
private class ActionGroup
{
public string Name { get; }
public List<InvokableActionDto<LgPreset>> Actions { get; }
public ActionGroup(string name, List<InvokableActionDto<LgPreset>> actions)
{
Name = (name ?? "Misc");
Actions = actions;
}
}
private List<LgPreset>? Presets;
private List<LgDeviceDto>? Devices;
private List<InvokableActionDto<LgPreset>>? InvokableActions;
private List<ActionGroup>? ActionGroups;
private LgServiceConfigDto? Config;
private LgPreset? CurrentPreset;
private LgDeviceDto? CurrentDevice;
private bool IsRefreshDisabled;
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() ?? new List<ActionGroup>();
}
protected override async Task OnAfterRenderAsync(bool firstTime)
{
if (firstTime)
{
PresetOrder = await _localStorage.TryGet<PresetOrder>("LgPresetOrder");
Presets = Presets?.OrderPresetsBy(PresetOrder);
StateHasChanged();
}
}
private async Task PresetOnClick(LgPreset preset)
{
await _rpcClientService.CallAsync<bool>("LgService", "ApplyPresetWithId", preset.id);
}
private void PresetOnEditClick(LgPreset preset)
{
CurrentPreset = preset;
}
private void PresetOnCloneClick(LgPreset preset)
{
CurrentPreset = new LgPreset(preset);
CurrentPreset.SetCloneValues();
}
private void PresetOnDeleteClick(LgPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnDeleteConfirmed()
{
if (CurrentPreset == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("LgService", "DeletePreset", CurrentPreset.id);
await RefreshPresets();
StateHasChanged();
}
private void DeviceOnClick(LgDeviceDto device)
{
CurrentDevice = device;
}
private void AddDeviceOnClick()
{
CurrentDevice = new LgDeviceDto { IsCustom = true };
}
private void AddPresetOnClick()
{
CurrentPreset = new LgPreset();
}
private async Task AfterPresetApplied(LgPreset preset)
{
await RefreshPresets();
StateHasChanged();
}
private async Task AfterDeviceApplied(LgDeviceDto device)
{
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
StateHasChanged();
}
private async Task RefreshDevicesOnClick()
{
IsRefreshDisabled = true;
try
{
try
{
await _rpcClientService.CallAsync<bool>("LgService", "RefreshDevices");
}
catch
{
}
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
}
finally
{
IsRefreshDisabled = false;
}
StateHasChanged();
}
private void ActionOnclick(InvokableActionDto<LgPreset> action)
{
CurrentInvokableAction = action;
}
private string GetDeviceName(LgPreset preset)
{
if (string.IsNullOrEmpty(preset.DeviceMacAddress))
{
return "Global";
}
return Devices?.FirstOrDefault(d => d.MacAddress == preset.DeviceMacAddress)?.Name ?? preset.DeviceMacAddress;
}
private async Task SetAsPrimaryOnClick(LgDeviceDto device)
{
var result = await _rpcClientService.CallAsync<bool>("LgService", "SetSelectedDevice", device);
if (result)
{
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
}
}
private async Task PresetOrderChanged()
{
await _localStorage.SetAsync("LgPresetOrder", PresetOrder);
await RefreshPresets();
}
private async Task RefreshPresets()
{
Presets = await _rpcClientService.CallAsync<List<LgPreset>>("LgService", "GetPresets");
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
private async Task ButtonOnClick(ButtonType button)
{
var preset = new LgPreset(button.ToString(), null, [button.ToString()]);
await _rpcClientService.CallAsync<bool>("LgService", "ApplyPreset", preset);
}
}
@@ -0,0 +1,207 @@
@page "/logging"
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<PageTitle>Logging</PageTitle>
<h1>Logging</h1>
@if (config == null)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<h5 class="card-title">Settings</h5>
</div>
<form class="ms-3 me-3">
<div class="mb-3 col-md-3">
<label class="form-label" for="logLevel">
Log Level
</label>
<select class="form-select" @bind="logLevel" id="logLevel" @oninput="LogLevelInput">
@foreach (var level in logLevels)
{
<option value="@level.Name">@level.Name</option>
}
</select>
</div>
</form>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-3">
<label class="form-label" for="logSource">
Show log from
</label>
<select class="form-select" @bind="logSource" id="logSource" @bind:after="GetLog">
<option value="0">Main application</option>
<option value="1">Service</option>
</select>
</div>
<div class="col-3">
<label class="form-label" for="logOrder">
Order
</label>
<select class="form-select" @bind="logOrder" id="logOrder" @bind:after="GetLog">
<option value="0">Newest first</option>
<option value="1">Oldest first</option>
</select>
</div>
<div class="col-3">
<label class="form-label" for="maxLines">
Maximum number of lines to show
</label>
<select class="form-select" @bind="maxLines" id="maxLines" @bind:after="GetLog">
<option value="1000">1000</option>
<option value="10000">10000</option>
<option value="999999999">Show all</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">TimeStamp</th>
<th scope="col">
<select class="form-select fw-bold" id="logLevel" @oninput="LogLevelFilterInput">
@foreach (var level in logLevels)
{
<option class="fw-bold" value="@level.Name">@level.Name</option>
}
</select>
</th>
<th scope="col">Name</th>
<th scope="col">Message</th>
</tr>
</thead>
<tbody>
@foreach (var logLine in logLines.Where(l => l.Show(logLevelFilter)))
{
<tr class="table-@logLine.BsClass">
<td style="min-width: 220px">@logLine.TimeStamp</td>
<td style="min-width: 120px">@logLine.Level</td>
<td>@logLine.Name</td>
<td>@logLine.Message</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
@code {
private Config? config;
private string logLevel = "0";
private int logSource = 0;
private int maxLines = 1000;
private int logOrder = 0;
private List<string>? allLines;
private List<LogLine> logLines = new();
private string logLevelFilterName = NLog.LogLevel.Debug.Name;
private NLog.LogLevel logLevelFilter = NLog.LogLevel.Debug;
private List<NLog.LogLevel> logLevels = [NLog.LogLevel.Trace, NLog.LogLevel.Debug, NLog.LogLevel.Info, NLog.LogLevel.Warn, NLog.LogLevel.Error, NLog.LogLevel.Fatal];
private class LogLine
{
public string TimeStamp { get; }
public string Level { get; }
public string Name { get; }
public string Message { get; }
public NLog.LogLevel LogLevel { get; }
public LogLine(string timeStamp, string level, string name, string message)
{
TimeStamp = timeStamp;
Level = level;
Name = name;
Message = message;
LogLevel = NLog.LogLevel.FromString(level);
}
public string BsClass => Level switch
{
"TRACE" => "dark",
"DEBUG" => "secondary",
"INFO" => "info",
"WARN" => "warning",
"ERROR" => "danger",
"FATAL" => "secondary",
_ => "info"
};
public bool Show(NLog.LogLevel level) => LogLevel >= level;
}
protected override async Task OnInitializedAsync()
{
config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
logLevel = config.LogLevel;
if (allLines == null)
{
await GetLog();
}
}
private async Task GetLog()
{
allLines = await _rpcClientService.CallAsync<List<string>>("LoggingService", "LoadLog", logSource, maxLines, logOrder);
ProcessLines();
}
private void ProcessLines()
{
logLines.Clear();
if (allLines == null)
{
return;
}
var lastLogLine = default(LogLine);
foreach (var line in allLines)
{
var strings = line.Split('|');
if (strings.Count() == 4)
{
lastLogLine = new LogLine(strings[0], strings[1], strings[2], strings[3]);
logLines.Add(lastLogLine);
}
}
}
private async Task LogLevelInput(ChangeEventArgs e)
{
var value = e.Value ?? NLog.LogLevel.Debug.Name;
await _rpcClientService.CallAsync<bool>("LoggingService", "SetLogLevel", value);
}
private void LogLevelFilterInput(ChangeEventArgs e)
{
var value = e.Value as string ?? NLog.LogLevel.Debug.Name;
logLevelFilter = NLog.LogLevel.FromString(value);
}
}
@@ -0,0 +1,178 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<div class="modal modal-lg" id="ditheringModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Test dithering</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 (displayPresets != null)
{
<div class="mb-2">
<label class="form-label" for="mode">Display</label>
<select class="form-select" id="mode" @bind="SelectedDisplayId" disabled="@(displayPresets.Count <= 1)">
@foreach (var display in displayPresets)
{
<option value="@display.DisplayId">@display.displayName</option>
}
</select>
</div>
}
</div>
@if (Preset == null)
{
<div>Loading...</div>
}
else
{
<div class="row">
<div class="mb-2">
<label class="form-label" for="state">State</label>
<select class="form-select" id="state" @bind="Preset.NvDitherState" @bind:after="ApplyNormalPreset">
@foreach (var state in Enum.GetValues<NvDitherState>())
{
<option value="@state.ToString()">@state.GetDescription()</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="mode">Mode</label>
<select class="form-select" id="mode" @bind="Preset.NvDitherMode" @bind:after="ApplyNormalPreset">
@foreach (var mode in Enum.GetValues<NvDitherMode>())
{
<option value="@mode.ToString()">@mode.GetDescription()</option>
}
</select>
</div>
<div class="mb-3">
<label class="form-label" for="bits">Bit depth</label>
<select class="form-select" id="bits" @bind="Preset.NvDitherBits" @bind:after="ApplyNormalPreset">
@foreach (var bits in Enum.GetValues<NvDitherBits>())
{
<option value="@bits.ToString()">@bits.GetDescription()</option>
}
</select>
</div>
</div>
<div class="">
<div class="btn-group ms-2 mb-2">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
Gradient
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" @onclick="() => GradientColor = GrayGradient">Grayscale</a></li>
<li><a class="dropdown-item" @onclick="() => GradientColor = RedGradient">Red</a></li>
<li><a class="dropdown-item" @onclick="() => GradientColor = GreenGradient">Green</a></li>
<li><a class="dropdown-item" @onclick="() => GradientColor = BlueGradient">Blue</a></li>
</ul>
</div>
<div class="mb-2 col-24 mx-2" style="height: 200px; background: linear-gradient(to right, #000000 0%, @GradientColor 100%)">
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<span class="me-auto">
<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>
</div>
</div>
</div>
@code {
public const string GrayGradient = "#ffffff";
public const string RedGradient = "#ff0000";
public const string GreenGradient = "#00ff00";
public const string BlueGradient = "#0000ff";
private bool _visible;
[Parameter]
public bool Visible { get; set; }
public NvPreset? Preset { get; set; }
private string? SelectedDisplayId { get; set; }
private List<NvPreset>? displayPresets;
private string GradientColor { get; set; } = GrayGradient;
protected override async Task OnParametersSetAsync()
{
if (_visible != Visible)
{
_visible = Visible;
if (Visible)
{
await GetSettingsAsync();
}
}
}
protected async Task GetSettingsAsync()
{
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
if (displayPresets?.Any() == true)
{
Preset = displayPresets.FirstOrDefault(d => d.DisplayConfig.IsPrimary == true) ?? displayPresets.First();
SelectedDisplayId = Preset.DisplayId;
}
}
private async Task DitherOnChange(EventArgs e)
{
if (Preset == null)
{
return;
}
Preset.applyDithering = true;
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", Preset.id);
}
private async Task ApplyNormalPreset()
{
await ApplyPreset();
}
private async Task SetDitherRegistryKeyOnClick(MouseEventArgs e)
{
await ApplyPreset(true);
}
private async Task RestartDriverOnClick(MouseEventArgs e)
{
await ApplyPreset(true, true);
}
private async Task ApplyPreset(bool setRegistryKey = false, bool restartDriver = false)
{
if (Preset == null)
{
return;
}
Preset.applyDithering = true;
Preset.SetDitherRegistryKey = setRegistryKey;
Preset.RestartDriver = restartDriver;
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPreset", Preset);
}
}
@@ -0,0 +1,267 @@
@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="driverSettingsModal" 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 ? Preset.IsDisplayPreset ? $"Driver settings for display {Preset.displayName}" : $"Driver settings for {Preset.IdOrName}" : "Driver settings")</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 (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 profile in Profiles)
{
<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">
@if (Groups != null)
{
foreach (var group in Groups)
{
var groupId = $"{group.Name.Replace(' ', '_')}Settings";
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2 @(group.HasChangedSettings ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#@groupId">
@group.Name
</button>
</h2>
<div id="@groupId" class="accordion-collapse collapse" data-bs-parent="#driverSettingsAccordion">
<div class="accordion-body">
@foreach (var setting in group.DriverSettings)
{
if (NvSettingConstants.RangeDriverSettings.Contains(setting.SettingId) && setting.Values != null)
{
<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)
{
<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</label>
<select class="form-select" id="@setting.SettingId" @bind="setting.Value">
@if (Preset != null)
{
<option value="@(NvSettingConstants.UnsetDwordValue)">Unchanged</option>
}
@foreach (var settingValue in setting.Values)
{
<option value="@settingValue.Value" class="@(setting.DefaultValue == settingValue.Value ? "text-success" : "")">@settingValue.ValueName</option>
}
</select>
</div>
}
}
</div>
</div>
</div>
}
}
</div>
</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 {
public class GroupedSettings
{
public string Name { get; private set; }
public List<NvSettingItemDto> DriverSettings { get; private set; }
public bool HasChangedSettings { get; private set; }
public GroupedSettings(string name, List<NvSettingItemDto> settings, bool hasChangedSettings)
{
Name = name;
DriverSettings = settings;
HasChangedSettings = hasChangedSettings;
}
}
private NvPreset? _preset;
[Parameter]
public NvPreset? Preset { get; set; }
[Parameter]
public Func<NvPreset, Task>? AfterApply { get; set; }
private List<GroupedSettings>? Groups { get; set; }
private List<NvSettingItemDto>? DriverSettings { 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)
{
_preset = Preset;
CurrentProfileName = Preset?.DriverProfileName;
DriverSettings = null;
await LoadDriverSettings();
}
}
protected async Task LoadDriverSettings()
{
DriverSettings = await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", CurrentProfileName);
if (DriverSettings != null)
{
DriverSettings.ForEach(s => s.ValueChanged = false);
}
if (DriverSettings != null)
{
if (Preset != null)
{
foreach (var setting in DriverSettings)
{
setting.Value = setting.DefaultValue;
setting.ValueChanged = false;
}
foreach (var presetSetting in Preset.driverSettings)
{
var setting = DriverSettings.FirstOrDefault(s => s.SettingId == presetSetting.Key);
if (setting == null)
{
continue;
}
setting.Value = presetSetting.Value;
//setting.ValueChanged = false;
}
}
Groups = DriverSettings.OrderBy(s => s.GroupName).GroupBy(s => s.GroupName).Select(g =>
{
var settings = g.ToList();
var changedSettings = settings.Any(s => s.Value != s.DefaultValue && s.Value != NvSettingConstants.UnsetDwordValue);
var group = new GroupedSettings(g.Key, g.ToList(), changedSettings);
return group;
}).ToList();
}
StateHasChanged();
}
private async Task ApplyClick(MouseEventArgs e)
{
if (DriverSettings == null)
{
return;
}
var changedSettings = DriverSettings.Where(s => s.ValueChanged);
if (Preset != null)
{
Preset.UpdateDriverSettings(changedSettings);
await jsHelper.CloseModal("driverSettingsModal");
await jsHelper.OpenModal("presetModal");
return;
}
var changedSettingsDictionary = changedSettings.ToDictionary(k => k.SettingId, v => v.Value);
var result = await _rpcClientService.CallAsync<bool>("NvService", "SetDriverSettings", changedSettingsDictionary, CurrentProfileName);
}
private async Task ProfileOnChange()
{
if (CurrentProfileName?.Contains("Base") == true)
{
CurrentProfileName = null;
}
if (Preset != null)
{
Preset.DriverProfileName = CurrentProfileName;
}
DriverSettings = null;
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;
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;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,76 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" 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">NVIDIA settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Config = null"></button>
</div>
<div class="modal-body">
<form class="row g-1">
@if (Config == null)
{
<div>Loading...</div>
}
else
{
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Config.ShowOverclocking" id="showOverclocking">
<label class="form-check-label" for="showOverclocking">
Show and allow overclocking
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Config.ApplyNovideoOnStartup" id="applyNovideoOnStartup">
<label class="form-check-label" for="applyNovideoOnStartup">
Apply Novideo settings on startup
</label>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => Config = null">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
private NvServiceConfig? Config { get; set; }
private async Task ApplyClick(MouseEventArgs e)
{
if (Config == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdateConfig", Config);
if (result)
{
Config = null;
}
}
private async Task OnFocus(FocusEventArgs e)
{
if (Config != null)
{
return;
}
Config = await _rpcClientService.CallAsync<NvServiceConfig>("NvService", "GetConfig");
}
}
@@ -0,0 +1,435 @@
@page "/NVIDIA"
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@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 Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
<PageTitle>NVIDIA controller</PageTitle>
<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>
<div class="d-flex ms-auto">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
Tools
</button>
<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">
Settings
</button>
</div>
</div>
<h2>Displays</h2>
<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)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
@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>
}
<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>
}
@if (Presets == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Presets</h2>
<div class="d-flex ms-auto">
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
@foreach (var order in Enum.GetValues<PresetOrder>())
{
<option value="@order">Order by: @order.GetDescription()</option>
}
</select>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
Add
</button>
</div>
</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)
{
<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">@(preset.primaryDisplay ? "Primary display" : preset.displayName)</small>
@if (preset.ShowInQuickAccess)
{
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
}
</h5>
</div>
<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>
<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>
</span>
}
</div>
</div>
</div>
}
@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)
{
if (firstTime)
{
PresetOrder = await _localStorage.TryGet<PresetOrder>("NvidiaPresetOrder");
Presets = Presets?.OrderPresetsBy(PresetOrder);
StateHasChanged();
}
}
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)
{
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
if (result)
{
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
}
private void PresetOnEditClick(NvPreset preset)
{
CurrentPreset = preset;
}
private void PresetOnCloneClick(NvPreset preset)
{
CurrentPreset = new NvPreset(preset);
CurrentPreset.name = preset.name;
CurrentPreset.SetCloneValues();
}
private void PresetOnDeleteClick(NvPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnDeleteConfirmed()
{
if (CurrentPreset == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("NvService", "DeletePreset", CurrentPreset.id);
await RefreshPresets();
StateHasChanged();
}
private void DisplayPresetOnClick(NvPreset preset)
{
CurrentPreset = preset;
}
private void AddPresetOnClick()
{
CurrentPreset = new NvPreset();
}
private async Task AfterPresetApplied(NvPreset preset)
{
if (preset.IsDisplayPreset)
{
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
else
{
await RefreshPresets();
}
StateHasChanged();
}
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()
{
await _localStorage.SetAsync("NvidiaPresetOrder", PresetOrder);
await RefreshPresets();
}
private async Task RefreshPresets()
{
Presets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetPresets");
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
private async Task DriverSettingsOnClick(NvPreset preset)
{
DriverSettings ??= await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", Type.Missing, true);
}
}
@@ -0,0 +1,373 @@
@page "/options"
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager
@inject NotificationService _notificationService
@inject JSHelper _jsHelper;
@inject IHttpContextAccessor _httpContextAccessor
<PageTitle>Options</PageTitle>
<h1>Options</h1>
@if (config == null)
{
<div>Loading...</div>
}
else
{
<div class="row">
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">General</h5>
<form>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.AutoStart" id="startAfterLogin" @oninput="StartAfterLoginInput">
<label class="form-check-label" for="startAfterLogin">
Automatically start after login
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.CheckForUpdates" id="checkForUpdates" @oninput="CheckForUpdatesInput">
<label class="form-check-label" for="checkForUpdates">
Automatically check for updates
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.AutoInstallUpdates" id="autoInstallUpdates" @oninput="AutoInstallUpdatesInput">
<label class="form-check-label" for="autoInstallUpdates">
Automatically install updates
</label>
</div>
<div class="mb-3">
<label class="form-label" for="elevationMethod">
Elevation Method
</label>
<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.
Some operations like installing a service, changing the priority of a process or creating a temporary IP-route for improved WOL-functionality will not work without those rights.
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">UI-settings</h5>
<form>
<div class="mb-2">
<label class="form-label" for="uiType">Default UI</label>
<select class="form-select" id="uiType" @bind="config.UiType" @oninput="UiTypeOnInput">
@foreach (var type in Enum.GetValues<UiType>())
{
<option value="@type.ToString()">@type.GetDescription()</option>
}
</select>
</div>
<div class="mb-3">
<label class="form-label" for="webUiPort">
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">
<label class="form-check-label" for="minimizeOnClose">
Minimize on close
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.StartMinimized" id="startMinimized" @oninput="StartMinimizedInput">
<label class="form-check-label" for="startMinimized">
Start minimized
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.MinimizeToTray" id="minimizeToTray" @oninput="MinimizeToTrayInput">
<label class="form-check-label" for="minimizeToTray">
Minimize to system tray
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.UseDarkMode" id="useDarkMode" @oninput="UseDarkModeInput">
<label class="form-check-label" for="useDarkMode">
Dark Mode
</label>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Modules</h5>
<form>
@foreach (var module in config.Modules)
{
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="@("module_" + module.DisplayName)" @bind="module.IsActive" @oninput="(ChangeEventArgs e) => ModuleOnInput(module, e)">
<label class="form-check-label" for="@("module_" + module.DisplayName)">@module.DisplayName</label>
</div>
}
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Advanced</h5>
<form>
<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">
Use Raw Input for shortcuts (hot keys)
</label>
<div id="useRawInputHelp" class="form-text">This enables shortcuts to work during applications/games that block certain keys (like WinKey or Control). NOTE: if the application in the foreground runs with higher privileges than ColorControl, Raw Input does not work and normal hot keys are used</div>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.SetMinTmlAndMaxTml" id="setMinTmlAndMaxTml" @oninput="SetMinMaxTmlInput">
<label class="form-check-label" for="setMinTmlAndMaxTml">
Set MinTML and MaxTML when applying color profiles
</label>
</div>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.DisableErrorPopupWhenApplyingPreset" id="disableErrorPopupWhenApplyingPreset" @oninput="DisableErrorPopupInput">
<label class="form-check-label" for="disableErrorPopupWhenApplyingPreset">
Disable error popup when applying presets
</label>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Miscellaneous</h5>
<form>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.FixChromeFonts" id="fixChromeFonts" aria-describedby="fixChromeFontsHelp" @bind:after="FixChromeFontsInput">
<label class="form-check-label" for="fixChromeFonts">
ClearType: fix the bad fonts in Chrome when using a monitor with a different pixel structure
</label>
<div id="fixChromeFontsHelp" class="form-text">This will add the parameter --disable-lcd-text to Chrome which enables grayscale anti-aliasing</div>
</div>
<ShortcutInput Label="Shortcut to start the blank screensaver" @bind-Shortcut="config.ScreenSaverShortcut" @bind-Shortcut:after="UpdateScreenSaverShortcut" />
</form>
</div>
</div>
</div>
</div>
}
@code {
private Config? config;
private bool IsEmbeddedBrowser;
private bool IsChangingUiPort;
protected override async Task OnInitializedAsync()
{
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
IsEmbeddedBrowser = _httpContextAccessor.HttpContext?.Request.Headers["user-agent"].ToString().Contains("ColorControlEmbedded") == true;
}
private async Task SaveConfig()
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetConfig", config);
}
private async Task UseDarkModeInput(ChangeEventArgs e)
{
var value = e.Value != null ? (bool)e.Value : false;
var result = await UpdateBoolSetting("SetDarkMode", e);
if (result)
{
AppState.SetTheme(value ? "dark" : "light");
//ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme";
_navigationManager.Refresh();
}
}
private async Task StartAfterLoginInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetStartAfterLogin", e);
}
private async Task MinimizeOnCloseInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetMinimizeOnClose", e);
}
private async Task StartMinimizedInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetStartMinimized", e);
}
private async Task MinimizeToTrayInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetMinimizeToTray", e);
}
private async Task CheckForUpdatesInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetCheckForUpdates", e);
}
private async Task AutoInstallUpdatesInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetAutoInstallUpdates", e);
}
private async Task ModuleOnInput(Module module, ChangeEventArgs e)
{
module.IsActive = e.Value != null ? (bool)e.Value : false;
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "UpdateModule", module);
if (result)
{
_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()
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetProcessMonitorPollingInterval", config!.ProcessMonitorPollingInterval);
}
private async Task UseRawInputInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetUseRawInput", e);
}
private async Task SetMinMaxTmlInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetMinTmlAndMaxTml", e);
}
private async Task DisableErrorPopupInput(ChangeEventArgs e)
{
await UpdateBoolSetting("SetDisableErrorPopupWhenApplyingPreset", e);
}
private async Task FixChromeFontsInput()
{
await UpdateBoolSetting2("SetFixChromeFonts", config!.FixChromeFonts);
}
private async Task UpdateScreenSaverShortcut()
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetScreenSaverShortcut", config!.ScreenSaverShortcut);
}
private async Task<bool> UpdateBoolSetting(string method, ChangeEventArgs e)
{
var value = e.Value != null ? (bool)e.Value : false;
return await UpdateBoolSetting2(method, value);
}
private async Task<bool> UpdateBoolSetting2(string method, bool value)
{
return await _rpcClientService.CallAsync<bool>("OptionsService", method, value);
}
private async Task ElevationMethodInput(ChangeEventArgs e)
{
var value = e.Value != null ? Enum.Parse<ElevationMethod>((string)e.Value) : ElevationMethod.None;
await _rpcClientService.CallAsync<bool>("OptionsService", "SetElevationMethod", value);
}
private async Task UiTypeOnInput(ChangeEventArgs e)
{
if (Enum.TryParse<UiType>(e.Value?.ToString(), out var uiType))
{
await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiType", uiType);
config!.UiType = uiType;
}
}
private async Task UiPortBlur()
{
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;
}
}
@@ -0,0 +1,264 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services
@using ColorControl.Shared.Common
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<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>
<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 (Device == 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="Device.Name" required />
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" @bind="Device.IsCustom" id="isCustom">
<label class="form-check-label" for="isCustom">
Is custom device
</label>
</div>
<div class="mb-2">
<label class="form-label" for="ipAddress">IP-address</label>
<input class="form-control" id="ipAddress" type="text" @bind="Device.IpAddress" disabled="@(!Device.IsCustom)" required="Device.IsCustom" />
</div>
<div class="mb-2">
<label class="form-label" for="macAddress">MAC-address</label>
<input class="form-control" id="macAddress" type="text" @bind="Device.MacAddress" disabled="@(!Device.IsCustom)" />
</div>
@if (!Device.IsConnected)
{
<div class="mb-2">
<button class="btn btn-primary me-2" type="button" disabled="@IsTestingConnection" @onclick="TestConnectionClick">
@if (IsTestingConnection)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">Test connection</span>
</button>
@if (IsTestConnectionSuccessful != null)
{
if (IsTestConnectionSuccessful == true)
{
<span class="text-success">Connection test was successful!</span>
}
else
{
<span class="text-danger">Connection failed!</span>
}
}
</div>
}
<div class="accordion accordion-flush" id="accordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
<div id="deviceOptions" class="accordion-collapse collapse" data-bs-parent="#accordion">
<div class="accordion-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterStartup" @bind="Device.Options.PowerOnAfterStartup">
<label class="form-check-label" for="checkPowerOnAfterStartup">Power on after startup</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterResume" @bind="Device.Options.PowerOnAfterResume">
<label class="form-check-label" for="checkPowerOnAfterResume">Power on after resume from standby</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnShutdown" @bind="Device.Options.PowerOffOnShutdown">
<label class="form-check-label" for="checkPowerOffOnShutdown">Power off on shutdown</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnStandby" @bind="Device.Options.PowerOffOnStandby">
<label class="form-check-label" for="checkPowerOffOnStandby">Power off on standby</label>
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnScreensaverStart" @bind="Device.Options.PowerOffOnScreenSaver">
<label class="form-check-label" for="checkPowerOffOnScreensaverStart">Power off when screensaver activates</label>
<button class="btn btn-sm btn-primary ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#screenSaverSettings" aria-expanded="false" aria-controls="screenSaverSettings">
Settings
</button>
</div>
<div class="collapse mb-3" id="screenSaverSettings">
<div class="card card-body">
<div class="mb-2">
<label class="form-label" for="minScreenSaverDuration">Minimal screen saver duration (seconds)</label>
<input class="form-control" id="minScreenSaverDuration" type="number" @bind="Device.Options.ScreenSaverMinimalDuration" />
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOffInsteadOfPowerOff" @bind="Device.Options.TurnScreenOffOnScreenSaver">
<label class="form-check-label" for="checkTurnScreenOffInsteadOfPowerOff">Turn screen off instead of power off</label>
<div class="form-text">NOTE: turning the screen off might not work correctly on all models</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOnInsteadOfPowerOn" @bind="Device.Options.TurnScreenOnAfterScreenSaver">
<label class="form-check-label" for="checkTurnScreenOnInsteadOfPowerOn">Turn screen on instead of power on</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPerformActionOnManualScreenSaver" @bind="Device.Options.HandleManualScreenSaver">
<label class="form-check-label" for="checkPerformActionOnManualScreenSaver">Perform action even on manually executed screen saver</label>
</div>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnOnScreensaverEnd" @bind="Device.Options.PowerOnAfterScreenSaver">
<label class="form-check-label" for="checkPowerOnOnScreensaverEnd">Power on when screensaver deactivates</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterManualPowerOff" @bind="Device.Options.PowerOnAfterManualPowerOff">
<label class="form-check-label" for="checkPowerOnAfterManualPowerOff">Power on even after manual power off</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkAllowTriggers" @bind="Device.Options.TriggersEnabled">
<label class="form-check-label" for="checkAllowTriggers">Allow triggers to be fired for this device</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOn" @bind="Device.Options.PowerOnByWindows">
<label class="form-check-label" for="checkWindowsPowerOn">Use Windows power settings to power on</label>
</div>
<div class="form-check form-switch">
<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>
</div>
</div>
</div>
}
</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>
</div>
</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;
[Parameter]
public SamsungDeviceDto? Device { get; set; }
[Parameter]
public Func<SamsungDeviceDto, Task>? AfterApply { get; set; }
private bool IsTestingConnection;
private bool? IsTestConnectionSuccessful;
private string WasValidated = "";
private bool IsVisible;
protected override void OnParametersSet()
{
if (_device != Device)
{
_device = Device;
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Device == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdateDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
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;
try
{
IsTestConnectionSuccessful = await _rpcClientService.CallWithOptionsAsync<bool>("SamsungService", "TestDevice", new RpcUiClientOptions { Timeout = 120000 }, Device);
}
finally
{
IsTestingConnection = false;
}
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Device?.Name) && (Device?.IsCustom != true || !string.IsNullOrEmpty(Device?.IpAddress));
}
}
@@ -0,0 +1,170 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.Samsung.SamsungPreset>
@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-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
{
<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>
@if (Devices != null)
{
<div class="mb-2">
<label class="form-label" for="device">Device</label>
<select class="form-select" id="device" @bind="Preset.DeviceMacAddress">
<option value="">Globally selected device</option>
@foreach (var device in Devices)
{
<option value="@device.MacAddress">@device.Name</option>
}
</select>
</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>
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
<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">
Advanced settings
</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" CustomItems="CustomItems" ActionItems="CustomActions" SamsungPresetsEnabled="false" TPreset="SamsungPreset"></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>
}
</form>
</div>
<div class="modal-footer">
<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>
</div>
</div>
</div>
@code {
private SamsungPreset? Preset;
[Parameter]
public SamsungPreset? PresetParam { get; set; }
[Parameter]
public List<SamsungDeviceDto>? Devices { get; set; }
[Parameter]
public Func<SamsungPreset, Task>? AfterApply { get; set; }
private List<CustomStepItem>? CustomItems;
private List<ActionStepItem>? CustomActions;
private string WasValidated = "";
private bool IsVisible;
protected override void OnParametersSet()
{
if (PresetParam == null)
{
return;
}
Preset = PresetParam.CloneWithId();
}
protected override async Task OnInitializedAsync()
{
CustomItems = [new CustomStepItem("RC buttons", Enum.GetValues<ButtonType>().Select(b => b.ToString()).ToList())];
var actions = await _rpcClientService.CallAsync<List<InvokableActionDto<SamsungPreset>>>("SamsungService", "GetInvokableActions");
if (actions != null)
{
CustomActions = [new ActionStepItem("Actions", actions.Where(a => !a.Advanced).ToList())];
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdatePreset", Preset);
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name);
}
}
@@ -0,0 +1,255 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal ModalId="serviceMenuModal" Title="@Title" ConfirmCaption="@ConfirmCaption" SecondaryCaption="@SecondaryCaption"
Fields="Fields" OnConfirm="NextClick" OnSecondary="SecondaryClick" Dismiss="@Dismiss" OnShow="OnShow"></FieldsModal>
@code {
[Parameter]
public List<SamsungDeviceDto>? Devices { get; set; }
private string? Title { get; set; }
private string? ConfirmCaption { get; set; }
private string? SecondaryCaption { get; set; }
private string? Dismiss { get; set; }
private List<FieldDefinition>? Fields { get; set; }
private FieldDefinition? DeviceField { get; set; }
private Func<Task>? NextAction;
private Func<Task>? SecondaryAction;
protected override void OnInitialized()
{
// NextAction = FirstStepExit;
// Title = "Access Service Menu";
// ConfirmCaption = "Next >";
// 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 Task OnShow()
{
NextAction = FirstStepExit;
Title = "Access Service Menu";
ConfirmCaption = "Next >";
SecondaryCaption = null;
SecondaryAction = null;
Dismiss = null;
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")];
if (Devices?.Any() == true)
{
DeviceField = FieldDefinition.CreateDropDownField("Device", Devices.Select(d => d.ToString()).ToList());
DeviceField.Value = Devices.FirstOrDefault(d => d.IsSelected)?.ToString();
Fields.Insert(0, DeviceField);
}
StateHasChanged();
return Task.CompletedTask;
}
private async Task NextClick()
{
if (NextAction == null)
{
return;
}
await NextAction();
}
private async Task SecondaryClick()
{
if (SecondaryAction != null)
{
await SecondaryAction();
}
}
private async Task FirstStepExit()
{
if (Fields == null)
{
return;
}
var menuType = Fields.First(f => f.ValueType == typeof(ServiceMenuType)).ValueAsEnum<ServiceMenuType>();
if (menuType == ServiceMenuType.FactoryMenu)
{
await FactoryMenuFirstStepEnter();
}
else
{
await HospitalityMenuFirstStepEnter();
}
}
protected async Task FactoryMenuFirstStepEnter()
{
await ExecutePreset(SamsungFactoryMenuPresets.Preset1);
NextAction = FactoryMenuSecondStepEnter;
Title = "Access Service Menu - Step 2";
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. 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 Task FactoryMenuSecondStepEnterWithoutExecute()
{
NextAction = FactoryMenuSecondStepExit;
Title = "Exit Service Menu";
ConfirmCaption = "Next >";
SecondaryCaption = null;
SecondaryAction = null;
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()
{
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();
}
protected async Task FactoryMenuSecondStepExit()
{
if (Fields == null)
{
return;
}
var menuType = Fields.First().ValueAsEnum<ExitServiceMenuType>();
if (menuType == ExitServiceMenuType.Exit)
{
await ExitServiceMenuEnter();
}
else
{
await RebootTvEnter();
}
}
protected async Task ExitServiceMenuEnter()
{
await ExecutePreset(SamsungFactoryMenuPresets.Exit);
NextAction = null;
Title = "Exited Service Menu";
Dismiss = "modal";
ConfirmCaption = "OK";
SecondaryCaption = "Factory";
SecondaryAction = ExecuteFactoryKey;
Fields = [AddField("Service Menu - Exited", null, FieldType.Label, "Exited the service menu. Click OK to close. If the TV is stuck on cycling full screen RGB images, use the 'Factory' button below.")];
StateHasChanged();
}
protected async Task RebootTvEnter()
{
await ExecutePreset(SamsungGenericPresets.Reboot);
NextAction = null;
Title = "Rebooted TV";
Dismiss = "modal";
ConfirmCaption = "OK";
Fields = [AddField("Service Menu - Rebooted TV", null, FieldType.Label, "Rebooted the TV. Power on the TV manually if the TV does not come on automatically.")];
StateHasChanged();
}
protected async Task HospitalityMenuFirstStepEnter()
{
await ExecutePreset(SamsungHospitalityMenuPresets.Preset1);
NextAction = HospitalityMenuSecondStepEnter;
Title = "Access Service Menu - Step 2";
ConfirmCaption = "Next >";
Fields = [AddField("Service Menu opened?", null, FieldType.Label, "If the menu is opened with the 'Hospitality Mode' item highlighted and the rest disabled, click 'Next >' to continue or 'X' to stop")];
StateHasChanged();
}
protected async Task HospitalityMenuSecondStepEnter()
{
await ExecutePreset(SamsungHospitalityMenuPresets.Preset2);
NextAction = HospitalityMenuThirdStepEnter;
Title = "Access Service Menu - Step 3";
ConfirmCaption = "Next >";
SecondaryCaption = "Skip";
Fields = [AddField("Service Menu - Advanced", null, FieldType.Label, "If the 'Advanced' item is highlighted and you want to access these settings, click 'Next >' or click 'Skip' to skip")];
StateHasChanged();
}
protected async Task HospitalityMenuThirdStepEnter()
{
await ExecutePreset(SamsungHospitalityMenuPresets.Preset3);
await FactoryMenuSecondStepEnterWithoutExecute();
}
protected async Task ExecuteFactoryKey()
{
await ExecutePreset(SamsungFactoryMenuPresets.KeyFactory);
}
private async Task ExecutePreset(SamsungPreset preset)
{
if (Devices != null && DeviceField?.Value != null)
{
var device = Devices.FirstOrDefault(d => d.ToString() == DeviceField.Value.ToString());
preset.DeviceMacAddress = device?.MacAddress;
}
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPreset", preset);
//await Task.Delay(2000);
}
private FieldDefinition AddField(string label, object? value, FieldType fieldType = FieldType.DropDown, string? subLabel = null)
{
return new FieldDefinition
{
FieldType = fieldType,
Value = value,
StringValue = value?.ToString(),
ValueType = value?.GetType(),
Label = label,
SubLabel = subLabel
};
}
}
@@ -0,0 +1,34 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
<FieldsModal Title="Samsung settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
@code {
private SamsungServiceConfigDto? Config { get; set; }
private List<FieldDefinition>? Fields { get; set; }
protected async Task ModalOnShow()
{
Config = await _rpcClientService.CallAsync<SamsungServiceConfigDto>("SamsungService", "GetConfig");
Fields = SamsungFields.GetSettingsFields(Config);
StateHasChanged();
}
private async Task ApplyClick()
{
if (Config == null)
{
return;
}
FieldDefinition.UpdateObject(Fields, Config);
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdateConfig", Config);
}
}
@@ -0,0 +1,371 @@
@page "/Samsung"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@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 Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject ProtectedLocalStorage _localStorage
<PageTitle>Samsung controller</PageTitle>
<SamsungSettings />
<SamsungServiceMenu Devices="Devices"></SamsungServiceMenu>
<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
</button>
<ul class="dropdown-menu">
@foreach (var button in Enum.GetValues<ButtonType>())
{
<li>
<a class="dropdown-item" @onclick="() => ButtonOnClick(button)">@button.GetDescription()</a>
</li>
}
</ul>
</div>
@if (InvokableActions != null)
{
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
Expert
</button>
<ul class="dropdown-menu">
@foreach (var action in InvokableActions)
{
if (action.Name == "ServiceMenu")
{
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#serviceMenuModal">Service menu</a></li>
}
else
{
<li><a class="dropdown-item" @onclick="() => ActionOnclick(action)">@action.Name</a></li>
}
}
</ul>
</div>
}
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
Settings
</button>
</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" />
@if (Devices == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Devices</h2>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" disabled="@IsRefreshDisabled" @onclick="RefreshDevicesOnClick">
@if (IsRefreshDisabled)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#samsungDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
</div>
<div class="row mb-3">
@if (!Devices.Any())
{
<span>No devices found</span>
}
@foreach (var device in Devices)
{
<div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@device.Name
@if (device.IsConnected)
{
<span class="ms-2 badge text-bg-success" title="Device is connected">Connected</span>
}
else
{
<span class="ms-2 badge text-bg-danger" title="Device is disconnected">Disconnected</span>
}
@if (device.IsSelected)
{
<span class="ms-2 badge text-bg-primary" title="Global device">Primary</span>
}
</span>
<div class="d-flex ms-auto">
@if (!device.IsSelected)
{
<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="#samsungDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
</div>
<div class="card-body">
<small class="text-muted">@device.IpAddress (@(device.MacAddress ?? "No MAC-address entered"))</small>
</div>
</div>
</div>
}
</div>
}
@if (Presets == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Presets</h2>
<div class="d-flex ms-auto">
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
@foreach (var order in Enum.GetValues<PresetOrder>())
{
<option value="@order">Order by: @order.GetDescription()</option>
}
</select>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
Add
</button>
</div>
</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)
{
<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>
@if (preset.Triggers.Any(t => t.Trigger != PresetTriggerType.None))
{
<div><small class="text-muted">Trigger: @preset.Triggers[0].ToString()</small></div>
}
<div><small class="text-muted">@preset.shortcut</small></div>
</div>
<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>
</span>
}
</div>
</div>
</div>
}
@code {
private List<SamsungPreset>? Presets;
private List<SamsungDeviceDto>? Devices;
private List<InvokableActionDto<SamsungPreset>>? InvokableActions;
private SamsungServiceConfigDto? Config;
private SamsungPreset? CurrentPreset;
private SamsungDeviceDto? CurrentDevice;
private bool IsRefreshDisabled;
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");
}
protected override async Task OnAfterRenderAsync(bool firstTime)
{
if (firstTime)
{
PresetOrder = await _localStorage.TryGet<PresetOrder>("SamsungPresetOrder");
Presets = Presets?.OrderPresetsBy(PresetOrder);
StateHasChanged();
}
}
private async Task PresetOnClick(SamsungPreset preset)
{
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPresetWithId", preset.id);
}
private void PresetOnEditClick(SamsungPreset preset)
{
CurrentPreset = preset;
}
private void PresetOnCloneClick(SamsungPreset preset)
{
CurrentPreset = new SamsungPreset(preset);
CurrentPreset.SetCloneValues();
}
private void PresetOnDeleteClick(SamsungPreset preset)
{
CurrentPreset = preset;
}
private async Task PresetOnDeleteConfirmed()
{
if (CurrentPreset == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("SamsungService", "DeletePreset", CurrentPreset.id);
await RefreshPresets();
StateHasChanged();
}
private void DeviceOnClick(SamsungDeviceDto device)
{
CurrentDevice = device;
}
private void AddDeviceOnClick()
{
CurrentDevice = new SamsungDeviceDto { IsCustom = true };
}
private void AddPresetOnClick()
{
CurrentPreset = new SamsungPreset();
}
private async Task AfterPresetApplied(SamsungPreset preset)
{
await RefreshPresets();
StateHasChanged();
}
private async Task AfterDeviceApplied(SamsungDeviceDto device)
{
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
StateHasChanged();
}
private async Task RefreshDevicesOnClick(MouseEventArgs e)
{
IsRefreshDisabled = true;
try
{
await _rpcClientService.CallAsync<bool>("SamsungService", "RefreshDevices");
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
}
finally
{
IsRefreshDisabled = false;
}
StateHasChanged();
}
private async Task ActionOnclick(InvokableActionDto<SamsungPreset> action)
{
await _rpcClientService.CallAsync<bool>("SamsungService", "ExecuteInvokableAction", action);
}
private string GetDeviceName(SamsungPreset preset)
{
if (string.IsNullOrEmpty(preset.DeviceMacAddress))
{
return "Global";
}
return Devices?.FirstOrDefault(d => d.MacAddress == preset.DeviceMacAddress)?.Name ?? preset.DeviceMacAddress;
}
private async Task SetAsPrimaryOnClick(SamsungDeviceDto device)
{
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "SetSelectedDevice", device);
if (result)
{
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
}
}
private async Task PresetOrderChanged()
{
await _localStorage.SetAsync("SamsungPresetOrder", PresetOrder);
await RefreshPresets();
}
private async Task RefreshPresets()
{
Presets = await _rpcClientService.CallAsync<List<SamsungPreset>>("SamsungService", "GetPresets");
Presets = Presets?.OrderPresetsBy(PresetOrder);
}
private async Task ButtonOnClick(ButtonType button)
{
var preset = new SamsungPreset(button.ToString(), null, [button.ToString()]);
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPreset", preset);
}
}
+6
View File
@@ -0,0 +1,6 @@
<Router AppAssembly="@typeof(Blazor).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
+10
View File
@@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using ColorControl.UI
@using ColorControl.UI.Components
+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;
}
}
}
@@ -0,0 +1,25 @@
using ColorControl.Shared.Contracts;
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;
}
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;
}
}
+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)
};
}
+20
View File
@@ -0,0 +1,20 @@
using ColorControl.Shared.Contracts;
using ColorControl.UI;
using ColorControl.UI.Services;
using System.Diagnostics;
var arguments = args;
if (Debugger.IsAttached)
{
var rpcServer = new RpcUiClientService(null);
var options = new RpcUiClientOptions
{
Timeout = 10000
};
var config = await rpcServer.CallWithOptionsAsync<Config>("OptionsService", "GetConfig", options);
arguments = ["", config.UseDarkMode.ToString(), config.UiPort.ToString(), config.UiAllowRemoteConnections.ToString()];
}
await Blazor.Start(arguments);
@@ -0,0 +1,23 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5175",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7135;http://localhost:5175",
"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;
}
}
+16
View File
@@ -0,0 +1,16 @@
namespace ColorControl.UI;
public class AppState
{
public string SelectedTheme { get; set; } = "light";
public event Action? OnChange;
public void SetTheme(string theme)
{
SelectedTheme = theme;
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
@@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
+55
View File
@@ -0,0 +1,55 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
.dropdown-item, .list-group-item {
cursor: pointer;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+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

-31
View File
@@ -1,31 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorControl", "ColorControl\ColorControl.csproj", "{A6005D08-70F0-44CB-A384-9219EBA84F21}"
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|x64
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.Build.0 = Debug|x64
{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|x64
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|x64.Build.0 = Release|x64
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>
+1
View File
@@ -0,0 +1 @@
{"Build":"0.3.261602\u002Befb3cc30a24e699e6e4337f263a2b9478f04f618","CurrentProject":"ColorControl.csproj","EntryPoints":["ColorControl.csproj"],"Properties":{}}
-529
View File
@@ -1,529 +0,0 @@
#region Copyright
/*******************************************************************************
Copyright(c) 2008 - 2009 Advanced Micro Devices, Inc. All Rights Reserved.
Copyright (c) 2002 - 2006 ATI Technologies Inc. All Rights Reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDED BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
File: ADL.cs
Purpose: Implements ADL interface
Description: Implements some of the methods defined in ADL interface.
********************************************************************************/
#endregion Copyright
#region Using
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;
using FARPROC = System.IntPtr;
using HMODULE = System.IntPtr;
#endregion Using
#region ATI.ADL
namespace ATI.ADL
{
#region Export Delegates
/// <summary> ADL Memory allocation function allows ADL to callback for memory allocation</summary>
/// <param name="size">input size</param>
/// <returns> retrun ADL Error Code</returns>
internal delegate IntPtr ADL_Main_Memory_Alloc (int size);
// ///// <summary> ADL Create Function to create ADL Data</summary>
/// <param name="callback">Call back functin pointer which is ised to allocate memeory </param>
/// <param name="enumConnectedAdapters">If it is 1, then ADL will only retuen the physical exist adapters </param>
///// <returns> retrun ADL Error Code</returns>
internal delegate int ADL_Main_Control_Create(ADL_Main_Memory_Alloc callback, int enumConnectedAdapters);
/// <summary> ADL Destroy Function to free up ADL Data</summary>
/// <returns> retrun ADL Error Code</returns>
internal delegate int ADL_Main_Control_Destroy ();
/// <summary> ADL Function to get the number of adapters</summary>
/// <param name="numAdapters">return number of adapters</param>
/// <returns> retrun ADL Error Code</returns>
internal delegate int ADL_Adapter_NumberOfAdapters_Get (ref int numAdapters);
/// <summary> ADL Function to get the GPU adapter information</summary>
/// <param name="info">return GPU adapter information</param>
/// <param name="inputSize">the size of the GPU adapter struct</param>
/// <returns> retrun ADL Error Code</returns>
internal delegate int ADL_Adapter_AdapterInfo_Get (IntPtr info, int inputSize);
/// <summary> Function to determine if the adapter is active or not.</summary>
/// <remarks>The function is used to check if the adapter associated with iAdapterIndex is active</remarks>
/// <param name="adapterIndex"> Adapter Index.</param>
/// <param name="status"> Status of the adapter. True: Active; False: Dsiabled</param>
/// <returns>Non zero is successfull</returns>
internal delegate int ADL_Adapter_Active_Get(int adapterIndex, ref int status);
/// <summary>Get display information based on adapter index</summary>
/// <param name="adapterIndex">Adapter Index</param>
/// <param name="numDisplays">return the total number of supported displays</param>
/// <param name="displayInfoArray">return ADLDisplayInfo Array for supported displays' information</param>
/// <param name="forceDetect">force detect or not</param>
/// <returns>return ADL Error Code</returns>
internal delegate int ADL_Display_DisplayInfo_Get(int adapterIndex, ref int numDisplays, out IntPtr displayInfoArray, int forceDetect);
internal delegate int ADL_Display_Size_Get(int adapterIndex, int displayIndex, ref int lpWidth, ref int lpHeight, ref int lpDefaultWidth, ref int lpDefaultHeight, ref int lpMinWidth, ref int lpMinHeight, ref int lpMaxWidth, ref int lpMaxHeight, ref int lpStepWidth, ref int lpStepHeight);
#endregion Export Delegates
#region Export Struct
#region ADLAdapterInfo
/// <summary> ADLAdapterInfo Structure</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ADLAdapterInfo
{
/// <summary>The size of the structure</summary>
int Size;
/// <summary> Adapter Index</summary>
internal int AdapterIndex;
/// <summary> Adapter UDID</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string UDID;
/// <summary> Adapter Bus Number</summary>
internal int BusNumber;
/// <summary> Adapter Driver Number</summary>
internal int DriverNumber;
/// <summary> Adapter Function Number</summary>
internal int FunctionNumber;
/// <summary> Adapter Vendor ID</summary>
internal int VendorID;
/// <summary> Adapter Adapter name</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string AdapterName;
/// <summary> Adapter Display name</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string DisplayName;
/// <summary> Adapter Present status</summary>
internal int Present;
/// <summary> Adapter Exist status</summary>
internal int Exist;
/// <summary> Adapter Driver Path</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string DriverPath;
/// <summary> Adapter Driver Ext Path</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string DriverPathExt;
/// <summary> Adapter PNP String</summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string PNPString;
/// <summary> OS Display Index</summary>
internal int OSDisplayIndex;
}
/// <summary> ADLAdapterInfo Array</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ADLAdapterInfoArray
{
/// <summary> ADLAdapterInfo Array </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = (int)ADL.ADL_MAX_ADAPTERS)]
internal ADLAdapterInfo[] ADLAdapterInfo;
}
#endregion ADLAdapterInfo
#region ADLDisplayInfo
/// <summary> ADLDisplayID Structure</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ADLDisplayID
{
/// <summary> Display Logical Index </summary>
internal int DisplayLogicalIndex;
/// <summary> Display Physical Index </summary>
internal int DisplayPhysicalIndex;
/// <summary> Adapter Logical Index </summary>
internal int DisplayLogicalAdapterIndex;
/// <summary> Adapter Physical Index </summary>
internal int DisplayPhysicalAdapterIndex;
}
/// <summary> ADLDisplayInfo Structure</summary>
[StructLayout(LayoutKind.Sequential)]
internal struct ADLDisplayInfo
{
/// <summary> Display Index </summary>
internal ADLDisplayID DisplayID;
/// <summary> Display Controller Index </summary>
internal int DisplayControllerIndex;
/// <summary> Display Name </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string DisplayName;
/// <summary> Display Manufacturer Name </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = (int)ADL.ADL_MAX_PATH)]
internal string DisplayManufacturerName;
/// <summary> Display Type : < The Display type. CRT, TV,CV,DFP are some of display types,</summary>
internal int DisplayType;
/// <summary> Display output type </summary>
internal int DisplayOutputType;
/// <summary> Connector type</summary>
internal int DisplayConnector;
///<summary> Indicating the display info bits' mask.<summary>
internal int DisplayInfoMask;
///<summary> Indicating the display info value.<summary>
internal int DisplayInfoValue;
}
#endregion ADLDisplayInfo
#endregion Export Struct
#region ADL Class
/// <summary> ADL Class</summary>
internal static class ADL
{
#region Internal Constant
/// <summary> Define the maximum path</summary>
internal const int ADL_MAX_PATH = 256;
/// <summary> Define the maximum adapters</summary>
internal const int ADL_MAX_ADAPTERS = 40 /* 150 */;
/// <summary> Define the maximum displays</summary>
internal const int ADL_MAX_DISPLAYS = 40 /* 150 */;
/// <summary> Define the maximum device name length</summary>
internal const int ADL_MAX_DEVICENAME = 32;
/// <summary> Define the successful</summary>
internal const int ADL_SUCCESS = 0;
/// <summary> Define the failure</summary>
internal const int ADL_FAIL = -1;
/// <summary> Define the driver ok</summary>
internal const int ADL_DRIVER_OK = 0;
/// <summary> Maximum number of GL-Sync ports on the GL-Sync module </summary>
internal const int ADL_MAX_GLSYNC_PORTS = 8;
/// <summary> Maximum number of GL-Sync ports on the GL-Sync module </summary>
internal const int ADL_MAX_GLSYNC_PORT_LEDS = 8;
/// <summary> Maximum number of ADLMOdes for the adapter </summary>
internal const int ADL_MAX_NUM_DISPLAYMODES = 1024;
#endregion Internal Constant
#region Class ADLImport
/// <summary> ADLImport class</summary>
private static class ADLImport
{
#region Internal Constant
/// <summary> Atiadlxx_FileName </summary>
internal const string Atiadlxx_FileName = "atiadlxx.dll";
/// <summary> Kernel32_FileName </summary>
internal const string Kernel32_FileName = "kernel32.dll";
#endregion Internal Constant
#region DLLImport
[DllImport(Kernel32_FileName)]
internal static extern HMODULE GetModuleHandle (string moduleName);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Main_Control_Create (ADL_Main_Memory_Alloc callback, int enumConnectedAdapters);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Main_Control_Destroy ();
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Main_Control_IsFunctionValid (HMODULE module, string procName);
[DllImport(Atiadlxx_FileName)]
internal static extern FARPROC ADL_Main_Control_GetProcAddress (HMODULE module, string procName);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Adapter_NumberOfAdapters_Get (ref int numAdapters);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Adapter_AdapterInfo_Get (IntPtr info, int inputSize);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Adapter_Active_Get(int adapterIndex, ref int status);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Display_DisplayInfo_Get(int adapterIndex, ref int numDisplays, out IntPtr displayInfoArray, int forceDetect);
[DllImport(Atiadlxx_FileName)]
internal static extern int ADL_Display_Size_Get(int iAdapterIndex, int iDisplayIndex, ref int lpWidth, ref int lpHeight, ref int lpDefaultWidth, ref int lpDefaultHeight, ref int lpMinWidth, ref int lpMinHeight, ref int lpMaxWidth, ref int lpMaxHeight, ref int lpStepWidth, ref int lpStepHeight);
#endregion DLLImport
}
#endregion Class ADLImport
#region Class ADLCheckLibrary
/// <summary> ADLCheckLibrary class</summary>
private class ADLCheckLibrary
{
#region Private Members
private HMODULE ADLLibrary = System.IntPtr.Zero;
#endregion Private Members
#region Static Members
/// <summary> new a private instance</summary>
private static ADLCheckLibrary ADLCheckLibrary_ = new ADLCheckLibrary();
#endregion Static Members
#region Constructor
/// <summary> Constructor</summary>
private ADLCheckLibrary ()
{
try
{
if (1 == ADLImport.ADL_Main_Control_IsFunctionValid(IntPtr.Zero, "ADL_Main_Control_Create"))
{
ADLLibrary = ADLImport.GetModuleHandle(ADLImport.Atiadlxx_FileName);
}
}
catch (DllNotFoundException) { }
catch (EntryPointNotFoundException) { }
catch (Exception) { }
}
#endregion Constructor
#region Destructor
/// <summary> Destructor to force calling ADL Destroy function before free up the ADL library</summary>
~ADLCheckLibrary ()
{
if (System.IntPtr.Zero != ADLCheckLibrary_.ADLLibrary)
{
ADLImport.ADL_Main_Control_Destroy();
}
}
#endregion Destructor
#region Static IsFunctionValid
/// <summary> Check the import function to see it exists or not</summary>
/// <param name="functionName"> function name</param>
/// <returns>return true, if function exists</returns>
internal static bool IsFunctionValid (string functionName)
{
bool result = false;
if (System.IntPtr.Zero != ADLCheckLibrary_.ADLLibrary)
{
if (1 == ADLImport.ADL_Main_Control_IsFunctionValid(ADLCheckLibrary_.ADLLibrary, functionName))
{
result = true;
}
}
return result;
}
#endregion Static IsFunctionValid
#region Static GetProcAddress
/// <summary> Get the unmanaged function pointer </summary>
/// <param name="functionName"> function name</param>
/// <returns>return function pointer, if function exists</returns>
internal static FARPROC GetProcAddress (string functionName)
{
FARPROC result = System.IntPtr.Zero;
if (System.IntPtr.Zero != ADLCheckLibrary_.ADLLibrary)
{
result = ADLImport.ADL_Main_Control_GetProcAddress(ADLCheckLibrary_.ADLLibrary, functionName);
}
return result;
}
#endregion Static GetProcAddress
}
#endregion Class ADLCheckLibrary
#region Export Functions
#region ADL_Main_Memory_Alloc
/// <summary> Build in memory allocation function</summary>
internal static ADL_Main_Memory_Alloc ADL_Main_Memory_Alloc = ADL_Main_Memory_Alloc_;
/// <summary> Build in memory allocation function</summary>
/// <param name="size">input size</param>
/// <returns>return the memory buffer</returns>
private static IntPtr ADL_Main_Memory_Alloc_ (int size)
{
IntPtr result = Marshal.AllocCoTaskMem(size);
return result;
}
#endregion ADL_Main_Memory_Alloc
#region ADL_Main_Memory_Free
/// <summary> Build in memory free function</summary>
/// <param name="buffer">input buffer</param>
internal static void ADL_Main_Memory_Free (IntPtr buffer)
{
if (IntPtr.Zero != buffer)
{
Marshal.FreeCoTaskMem(buffer);
}
}
#endregion ADL_Main_Memory_Free
#region ADL_Main_Control_Create
/// <summary> ADL_Main_Control_Create Delegates</summary>
internal static ADL_Main_Control_Create ADL_Main_Control_Create
{
get
{
if (!ADL_Main_Control_Create_Check && null == ADL_Main_Control_Create_)
{
ADL_Main_Control_Create_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Main_Control_Create"))
{
ADL_Main_Control_Create_ = ADLImport.ADL_Main_Control_Create;
}
}
return ADL_Main_Control_Create_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Main_Control_Create ADL_Main_Control_Create_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Main_Control_Create_Check = false;
#endregion ADL_Main_Control_Create
#region ADL_Main_Control_Destroy
/// <summary> ADL_Main_Control_Destroy Delegates</summary>
internal static ADL_Main_Control_Destroy ADL_Main_Control_Destroy
{
get
{
if (!ADL_Main_Control_Destroy_Check && null == ADL_Main_Control_Destroy_)
{
ADL_Main_Control_Destroy_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Main_Control_Destroy"))
{
ADL_Main_Control_Destroy_ = ADLImport.ADL_Main_Control_Destroy;
}
}
return ADL_Main_Control_Destroy_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Main_Control_Destroy ADL_Main_Control_Destroy_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Main_Control_Destroy_Check = false;
#endregion ADL_Main_Control_Destroy
#region ADL_Adapter_NumberOfAdapters_Get
/// <summary> ADL_Adapter_NumberOfAdapters_Get Delegates</summary>
internal static ADL_Adapter_NumberOfAdapters_Get ADL_Adapter_NumberOfAdapters_Get
{
get
{
if (!ADL_Adapter_NumberOfAdapters_Get_Check && null == ADL_Adapter_NumberOfAdapters_Get_)
{
ADL_Adapter_NumberOfAdapters_Get_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Adapter_NumberOfAdapters_Get"))
{
ADL_Adapter_NumberOfAdapters_Get_ = ADLImport.ADL_Adapter_NumberOfAdapters_Get;
}
}
return ADL_Adapter_NumberOfAdapters_Get_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Adapter_NumberOfAdapters_Get ADL_Adapter_NumberOfAdapters_Get_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Adapter_NumberOfAdapters_Get_Check = false;
#endregion ADL_Adapter_NumberOfAdapters_Get
#region ADL_Adapter_AdapterInfo_Get
/// <summary> ADL_Adapter_AdapterInfo_Get Delegates</summary>
internal static ADL_Adapter_AdapterInfo_Get ADL_Adapter_AdapterInfo_Get
{
get
{
if (!ADL_Adapter_AdapterInfo_Get_Check && null == ADL_Adapter_AdapterInfo_Get_)
{
ADL_Adapter_AdapterInfo_Get_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Adapter_AdapterInfo_Get"))
{
ADL_Adapter_AdapterInfo_Get_ = ADLImport.ADL_Adapter_AdapterInfo_Get;
}
}
return ADL_Adapter_AdapterInfo_Get_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Adapter_AdapterInfo_Get ADL_Adapter_AdapterInfo_Get_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Adapter_AdapterInfo_Get_Check = false;
#endregion ADL_Adapter_AdapterInfo_Get
#region ADL_Adapter_Active_Get
/// <summary> ADL_Adapter_Active_Get Delegates</summary>
internal static ADL_Adapter_Active_Get ADL_Adapter_Active_Get
{
get
{
if (!ADL_Adapter_Active_Get_Check && null == ADL_Adapter_Active_Get_)
{
ADL_Adapter_Active_Get_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Adapter_Active_Get"))
{
ADL_Adapter_Active_Get_ = ADLImport.ADL_Adapter_Active_Get;
}
}
return ADL_Adapter_Active_Get_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Adapter_Active_Get ADL_Adapter_Active_Get_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Adapter_Active_Get_Check = false;
#endregion ADL_Adapter_Active_Get
#region ADL_Display_DisplayInfo_Get
/// <summary> ADL_Display_DisplayInfo_Get Delegates</summary>
internal static ADL_Display_DisplayInfo_Get ADL_Display_DisplayInfo_Get
{
get
{
if (!ADL_Display_DisplayInfo_Get_Check && null == ADL_Display_DisplayInfo_Get_)
{
ADL_Display_DisplayInfo_Get_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Display_DisplayInfo_Get"))
{
ADL_Display_DisplayInfo_Get_ = ADLImport.ADL_Display_DisplayInfo_Get;
}
}
return ADL_Display_DisplayInfo_Get_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Display_DisplayInfo_Get ADL_Display_DisplayInfo_Get_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Display_DisplayInfo_Get_Check = false;
#endregion ADL_Display_DisplayInfo_Get
#region ADL_Display_Size_Get
/// <summary> ADL_Display_Size_Get Delegates</summary>
internal static ADL_Display_Size_Get ADL_Display_Size_Get
{
get
{
if (!ADL_Display_Size_Get_Check && null == ADL_Display_Size_Get)
{
ADL_Display_Size_Get_Check = true;
if (ADLCheckLibrary.IsFunctionValid("ADL_Display_Size_Get"))
{
ADL_Display_Size_Get_ = ADLImport.ADL_Display_Size_Get;
}
}
return ADL_Display_Size_Get_;
}
}
/// <summary> Private Delegate</summary>
private static ADL_Display_Size_Get ADL_Display_Size_Get_ = null;
/// <summary> check flag to indicate the delegate has been checked</summary>
private static bool ADL_Display_Size_Get_Check = false;
#endregion ADL_Display_Size_Get
#endregion Export Functions
}
#endregion ADL Class
}
#endregion ATI.ADL
-170
View File
@@ -1,170 +0,0 @@
using ATI.ADL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace ColorControl
{
class ADLWrapper
{
public static bool Initialize()
{
int ADLRet = -1;
if (null != ADL.ADL_Main_Control_Create)
{
// Second parameter is 1: Get only the present adapters
ADLRet = ADL.ADL_Main_Control_Create(ADL.ADL_Main_Memory_Alloc, 1);
}
return (ADL.ADL_SUCCESS == ADLRet);
}
public static void Uninitialze()
{
if (null != ADL.ADL_Main_Control_Destroy)
{
ADL.ADL_Main_Control_Destroy();
}
}
public static ADLAdapterInfoArray? GetAdapters()
{
int ADLRet = -1;
int NumberOfAdapters = 0;
if (null != ADL.ADL_Adapter_NumberOfAdapters_Get)
{
ADL.ADL_Adapter_NumberOfAdapters_Get(ref NumberOfAdapters);
}
Console.WriteLine("Number Of Adapters: " + NumberOfAdapters.ToString() + "\n");
if (NumberOfAdapters > 0)
{
// Get OS adpater info from ADL
ADLAdapterInfoArray OSAdapterInfoData;
OSAdapterInfoData = new ADLAdapterInfoArray();
if (null != ADL.ADL_Adapter_AdapterInfo_Get)
{
IntPtr AdapterBuffer = IntPtr.Zero;
int size = Marshal.SizeOf(OSAdapterInfoData);
AdapterBuffer = Marshal.AllocCoTaskMem((int)size);
Marshal.StructureToPtr(OSAdapterInfoData, AdapterBuffer, false);
if (null != ADL.ADL_Adapter_AdapterInfo_Get)
{
ADLRet = ADL.ADL_Adapter_AdapterInfo_Get(AdapterBuffer, size);
if (ADL.ADL_SUCCESS == ADLRet)
{
OSAdapterInfoData = (ADLAdapterInfoArray)Marshal.PtrToStructure(AdapterBuffer, OSAdapterInfoData.GetType());
int IsActive = 0;
for (int i = 0; i < NumberOfAdapters; i++)
{
// Check if the adapter is active
if (null != ADL.ADL_Adapter_Active_Get)
ADLRet = ADL.ADL_Adapter_Active_Get(OSAdapterInfoData.ADLAdapterInfo[i].AdapterIndex, ref IsActive);
if (ADL.ADL_SUCCESS == ADLRet)
{
}
}
}
else
{
Console.WriteLine("ADL_Adapter_AdapterInfo_Get() returned error code " + ADLRet.ToString());
}
}
// Release the memory for the AdapterInfo structure
if (IntPtr.Zero != AdapterBuffer)
{
Marshal.FreeCoTaskMem(AdapterBuffer);
}
}
return OSAdapterInfoData;
}
return null;
}
public static List<ADLDisplayInfo> GetAllDisplays()
{
int ADLRet;
int NumberOfDisplays = 0;
var DisplayInfoData = new List<ADLDisplayInfo>();
var OSAdapterInfoData = GetAdapters();
if (OSAdapterInfoData == null || !OSAdapterInfoData.Value.ADLAdapterInfo.Any())
{
return DisplayInfoData;
}
var primaryAdapter = OSAdapterInfoData.Value.ADLAdapterInfo.First();
ADLDisplayInfo oneDisplayInfo = new ADLDisplayInfo();
if (null != ADL.ADL_Display_DisplayInfo_Get)
{
IntPtr DisplayBuffer = IntPtr.Zero;
int j = 0;
// Force the display detection and get the Display Info. Use 0 as last parameter to NOT force detection
ADLRet = ADL.ADL_Display_DisplayInfo_Get(primaryAdapter.AdapterIndex, ref NumberOfDisplays, out DisplayBuffer, 1);
if (ADL.ADL_SUCCESS == ADLRet)
{
for (j = 0; j < NumberOfDisplays; j++)
{
oneDisplayInfo = (ADLDisplayInfo)Marshal.PtrToStructure(new IntPtr(DisplayBuffer.ToInt32() + j * Marshal.SizeOf(oneDisplayInfo)), oneDisplayInfo.GetType());
DisplayInfoData.Add(oneDisplayInfo);
}
Console.WriteLine("\nTotal Number of Displays supported: " + NumberOfDisplays.ToString());
Console.WriteLine("\nDispID AdpID Type OutType CnctType Connected Mapped InfoValue DisplayName ");
for (j = 0; j < NumberOfDisplays; j++)
{
int InfoValue = DisplayInfoData[j].DisplayInfoValue;
string StrConnected = (1 == (InfoValue & 1)) ? "Yes" : "No ";
string StrMapped = (2 == (InfoValue & 2)) ? "Yes" : "No ";
int AdpID = DisplayInfoData[j].DisplayID.DisplayLogicalAdapterIndex;
string StrAdpID = (AdpID < 0) ? "--" : AdpID.ToString("d2");
Console.WriteLine(DisplayInfoData[j].DisplayID.DisplayLogicalIndex.ToString() + " " +
StrAdpID + " " +
DisplayInfoData[j].DisplayType.ToString() + " " +
DisplayInfoData[j].DisplayOutputType.ToString() + " " +
DisplayInfoData[j].DisplayConnector.ToString() + " " +
StrConnected + " " +
StrMapped + " " +
InfoValue.ToString("x4") + " " +
DisplayInfoData[j].DisplayName.ToString());
}
Console.WriteLine();
}
else
{
Console.WriteLine("ADL_Display_DisplayInfo_Get() returned error code " + ADLRet.ToString());
}
// Release the memory for the DisplayInfo structure
if (IntPtr.Zero != DisplayBuffer)
Marshal.FreeCoTaskMem(DisplayBuffer);
}
return DisplayInfoData;
}
public static bool GetDisplayResolution(string displayName, ref int horizontal, ref int vertical)
{
var displays = GetAllDisplays();
var display = displays.FirstOrDefault(d => d.DisplayName.Equals(displayName, StringComparison.OrdinalIgnoreCase));
if (display.DisplayID.DisplayPhysicalIndex >= 0 && ADL.ADL_Display_Size_Get != null)
{
int lpDefaultWidth, lpDefaultHeight, lpMinWidth, lpMinHeight, lpMaxWidth, lpMaxHeight, lpStepWidth, lpStepHeight;
lpDefaultWidth = lpDefaultHeight = lpMinWidth = lpMinHeight = lpMaxWidth = lpMaxHeight = lpStepWidth = lpStepHeight = 0;
var ADLRet = ADL.ADL_Display_Size_Get(display.DisplayID.DisplayPhysicalAdapterIndex, display.DisplayID.DisplayPhysicalIndex, ref horizontal, ref vertical, ref lpDefaultWidth, ref lpDefaultHeight, ref lpMinWidth, ref lpMinHeight, ref lpMaxWidth, ref lpMaxHeight, ref lpStepWidth, ref lpStepHeight);
return ADLRet == ADL.ADL_SUCCESS;
}
return false;
}
}
}
-186
View File
@@ -1,186 +0,0 @@
using NvAPIWrapper.Display;
using NvAPIWrapper.Native.Display;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorControl
{
class AmdPreset : PresetBase
{
public bool applyColorData { get; set; }
public ColorData colorData { get; set; }
public bool applyHDR { get; set; }
public bool HDREnabled { get; set; }
public bool toggleHDR { get; set; }
public bool applyDithering { get; set; }
public bool ditheringEnabled { get; set; }
public bool applyRefreshRate { get; set; }
public uint refreshRate { get; set; }
public bool primaryDisplay { get; set; }
public string displayName { get; set; }
public AmdPreset()
{
applyColorData = true;
applyDithering = false;
ditheringEnabled = true;
applyHDR = false;
HDREnabled = false;
toggleHDR = false;
applyRefreshRate = false;
refreshRate = 60;
primaryDisplay = true;
}
public AmdPreset(ColorData colorData) : this()
{
id = GetNewId();
this.colorData = colorData;
}
public AmdPreset(AmdPreset preset): this()
{
id = GetNewId();
primaryDisplay = preset.primaryDisplay;
displayName = preset.displayName;
var colorData = preset.colorData;
this.colorData = new ColorData(colorData.ColorFormat, dynamicRange: colorData.DynamicRange, colorDepth: colorData.ColorDepth, colorSelectionPolicy: colorData.SelectionPolicy);
applyColorData = preset.applyColorData;
applyHDR = preset.applyHDR;
HDREnabled = preset.HDREnabled;
toggleHDR = preset.toggleHDR;
applyDithering = preset.applyDithering;
ditheringEnabled = preset.ditheringEnabled;
applyRefreshRate = preset.applyRefreshRate;
refreshRate = preset.refreshRate;
}
public AmdPreset Clone()
{
var preset = new AmdPreset(this);
return preset;
}
public static string[] GetColumnNames()
{
//return new[] { "BPC", "Format", "Dynamic range", "Toggle HDR", "Shortcut" };
return new[] { "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Refresh rate|100", "Dithering", "HDR", "Shortcut" };
}
public List<string> GetDisplayValues()
{
var values = new List<string>();
var display = string.Format("{0}", primaryDisplay ? "Primary" : displayName);
values.Add(display);
var colorSettings = string.Format("{0}: {1}, {2}, {3}, {4}", applyColorData ? "Included" : "Excluded", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange, colorData.Colorimetry);
values.Add(colorSettings);
values.Add(string.Format("{0}: {1}Hz", applyRefreshRate ? "Included" : "Excluded", refreshRate));
values.Add(string.Format("{0}: {1}", applyDithering ? "Included" : "Excluded", ditheringEnabled ? "Enabled" : "Disabled"));
values.Add(string.Format("{0}: {1}", applyHDR ? "Included" : "Excluded", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled"));
//values.Add(colorData.ColorDepth.ToString());
//values.Add(colorData.ColorFormat.ToString());
//values.Add(colorData.DynamicRange.ToString());
//values.Add(toggleHDR.ToString());
values.Add(shortcut);
return values;
}
public static List<AmdPreset> GetDefaultPresets()
{
var presets = new List<AmdPreset>();
presets.Add(new AmdPreset(new ColorData(ColorDataFormat.Auto, dynamicRange: ColorDataDynamicRange.Auto, colorDepth: ColorDataDepth.Default, colorSelectionPolicy: ColorDataSelectionPolicy.BestQuality)));
for (var dynamicRange = ColorDataDynamicRange.VESA; dynamicRange <= ColorDataDynamicRange.CEA; dynamicRange++)
{
presets.Add(new AmdPreset(new ColorData(ColorDataFormat.RGB, dynamicRange: dynamicRange, colorDepth: ColorDataDepth.BPC8, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
presets.Add(new AmdPreset(new ColorData(ColorDataFormat.RGB, dynamicRange: dynamicRange, colorDepth: ColorDataDepth.BPC10, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
presets.Add(new AmdPreset(new ColorData(ColorDataFormat.RGB, dynamicRange: dynamicRange, colorDepth: ColorDataDepth.BPC12, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
}
for (var format = ColorDataFormat.YUV422; format <= ColorDataFormat.YUV420; format++)
{
presets.Add(new AmdPreset(new ColorData(format, dynamicRange: ColorDataDynamicRange.Auto, colorDepth: ColorDataDepth.BPC8, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
presets.Add(new AmdPreset(new ColorData(format, dynamicRange: ColorDataDynamicRange.Auto, colorDepth: ColorDataDepth.BPC10, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
presets.Add(new AmdPreset(new ColorData(format, dynamicRange: ColorDataDynamicRange.Auto, colorDepth: ColorDataDepth.BPC12, colorSelectionPolicy: ColorDataSelectionPolicy.User)));
}
return presets;
}
public string GetTextForMenuItem()
{
var sb = new StringBuilder();
if (displayName != null)
{
sb.AppendFormat("Display: {0} / ", displayName);
}
if (applyColorData)
{
var colorSettings = string.Format("Format: {0}, {1}, {2}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange);
sb.Append(colorSettings);
sb.Append(" / ");
}
if (applyRefreshRate)
{
sb.AppendFormat("{0}Hz", refreshRate);
sb.Append(" / ");
}
if (applyDithering)
{
sb.AppendFormat("Dithering: {0}", ditheringEnabled ? "Yes" : "No");
sb.Append(" / ");
}
if (applyHDR)
{
sb.AppendFormat("HDR: {0}", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled");
sb.Append(" / ");
}
return sb.ToString(0, Math.Max(0, sb.Length - 3));
}
public static ColorData GenerateColorData(IDictionary<string, object> dictionary)
{
var format = ColorDataFormat.RGB;
var dynamicRange = ColorDataDynamicRange.VESA;
var colorDepth = ColorDataDepth.BPC8;
var colorimetry = ColorDataColorimetry.Auto;
var selectionPolicy = ColorDataSelectionPolicy.User;
object value;
if (dictionary.TryGetValue("ColorFormat", out value))
{
format = (ColorDataFormat)Enum.ToObject(typeof(ColorDataFormat), value);
}
if (dictionary.TryGetValue("ColorDepth", out value))
{
colorDepth = (ColorDataDepth)Enum.ToObject(typeof(ColorDataDepth), value);
}
if (dictionary.TryGetValue("Colorimetry", out value))
{
colorimetry = (ColorDataColorimetry)Enum.ToObject(typeof(ColorDataColorimetry), value);
}
if (dictionary.TryGetValue("DynamicRange", out value))
{
dynamicRange = (ColorDataDynamicRange)Enum.ToObject(typeof(ColorDataDynamicRange), value);
}
if (dictionary.TryGetValue("SelectionPolicy", out value))
{
selectionPolicy = (ColorDataSelectionPolicy)Enum.ToObject(typeof(ColorDataSelectionPolicy), value);
}
return new ColorData(format, dynamicRange: dynamicRange, colorimetry: colorimetry, colorDepth: colorDepth, colorSelectionPolicy: selectionPolicy);
}
}
}
-132
View File
@@ -1,132 +0,0 @@
//using ATI.ADL;
using System;
using System.Collections.Generic;
namespace ColorControl
{
class AmdService : GraphicsService
{
private string CurrentDisplay;
public AmdService()
{
}
public void SetCurrentDisplay(AmdPreset preset)
{
CurrentDisplay = preset.displayName;
}
public string GetCurrentDisplay()
{
return CurrentDisplay;
}
public bool ApplyPreset(AmdPreset preset, Config config)
{
SetCurrentDisplay(preset);
if (preset.applyHDR)
{
if (preset.toggleHDR || preset.HDREnabled != IsHDREnabled())
{
ToggleHDR(config.DisplaySettingsDelay);
}
}
if (preset.applyRefreshRate)
{
SetRefreshRate(preset.refreshRate);
}
if (preset.applyColorData)
{
var display = GetCurrentDisplay();
// TODO: ADL call to set color format, depth, space
// ADL_Display_PixelFormat_Set
// ADL_Display_ColorDepth_Set
//var data = new ColorData(ColorDataFormat.YUV444, ColorDataColorimetry.YCC709, ColorDataDynamicRange.Auto, ColorDataDepth.BPC8, preset.colorData.SelectionPolicy, ColorDataDesktopDepth.Default);
//display.DisplayDevice.SetColorData(preset.colorData);
}
if (preset.applyDithering)
{
SetDithering(preset.ditheringEnabled);
}
return true;
}
public void SetDithering(bool enabled)
{
// TODO: ADL call to set dithering state
// ADL_Display_DitherState_Get
}
public bool GetDithering()
{
// TODO: ADL call to get dithering state
// ADL_Display_DitherState_Set
return false;
}
public bool SetRefreshRate(uint refreshRate)
{
var portrait = false;
var horizontal = 3840;
var vertical = 2160;
var display = GetCurrentDisplay();
//ADLWrapper.GetDisplayResolution(display, ref horizontal, ref vertical);
return SetRefreshRateInternal(display, refreshRate, portrait, horizontal, vertical);
}
public List<uint> GetAvailableRefreshRates(AmdPreset preset = null)
{
if (preset != null)
{
SetCurrentDisplay(preset);
}
var portrait = false;
var horizontal = 3840;
var vertical = 2160;
var display = GetCurrentDisplay();
//ADLWrapper.GetDisplayResolution(display, ref horizontal, ref vertical);
return GetAvailableRefreshRatesInternal(display, portrait, horizontal, vertical);
}
public bool IsHDREnabled()
{
// TODO: ADL call to get HDR state
//var displayDevice = GetCurrentDisplay().DisplayDevice;
//var hdr = displayDevice.HDRColorData;
//return hdr?.HDRMode == ColorDataHDRMode.UHDA;
return false;
}
public List<string> GetDisplays()
{
//return ADLWrapper.GetAllDisplays();
return null;
}
protected override void Initialize()
{
//if (!ADLWrapper.Initialize())
//{
// throw new InvalidOperationException("Cannot load AMD API");
//}
throw new InvalidOperationException("Cannot load AMD API");
}
protected override void Uninitialize()
{
//ADLWrapper.Uninitialze();
}
}
}
+317
View File
@@ -0,0 +1,317 @@
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Dependency Analysis",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA103",
"name": "ReferenceToBeDeleted",
"fullDescription": {
"text": "References that need to be deleted in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
},
{
"id": "UA106",
"name": "PackageToBeAdded",
"fullDescription": {
"text": "Packages that need to be added in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
},
{
"id": "UA108",
"name": "FrameworkReferenceToBeAdded",
"fullDescription": {
"text": "Framework Reference that need to be added in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
]
}
},
"results": [
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Configuration needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.ServiceModel needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Web needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Web.Extensions needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.Configuration.ConfigurationManager, Version=5.0.0 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Primitives, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Http, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Duplex, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.NetTcp, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Security, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Federation, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers, Version=0.3.261602 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.Windows.Compatibility, Version=6.0.0 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA108",
"message": {
"text": "Framework Reference to Microsoft.AspNetCore.App needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "API Upgradability",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
},
"results": [],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "Component Analysis",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA202",
"name": "Microsoft.DotNet.UpgradeAssistant.Extensions.Windows.WinformsDpiSettingUpdater",
"fullDescription": {
"text": "Winforms Source Updater"
},
"helpUri": "about:blank"
}
]
}
},
"results": [
{
"ruleId": "UA202",
"message": {
"text": "HighDpiMode needs to set in Main() instead of app.config or app.manifest - Application.SetHighDpiMode(HighDpiMode.<setting>). It is recommended to use SystemAware as the HighDpiMode option for better results."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/Program.cs"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
}
]
}
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
+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;
}
}
+139 -275
View File
@@ -1,276 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A6005D08-70F0-44CB-A384-9219EBA84F21}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>ColorControl</RootNamespace>
<AssemblyName>ColorControl</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<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>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.3.4.0</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>482C987E8C69872608C9D44751E004C5784E8540</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>ColorControl_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<TargetZone>LocalIntranet</TargetZone>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<SignManifests>true</SignManifests>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>pngbarn.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Win32.TaskScheduler, Version=2.8.20.0, Culture=neutral, PublicKeyToken=c416bc1b32d97233, processorArchitecture=MSIL">
<HintPath>..\packages\TaskScheduler.2.8.20\lib\net452\Microsoft.Win32.TaskScheduler.dll</HintPath>
</Reference>
<Reference Include="Native, Version=0.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Native.0.1.0\lib\net35\Native.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.7.2\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="NStandard, Version=0.4.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NStandard.0.4.3\lib\net46\NStandard.dll</HintPath>
</Reference>
<Reference Include="NvAPIWrapper, Version=0.8.0.98, Culture=neutral, PublicKeyToken=310fd07b25df79b3, processorArchitecture=MSIL">
<HintPath>..\packages\NvAPIWrapper.Net.0.8.0.98\lib\net45\NvAPIWrapper.dll</HintPath>
</Reference>
<Reference Include="NWin32, Version=1.0.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NWin32.1.0.6\lib\net46\NWin32.dll</HintPath>
</Reference>
<Reference Include="PacketDotNet, Version=1.0.5.0, Culture=neutral, PublicKeyToken=451414c7667b2a58, processorArchitecture=MSIL">
<HintPath>..\packages\PacketDotNet.1.0.5\lib\net45\PacketDotNet.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="SharpPcap, Version=5.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SharpPcap.5.2.0\lib\netstandard2.0\SharpPcap.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</HintPath>
</Reference>
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="Windows">
<HintPath>..\..\..\..\..\..\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.18362.0\Windows.winmd</HintPath>
</Reference>
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="GraphicsService.cs" />
<Compile Include="AmdService.cs" />
<Compile Include="AmdPreset.cs" />
<Compile Include="StartUpParams.cs" />
<Compile Include="LgServiceConfig.cs" />
<Compile Include="WOL.cs" />
<Compile Include="RestartDetector.cs" />
<Compile Include="ExceptionExtensions.cs" />
<Compile Include="lgtv\App.cs" />
<Compile Include="lgtv\Channel.cs" />
<Compile Include="lgtv\ClientKeyStore.cs" />
<Compile Include="lgtv\ExternalInput.cs" />
<Compile Include="lgtv\IsConnectedDelegate.cs" />
<Compile Include="lgtv\LgTvApi.cs" />
<Compile Include="lgtv\LgTvConnection.cs" />
<Compile Include="lgtv\LgWebOsMouseService.cs" />
<Compile Include="lgtv\MessageType.cs" />
<Compile Include="lgtv\RawRequestMessage.cs" />
<Compile Include="lgtv\RequestMessage.cs" />
<Compile Include="PnpDev.cs" />
<Compile Include="TreeNodeBuilder.cs" />
<Compile Include="ColorDataConverter.cs" />
<Compile Include="Config.cs" />
<Compile Include="lgtv\LgApp.cs" />
<Compile Include="PresetBase.cs" />
<Compile Include="LgPreset.cs" />
<Compile Include="NvPreset.cs" />
<Compile Include="LgService.cs" />
<Compile Include="NvService.cs" />
<Compile Include="ScreenInterrogatory.cs" />
<Compile Include="Utils.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="ColorControl_TemporaryKey.pfx" />
<Content Include="LgPresets.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="lgtv\README.md" />
<None Include="packages.config" />
<None Include="Properties\app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="pngbarn.ico" />
</ItemGroup>
<ItemGroup>
<Folder Include="bin\x64\Debug\" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<OutputType>WinExe</OutputType>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<MapFileExtensions>false</MapFileExtensions>
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<Company>Maassoft</Company>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>11.0.0.0</ApplicationVersion>
<Version>11.0.0.0</Version>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>false</BootstrapperEnabled>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>false</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<ApplicationIcon>Resources\pngbarn.ico</ApplicationIcon>
<Copyright>2020-2025 Maassoft</Copyright>
<Authors>Maassoft</Authors>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<OutputPath>bin\x64\Debug\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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" />
</ItemGroup>
<ItemGroup>
<Compile Update="Forms\OptionsPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Update="Forms\InfoPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
<Generator></Generator>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Update="Services\Common\QuickAccessForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="services\lg\RemoteControlPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Update="Services\NVIDIA\NvInfoPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Update="Services\NVIDIA\NvPanel.cs" />
<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>
</ItemGroup>
<ItemGroup>
<Folder Include="bin\x64\Debug\" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<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">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="XForms\BrowserWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="XForms\LogWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ColorControl.UI\ColorControl.UI.csproj" />
<ProjectReference Include="..\MHC2Gen\MHC2Gen.csproj" />
<ProjectReference Include="..\NLogViewer\NLogViewer.csproj" />
<ProjectReference Include="..\novideo_srgb\novideo_srgb.csproj" />
<ProjectReference Include="..\Nspector\Nspector.csproj" />
<ProjectReference Include="..\NvAPIWrapper\NvAPIWrapper.csproj" />
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Services\Samsung\SamsungPanel.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>
-54
View File
@@ -1,54 +0,0 @@
using NvAPIWrapper.Display;
using NvAPIWrapper.Native.Display;
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
namespace ColorControl
{
public class ColorDataConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new List<Type>() { typeof(ColorData) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (obj == null) return result;
return result;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var format = ColorDataFormat.RGB;
var dynamicRange = ColorDataDynamicRange.VESA;
var colorDepth = ColorDataDepth.BPC8;
var colorimetry = ColorDataColorimetry.Auto;
var selectionPolicy = ColorDataSelectionPolicy.User;
object value;
if (dictionary.TryGetValue("ColorFormat", out value))
{
format = (ColorDataFormat)Enum.ToObject(typeof(ColorDataFormat), value);
}
if (dictionary.TryGetValue("ColorDepth", out value))
{
colorDepth = (ColorDataDepth)Enum.ToObject(typeof(ColorDataDepth), value);
}
if (dictionary.TryGetValue("Colorimetry", out value))
{
colorimetry = (ColorDataColorimetry)Enum.ToObject(typeof(ColorDataColorimetry), value);
}
if (dictionary.TryGetValue("DynamicRange", out value))
{
dynamicRange = (ColorDataDynamicRange)Enum.ToObject(typeof(ColorDataDynamicRange), value);
}
if (dictionary.TryGetValue("SelectionPolicy", out value))
{
selectionPolicy = (ColorDataSelectionPolicy)Enum.ToObject(typeof(ColorDataSelectionPolicy), value);
}
return new ColorData(format, dynamicRange: dynamicRange, colorimetry: colorimetry, colorDepth: colorDepth, colorSelectionPolicy: selectionPolicy);
}
}
}
+130
View File
@@ -0,0 +1,130 @@
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;
using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Svc;
using Microsoft.Extensions.DependencyInjection;
namespace ColorControl
{
public static class CommandLineHandler
{
public static async Task<bool> HandleStartupParams(StartUpParams startUpParams, Process existingProcess)
{
if (startUpParams.StartElevated)
{
await StartElevated();
return true;
}
var useConsole = startUpParams.NoGui || existingProcess?.SessionId > 0;
if (!useConsole)
{
return false;
}
var result = false;
if (startUpParams.ExecuteHelp)
{
PrepareConsole(startUpParams);
Console.WriteLine();
Console.WriteLine($@"ColorControl CLI {Application.ProductVersion}
Syntax : ColorControl command options
Commands:
--nvpreset <preset name or id>: execute NVIDIA-preset
--amdpreset <preset name or id>: execute AMD-preset
--lgpreset <preset name> : execute LG-preset
--sampreset <preset name> : execute Samsung-preset
--help : displays this help info
Options:
--nogui : starts command from the command line and will not open GUI (is forced when GUI is already running)
--no-refresh: when using LG or Samsung-preset: skip refreshing devices (speeds up executing preset)");
result = true;
}
if (startUpParams.ExecuteLgPreset)
{
PrepareConsole(startUpParams);
Console.WriteLine($"Executing LG-preset '{startUpParams.LgPresetName}'...");
await LgService.ExecutePresetAsync(startUpParams.LgPresetName);
Console.WriteLine("Done.");
result = true;
}
if (startUpParams.ExecuteSamsungPreset)
{
PrepareConsole(startUpParams);
Console.WriteLine($"Executing Samsung-preset '{startUpParams.SamsungPresetName}'...");
await SamsungService.ExecutePresetAsync(startUpParams.SamsungPresetName);
Console.WriteLine("Done.");
result = true;
}
if (startUpParams.ExecuteNvidiaPreset)
{
PrepareConsole(startUpParams);
Console.WriteLine($"Executing NVIDIA-preset '{startUpParams.NvidiaPresetIdOrName}'...");
await NvService.ExecutePresetAsync(startUpParams.NvidiaPresetIdOrName);
Console.WriteLine("Done.");
result = true;
}
if (startUpParams.ExecuteAmdPreset)
{
PrepareConsole(startUpParams);
Console.WriteLine($"Executing AMD-preset '{startUpParams.AmdPresetIdOrName}'...");
await AmdService.ExecutePresetAsync(startUpParams.AmdPresetIdOrName);
Console.WriteLine("Done.");
result = true;
}
return result;
}
private static void PrepareConsole(StartUpParams startUpParams)
{
Utils.OpenConsole();
startUpParams.NoGui = true;
}
private static async Task StartElevated()
{
try
{
var backgroundService = Program.ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.ElevatedPipe;
await backgroundService.StartAndStopWithMutex();
Application.Run();
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing elevated application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
-24
View File
@@ -1,24 +0,0 @@
namespace ColorControl
{
class Config
{
public bool StartMinimized { get; set; }
public bool MinimizeOnClose { get; set; }
public int DisplaySettingsDelay { get; set; }
public string ScreenSaverShortcut { get; set; }
public int FormWidth { get; set; }
public int FormHeight { get; set; }
public Config()
{
DisplaySettingsDelay = 1000;
ScreenSaverShortcut = string.Empty;
FormWidth = 900;
FormHeight = 600;
}
}
}
+119
View File
@@ -0,0 +1,119 @@
namespace ColorControl.Forms
{
partial class InfoPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
wvInfo = new Microsoft.Web.WebView2.WinForms.WebView2();
grpInfo = new System.Windows.Forms.GroupBox();
lbPlugins = new System.Windows.Forms.ListBox();
label7 = new System.Windows.Forms.Label();
lblInfo = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)wvInfo).BeginInit();
grpInfo.SuspendLayout();
SuspendLayout();
//
// wvInfo
//
wvInfo.AllowExternalDrop = true;
wvInfo.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
wvInfo.CreationProperties = null;
wvInfo.DefaultBackgroundColor = System.Drawing.Color.White;
wvInfo.Location = new System.Drawing.Point(433, 3);
wvInfo.Name = "wvInfo";
wvInfo.Size = new System.Drawing.Size(671, 526);
wvInfo.Source = new System.Uri("https://github.com/Maassoft/ColorControl/releases", System.UriKind.Absolute);
wvInfo.TabIndex = 8;
wvInfo.ZoomFactor = 1D;
//
// grpInfo
//
grpInfo.Controls.Add(lbPlugins);
grpInfo.Controls.Add(label7);
grpInfo.Controls.Add(lblInfo);
grpInfo.Location = new System.Drawing.Point(4, 3);
grpInfo.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpInfo.Name = "grpInfo";
grpInfo.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpInfo.Size = new System.Drawing.Size(422, 336);
grpInfo.TabIndex = 7;
grpInfo.TabStop = false;
grpInfo.Text = "Info";
//
// lbPlugins
//
lbPlugins.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
lbPlugins.FormattingEnabled = true;
lbPlugins.HorizontalScrollbar = true;
lbPlugins.ItemHeight = 15;
lbPlugins.Location = new System.Drawing.Point(11, 77);
lbPlugins.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
lbPlugins.Name = "lbPlugins";
lbPlugins.Size = new System.Drawing.Size(403, 244);
lbPlugins.TabIndex = 3;
//
// label7
//
label7.AutoSize = true;
label7.Location = new System.Drawing.Point(8, 59);
label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label7.Name = "label7";
label7.Size = new System.Drawing.Size(267, 15);
label7.TabIndex = 2;
label7.Text = "This app contains the following 3rd party plugins:";
//
// lblInfo
//
lblInfo.AutoSize = true;
lblInfo.Location = new System.Drawing.Point(8, 23);
lblInfo.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblInfo.Name = "lblInfo";
lblInfo.Size = new System.Drawing.Size(28, 15);
lblInfo.TabIndex = 1;
lblInfo.Text = "Info";
//
// InfoPanel
//
Controls.Add(wvInfo);
Controls.Add(grpInfo);
Name = "InfoPanel";
Size = new System.Drawing.Size(1110, 534);
((System.ComponentModel.ISupportInitialize)wvInfo).EndInit();
grpInfo.ResumeLayout(false);
grpInfo.PerformLayout();
ResumeLayout(false);
}
#endregion
private Microsoft.Web.WebView2.WinForms.WebView2 wvInfo;
private System.Windows.Forms.GroupBox grpInfo;
private System.Windows.Forms.ListBox lbPlugins;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.Label lblInfo;
}
}
+54
View File
@@ -0,0 +1,54 @@
using ColorControl.Shared.Forms;
using ColorControl.Shared.Services;
using System.Diagnostics;
using System.Windows.Forms;
namespace ColorControl.Forms
{
public partial class InfoPanel : UserControl
{
private readonly WinApiService _winApiService;
public InfoPanel(WinApiService winApiService)
{
_winApiService = winApiService;
InitializeComponent();
if (DarkModeUtils.UseDarkMode)
{
grpInfo.ForeColor = FormUtils.CurrentForeColor;
}
Init();
}
private void Init()
{
var currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
var text = Application.ProductName + " " + Application.ProductVersion;
if (_winApiService.IsAdministrator())
{
text += " (administrator)";
}
lblInfo.Text = text + " - " + currentVersionInfo.LegalCopyright;
lbPlugins.Items.Add("lgtv.net by gr4b4z");
lbPlugins.Items.Add("Newtonsoft.Json by James Newton-King");
lbPlugins.Items.Add("NLog by Jarek Kowalski, Kim Christensen, Julian Verdurmen");
lbPlugins.Items.Add("NvAPIWrapper.Net by Soroush Falahati");
lbPlugins.Items.Add("NWin32 by zmjack");
lbPlugins.Items.Add("TaskScheduler by David Hall");
lbPlugins.Items.Add("NVIDIA Profile Inspector by Orbmu2k");
lbPlugins.Items.Add("NvidiaML wrapper by LibreHardwareMonitor");
lbPlugins.Items.Add("Novideo sRGB by ledoge");
lbPlugins.Items.Add("NLogViewer by dojo90");
lbPlugins.Items.Add("WPFDarkTheme by AngryCarrot789");
lbPlugins.Items.Add("Little-CMS by mm2");
lbPlugins.Items.Add("MHC2 by dantmnf");
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
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
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+537
View File
@@ -0,0 +1,537 @@
using System.DirectoryServices.ActiveDirectory;
using System.Drawing;
using System;
namespace ColorControl.Forms
{
partial class OptionsPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
grpOptionsModules = new System.Windows.Forms.GroupBox();
label1 = new System.Windows.Forms.Label();
chkModules = new System.Windows.Forms.CheckedListBox();
grpMiscellaneousOptions = new System.Windows.Forms.GroupBox();
btnOptionsColorProfiles = new System.Windows.Forms.Button();
btnSetShortcutScreenSaver = new System.Windows.Forms.Button();
label11 = new System.Windows.Forms.Label();
edtBlankScreenSaverShortcut = new System.Windows.Forms.TextBox();
lblFixChromeFontsDescription = new System.Windows.Forms.Label();
chkFixChromeFonts = new System.Windows.Forms.CheckBox();
grpGeneralOptions = new System.Windows.Forms.GroupBox();
btnOptionsLog = new System.Windows.Forms.Button();
btnOptionsAdvanced = new System.Windows.Forms.Button();
chkAutoInstallUpdates = new System.Windows.Forms.CheckBox();
btnStartStopService = new System.Windows.Forms.Button();
btnElevationInfo = new System.Windows.Forms.Button();
rbElevationService = new System.Windows.Forms.RadioButton();
rbElevationProcess = new System.Windows.Forms.RadioButton();
rbElevationAdmin = new System.Windows.Forms.RadioButton();
label8 = new System.Windows.Forms.Label();
rbElevationNone = new System.Windows.Forms.RadioButton();
chkCheckForUpdates = new System.Windows.Forms.CheckBox();
chkStartAfterLogin = new System.Windows.Forms.CheckBox();
chkOptionsUseDarkMode = new System.Windows.Forms.CheckBox();
chkGdiScaling = new System.Windows.Forms.CheckBox();
chkMinimizeToSystemTray = new System.Windows.Forms.CheckBox();
chkMinimizeOnClose = new System.Windows.Forms.CheckBox();
chkStartMinimized = new System.Windows.Forms.CheckBox();
mnuColorProfiles = new System.Windows.Forms.ContextMenuStrip(components);
miCreateHDRColorProfile = new System.Windows.Forms.ToolStripMenuItem();
miCreateSDRColorProfile = new System.Windows.Forms.ToolStripMenuItem();
lblUiType = new System.Windows.Forms.Label();
cbxUiType = new System.Windows.Forms.ComboBox();
grpUi = new System.Windows.Forms.GroupBox();
grpOptionsModules.SuspendLayout();
grpMiscellaneousOptions.SuspendLayout();
grpGeneralOptions.SuspendLayout();
mnuColorProfiles.SuspendLayout();
grpUi.SuspendLayout();
SuspendLayout();
//
// grpOptionsModules
//
grpOptionsModules.Controls.Add(label1);
grpOptionsModules.Controls.Add(chkModules);
grpOptionsModules.Location = new Point(14, 344);
grpOptionsModules.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpOptionsModules.Name = "grpOptionsModules";
grpOptionsModules.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpOptionsModules.Size = new Size(658, 131);
grpOptionsModules.TabIndex = 11;
grpOptionsModules.TabStop = false;
grpOptionsModules.Text = "Modules";
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(7, 19);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new Size(511, 15);
label1.TabIndex = 7;
label1.Text = "Here you can disable modules you do not want. A restart is required for a change to have effect.";
//
// chkModules
//
chkModules.ColumnWidth = 200;
chkModules.FormattingEnabled = true;
chkModules.Location = new Point(7, 40);
chkModules.MultiColumn = true;
chkModules.Name = "chkModules";
chkModules.Size = new Size(507, 76);
chkModules.TabIndex = 0;
chkModules.ItemCheck += chkModules_ItemCheck;
//
// grpMiscellaneousOptions
//
grpMiscellaneousOptions.Controls.Add(btnOptionsColorProfiles);
grpMiscellaneousOptions.Controls.Add(btnSetShortcutScreenSaver);
grpMiscellaneousOptions.Controls.Add(label11);
grpMiscellaneousOptions.Controls.Add(edtBlankScreenSaverShortcut);
grpMiscellaneousOptions.Controls.Add(lblFixChromeFontsDescription);
grpMiscellaneousOptions.Controls.Add(chkFixChromeFonts);
grpMiscellaneousOptions.Location = new Point(14, 481);
grpMiscellaneousOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpMiscellaneousOptions.Name = "grpMiscellaneousOptions";
grpMiscellaneousOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpMiscellaneousOptions.Size = new Size(658, 131);
grpMiscellaneousOptions.TabIndex = 5;
grpMiscellaneousOptions.TabStop = false;
grpMiscellaneousOptions.Text = "Miscellaneous";
//
// btnOptionsColorProfiles
//
btnOptionsColorProfiles.Location = new Point(538, 17);
btnOptionsColorProfiles.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnOptionsColorProfiles.Name = "btnOptionsColorProfiles";
btnOptionsColorProfiles.Size = new Size(112, 27);
btnOptionsColorProfiles.TabIndex = 12;
btnOptionsColorProfiles.Text = "Color Profiles...";
btnOptionsColorProfiles.UseVisualStyleBackColor = true;
btnOptionsColorProfiles.Click += btnOptionsColorProfiles_Click;
//
// btnSetShortcutScreenSaver
//
btnSetShortcutScreenSaver.Location = new Point(181, 98);
btnSetShortcutScreenSaver.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnSetShortcutScreenSaver.Name = "btnSetShortcutScreenSaver";
btnSetShortcutScreenSaver.Size = new Size(40, 27);
btnSetShortcutScreenSaver.TabIndex = 10;
btnSetShortcutScreenSaver.Text = "Set";
btnSetShortcutScreenSaver.UseVisualStyleBackColor = true;
btnSetShortcutScreenSaver.Click += btnSetShortcutScreenSaver_Click;
//
// label11
//
label11.AutoSize = true;
label11.Location = new Point(7, 80);
label11.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label11.Name = "label11";
label11.Size = new Size(238, 15);
label11.TabIndex = 9;
label11.Text = "Set a shortcut to start the blank screensaver:";
//
// edtBlankScreenSaverShortcut
//
edtBlankScreenSaverShortcut.Location = new Point(7, 101);
edtBlankScreenSaverShortcut.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
edtBlankScreenSaverShortcut.Name = "edtBlankScreenSaverShortcut";
edtBlankScreenSaverShortcut.ReadOnly = true;
edtBlankScreenSaverShortcut.Size = new Size(165, 23);
edtBlankScreenSaverShortcut.TabIndex = 7;
edtBlankScreenSaverShortcut.KeyDown += edtShortcut_KeyDown;
edtBlankScreenSaverShortcut.KeyUp += edtShortcut_KeyUp;
//
// lblFixChromeFontsDescription
//
lblFixChromeFontsDescription.AutoSize = true;
lblFixChromeFontsDescription.Location = new Point(4, 45);
lblFixChromeFontsDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblFixChromeFontsDescription.Name = "lblFixChromeFontsDescription";
lblFixChromeFontsDescription.Size = new Size(427, 15);
lblFixChromeFontsDescription.TabIndex = 6;
lblFixChromeFontsDescription.Text = "This will add the parameter --disable-lcd-text to Chrome and requires elevation.";
//
// chkFixChromeFonts
//
chkFixChromeFonts.AutoSize = true;
chkFixChromeFonts.Location = new Point(7, 22);
chkFixChromeFonts.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkFixChromeFonts.Name = "chkFixChromeFonts";
chkFixChromeFonts.Size = new Size(378, 19);
chkFixChromeFonts.TabIndex = 4;
chkFixChromeFonts.Text = "ClearType: fix bad fonts in Chrome (turn on grayscale anti-aliasing)";
chkFixChromeFonts.UseVisualStyleBackColor = true;
chkFixChromeFonts.CheckedChanged += chkFixChromeFonts_CheckedChanged;
//
// grpGeneralOptions
//
grpGeneralOptions.Controls.Add(btnOptionsLog);
grpGeneralOptions.Controls.Add(btnOptionsAdvanced);
grpGeneralOptions.Controls.Add(chkAutoInstallUpdates);
grpGeneralOptions.Controls.Add(btnStartStopService);
grpGeneralOptions.Controls.Add(btnElevationInfo);
grpGeneralOptions.Controls.Add(rbElevationService);
grpGeneralOptions.Controls.Add(rbElevationProcess);
grpGeneralOptions.Controls.Add(rbElevationAdmin);
grpGeneralOptions.Controls.Add(label8);
grpGeneralOptions.Controls.Add(rbElevationNone);
grpGeneralOptions.Controls.Add(chkCheckForUpdates);
grpGeneralOptions.Controls.Add(chkStartAfterLogin);
grpGeneralOptions.Location = new Point(7, 6);
grpGeneralOptions.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpGeneralOptions.Name = "grpGeneralOptions";
grpGeneralOptions.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
grpGeneralOptions.Size = new Size(662, 202);
grpGeneralOptions.TabIndex = 2;
grpGeneralOptions.TabStop = false;
grpGeneralOptions.Text = "General";
//
// btnOptionsLog
//
btnOptionsLog.Location = new Point(405, 47);
btnOptionsLog.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnOptionsLog.Name = "btnOptionsLog";
btnOptionsLog.Size = new Size(88, 27);
btnOptionsLog.TabIndex = 68;
btnOptionsLog.Text = "Logging...";
btnOptionsLog.UseVisualStyleBackColor = true;
btnOptionsLog.Click += btnOptionsLog_Click;
//
// btnOptionsAdvanced
//
btnOptionsAdvanced.Location = new Point(405, 17);
btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnOptionsAdvanced.Name = "btnOptionsAdvanced";
btnOptionsAdvanced.Size = new Size(88, 27);
btnOptionsAdvanced.TabIndex = 11;
btnOptionsAdvanced.Text = "Advanced...";
btnOptionsAdvanced.UseVisualStyleBackColor = true;
btnOptionsAdvanced.Click += btnOptionsAdvanced_Click;
//
// chkAutoInstallUpdates
//
chkAutoInstallUpdates.AutoSize = true;
chkAutoInstallUpdates.Location = new Point(7, 72);
chkAutoInstallUpdates.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkAutoInstallUpdates.Name = "chkAutoInstallUpdates";
chkAutoInstallUpdates.Size = new Size(179, 19);
chkAutoInstallUpdates.TabIndex = 66;
chkAutoInstallUpdates.Text = "Automatically install updates";
chkAutoInstallUpdates.UseVisualStyleBackColor = true;
chkAutoInstallUpdates.CheckedChanged += chkAutoInstallUpdates_CheckedChanged;
//
// btnStartStopService
//
btnStartStopService.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
btnStartStopService.Location = new Point(405, 150);
btnStartStopService.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnStartStopService.Name = "btnStartStopService";
btnStartStopService.Size = new Size(40, 27);
btnStartStopService.TabIndex = 65;
btnStartStopService.Text = "Start";
btnStartStopService.UseVisualStyleBackColor = true;
btnStartStopService.Click += btnStartStopService_Click;
//
// btnElevationInfo
//
btnElevationInfo.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
btnElevationInfo.Location = new Point(120, 88);
btnElevationInfo.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
btnElevationInfo.Name = "btnElevationInfo";
btnElevationInfo.Size = new Size(22, 27);
btnElevationInfo.TabIndex = 64;
btnElevationInfo.Text = "?";
btnElevationInfo.UseVisualStyleBackColor = true;
btnElevationInfo.Click += btnElevationInfo_Click;
//
// rbElevationService
//
rbElevationService.AutoSize = true;
rbElevationService.Location = new Point(7, 154);
rbElevationService.Name = "rbElevationService";
rbElevationService.Size = new Size(136, 19);
rbElevationService.TabIndex = 63;
rbElevationService.TabStop = true;
rbElevationService.Tag = "2";
rbElevationService.Text = "Use Windows Service";
rbElevationService.UseVisualStyleBackColor = true;
rbElevationService.CheckedChanged += rbElevationNone_CheckedChanged;
//
// rbElevationProcess
//
rbElevationProcess.AutoSize = true;
rbElevationProcess.Location = new Point(7, 175);
rbElevationProcess.Name = "rbElevationProcess";
rbElevationProcess.Size = new Size(189, 19);
rbElevationProcess.TabIndex = 62;
rbElevationProcess.TabStop = true;
rbElevationProcess.Tag = "3";
rbElevationProcess.Text = "Use dedicated elevated process";
rbElevationProcess.UseVisualStyleBackColor = true;
rbElevationProcess.CheckedChanged += rbElevationNone_CheckedChanged;
//
// rbElevationAdmin
//
rbElevationAdmin.AutoSize = true;
rbElevationAdmin.Location = new Point(7, 133);
rbElevationAdmin.Name = "rbElevationAdmin";
rbElevationAdmin.Size = new Size(97, 19);
rbElevationAdmin.TabIndex = 61;
rbElevationAdmin.TabStop = true;
rbElevationAdmin.Tag = "1";
rbElevationAdmin.Text = "Run as admin";
rbElevationAdmin.UseVisualStyleBackColor = true;
rbElevationAdmin.CheckedChanged += rbElevationNone_CheckedChanged;
//
// label8
//
label8.AutoSize = true;
label8.Location = new Point(7, 94);
label8.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label8.Name = "label8";
label8.Size = new Size(105, 15);
label8.TabIndex = 60;
label8.Text = "Elevation-method:";
//
// rbElevationNone
//
rbElevationNone.AutoSize = true;
rbElevationNone.Location = new Point(7, 112);
rbElevationNone.Name = "rbElevationNone";
rbElevationNone.Size = new Size(54, 19);
rbElevationNone.TabIndex = 10;
rbElevationNone.TabStop = true;
rbElevationNone.Tag = "0";
rbElevationNone.Text = "None";
rbElevationNone.UseVisualStyleBackColor = true;
rbElevationNone.CheckedChanged += rbElevationNone_CheckedChanged;
//
// chkCheckForUpdates
//
chkCheckForUpdates.AutoSize = true;
chkCheckForUpdates.Location = new Point(7, 47);
chkCheckForUpdates.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkCheckForUpdates.Name = "chkCheckForUpdates";
chkCheckForUpdates.Size = new Size(197, 19);
chkCheckForUpdates.TabIndex = 6;
chkCheckForUpdates.Text = "Automatically check for updates";
chkCheckForUpdates.UseVisualStyleBackColor = true;
chkCheckForUpdates.CheckedChanged += chkCheckForUpdates_CheckedChanged;
//
// chkStartAfterLogin
//
chkStartAfterLogin.AutoSize = true;
chkStartAfterLogin.Location = new Point(7, 22);
chkStartAfterLogin.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkStartAfterLogin.Name = "chkStartAfterLogin";
chkStartAfterLogin.Size = new Size(183, 19);
chkStartAfterLogin.TabIndex = 2;
chkStartAfterLogin.Text = "Automatically start after login";
chkStartAfterLogin.UseVisualStyleBackColor = true;
chkStartAfterLogin.CheckedChanged += chkStartAfterLogin_CheckedChanged;
//
// chkOptionsUseDarkMode
//
chkOptionsUseDarkMode.AutoSize = true;
chkOptionsUseDarkMode.Location = new Point(329, 22);
chkOptionsUseDarkMode.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkOptionsUseDarkMode.Name = "chkOptionsUseDarkMode";
chkOptionsUseDarkMode.Size = new Size(224, 19);
chkOptionsUseDarkMode.TabIndex = 67;
chkOptionsUseDarkMode.Text = "Dark Mode (WinForms: experimental)";
chkOptionsUseDarkMode.UseVisualStyleBackColor = true;
chkOptionsUseDarkMode.CheckedChanged += chkOptionsUseDarkMode_CheckedChanged;
//
// chkGdiScaling
//
chkGdiScaling.AutoSize = true;
chkGdiScaling.Location = new Point(329, 47);
chkGdiScaling.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkGdiScaling.Name = "chkGdiScaling";
chkGdiScaling.Size = new Size(257, 19);
chkGdiScaling.TabIndex = 7;
chkGdiScaling.Text = "WinForms: Use GDI Scaling (requires restart)";
chkGdiScaling.UseVisualStyleBackColor = true;
chkGdiScaling.CheckedChanged += chkGdiScaling_CheckedChanged;
//
// chkMinimizeToSystemTray
//
chkMinimizeToSystemTray.AutoSize = true;
chkMinimizeToSystemTray.Location = new Point(7, 97);
chkMinimizeToSystemTray.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkMinimizeToSystemTray.Name = "chkMinimizeToSystemTray";
chkMinimizeToSystemTray.Size = new Size(152, 19);
chkMinimizeToSystemTray.TabIndex = 5;
chkMinimizeToSystemTray.Text = "Minimize to system tray";
chkMinimizeToSystemTray.UseVisualStyleBackColor = true;
chkMinimizeToSystemTray.CheckedChanged += chkMinimizeToSystemTray_CheckedChanged;
//
// chkMinimizeOnClose
//
chkMinimizeOnClose.AutoSize = true;
chkMinimizeOnClose.Location = new Point(7, 72);
chkMinimizeOnClose.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkMinimizeOnClose.Name = "chkMinimizeOnClose";
chkMinimizeOnClose.Size = new Size(122, 19);
chkMinimizeOnClose.TabIndex = 4;
chkMinimizeOnClose.Text = "Minimize on close";
chkMinimizeOnClose.UseVisualStyleBackColor = true;
chkMinimizeOnClose.CheckedChanged += chkMinimizeOnClose_CheckedChanged;
//
// chkStartMinimized
//
chkStartMinimized.AutoSize = true;
chkStartMinimized.Location = new Point(7, 47);
chkStartMinimized.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
chkStartMinimized.Name = "chkStartMinimized";
chkStartMinimized.Size = new Size(109, 19);
chkStartMinimized.TabIndex = 3;
chkStartMinimized.Text = "Start minimized";
chkStartMinimized.UseVisualStyleBackColor = true;
chkStartMinimized.CheckedChanged += chkStartMinimized_CheckedChanged;
//
// mnuColorProfiles
//
mnuColorProfiles.ImageScalingSize = new Size(20, 20);
mnuColorProfiles.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miCreateHDRColorProfile, miCreateSDRColorProfile });
mnuColorProfiles.Name = "mnuLgButtons";
mnuColorProfiles.Size = new Size(214, 48);
//
// miCreateHDRColorProfile
//
miCreateHDRColorProfile.Name = "miCreateHDRColorProfile";
miCreateHDRColorProfile.Size = new Size(213, 22);
miCreateHDRColorProfile.Text = "Create HDR Color Profile...";
miCreateHDRColorProfile.Click += miCreateHDRColorProfile_Click;
//
// miCreateSDRColorProfile
//
miCreateSDRColorProfile.Name = "miCreateSDRColorProfile";
miCreateSDRColorProfile.Size = new Size(213, 22);
miCreateSDRColorProfile.Text = "Create SDR Color Profile...";
miCreateSDRColorProfile.Click += miCreateSDRColorProfile_Click;
//
// lblUiType
//
lblUiType.AutoSize = true;
lblUiType.Location = new Point(6, 21);
lblUiType.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lblUiType.Name = "lblUiType";
lblUiType.Size = new Size(62, 15);
lblUiType.TabIndex = 70;
lblUiType.Text = "Default UI:";
//
// cbxUiType
//
cbxUiType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
cbxUiType.FormattingEnabled = true;
cbxUiType.Items.AddRange(new object[] { "WinForms", "Web (default browser)", "Web (embedded browser)" });
cbxUiType.Location = new Point(105, 18);
cbxUiType.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
cbxUiType.Name = "cbxUiType";
cbxUiType.Size = new Size(208, 23);
cbxUiType.TabIndex = 69;
cbxUiType.SelectedIndexChanged += cbxUiType_SelectedIndexChanged;
//
// grpUi
//
grpUi.Controls.Add(lblUiType);
grpUi.Controls.Add(chkStartMinimized);
grpUi.Controls.Add(chkOptionsUseDarkMode);
grpUi.Controls.Add(cbxUiType);
grpUi.Controls.Add(chkMinimizeOnClose);
grpUi.Controls.Add(chkMinimizeToSystemTray);
grpUi.Controls.Add(chkGdiScaling);
grpUi.Location = new Point(7, 214);
grpUi.Name = "grpUi";
grpUi.Size = new Size(662, 124);
grpUi.TabIndex = 8;
grpUi.TabStop = false;
grpUi.Text = "UI-settings";
//
// OptionsPanel
//
Controls.Add(grpUi);
Controls.Add(grpOptionsModules);
Controls.Add(grpMiscellaneousOptions);
Controls.Add(grpGeneralOptions);
Name = "OptionsPanel";
Size = new Size(732, 615);
grpOptionsModules.ResumeLayout(false);
grpOptionsModules.PerformLayout();
grpMiscellaneousOptions.ResumeLayout(false);
grpMiscellaneousOptions.PerformLayout();
grpGeneralOptions.ResumeLayout(false);
grpGeneralOptions.PerformLayout();
mnuColorProfiles.ResumeLayout(false);
grpUi.ResumeLayout(false);
grpUi.PerformLayout();
ResumeLayout(false);
}
private System.Windows.Forms.GroupBox grpGeneralOptions;
private System.Windows.Forms.CheckBox chkStartMinimized;
private System.Windows.Forms.CheckBox chkStartAfterLogin;
private System.Windows.Forms.GroupBox grpMiscellaneousOptions;
private System.Windows.Forms.CheckBox chkFixChromeFonts;
private System.Windows.Forms.Label lblFixChromeFontsDescription;
private System.Windows.Forms.Button btnSetShortcutScreenSaver;
private System.Windows.Forms.Label label11;
private System.Windows.Forms.TextBox edtBlankScreenSaverShortcut;
private System.Windows.Forms.CheckBox chkMinimizeOnClose;
private System.Windows.Forms.CheckBox chkMinimizeToSystemTray;
private System.Windows.Forms.CheckBox chkCheckForUpdates;
private System.Windows.Forms.CheckBox chkGdiScaling;
private System.Windows.Forms.RadioButton rbElevationService;
private System.Windows.Forms.RadioButton rbElevationProcess;
private System.Windows.Forms.RadioButton rbElevationAdmin;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.RadioButton rbElevationNone;
private System.Windows.Forms.Button btnElevationInfo;
private System.Windows.Forms.Button btnStartStopService;
private System.Windows.Forms.CheckBox chkAutoInstallUpdates;
private System.Windows.Forms.GroupBox grpOptionsModules;
private System.Windows.Forms.CheckedListBox chkModules;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox chkOptionsUseDarkMode;
private System.Windows.Forms.Button btnOptionsAdvanced;
private System.Windows.Forms.Button btnOptionsLog;
private System.Windows.Forms.Button btnOptionsColorProfiles;
private System.Windows.Forms.ContextMenuStrip mnuColorProfiles;
private System.Windows.Forms.ToolStripMenuItem miCreateHDRColorProfile;
private System.Windows.Forms.ToolStripMenuItem miCreateSDRColorProfile;
#endregion
private System.Windows.Forms.Label lblUiType;
private System.Windows.Forms.ComboBox cbxUiType;
private System.Windows.Forms.GroupBox grpUi;
}
}
+391
View File
@@ -0,0 +1,391 @@
using ColorControl.Services.Common;
using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Shared.EventDispatcher;
using ColorControl.Shared.Forms;
using ColorControl.Shared.Services;
using ColorControl.XForms;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl.Forms;
public partial class OptionsPanel : UserControl
{
private readonly WinApiService _winApiService;
private readonly WinApiAdminService _winApiAdminService;
private readonly ElevationService _elevationService;
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
private readonly OptionsService _optionsService;
private bool _initialized;
private Config _config;
public OptionsPanel(WinApiService winApiService, WinApiAdminService winApiAdminService, ElevationService elevationService, KeyboardShortcutDispatcher keyboardShortcutDispatcher, OptionsService optionsService)
{
_winApiService = winApiService;
_winApiAdminService = winApiAdminService;
_elevationService = elevationService;
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
_optionsService = optionsService;
_config = _optionsService.GetConfig();
InitializeComponent();
if (DarkModeUtils.UseDarkMode)
{
for (var i = 0; i < Controls.Count; i++)
{
var control = Controls[i];
DarkModeUtils.SetControlTheme(control);
}
}
Init();
UpdateServiceInfo();
}
private void Init()
{
if (chkModules.Items.Count == 0)
{
foreach (var module in _config.Modules)
{
chkModules.Items.Add(module.DisplayName, module.IsActive);
}
}
chkStartMinimized.Checked = _config.StartMinimized;
chkMinimizeOnClose.Checked = _config.MinimizeOnClose;
chkMinimizeToSystemTray.Checked = _config.MinimizeToTray;
chkCheckForUpdates.Checked = _config.CheckForUpdates;
chkAutoInstallUpdates.Checked = _config.AutoInstallUpdates;
chkAutoInstallUpdates.Enabled = _config.CheckForUpdates && _config.ElevationMethod == ElevationMethod.UseService;
edtBlankScreenSaverShortcut.Text = _config.ScreenSaverShortcut;
chkGdiScaling.Checked = _config.UseGdiScaling;
chkOptionsUseDarkMode.Checked = _config.UseDarkMode;
chkStartAfterLogin.Checked = _winApiService.TaskExists(Program.TS_TASKNAME);
chkFixChromeFonts.Enabled = _winApiService.IsChromeInstalled();
if (chkFixChromeFonts.Enabled)
{
chkFixChromeFonts.Checked = _winApiService.IsChromeFixInstalled();
}
_initialized = false;
try
{
var _ = _config.ElevationMethod switch
{
ElevationMethod.None => rbElevationNone.Checked = true,
ElevationMethod.RunAsAdmin => rbElevationAdmin.Checked = true,
ElevationMethod.UseService => rbElevationService.Checked = true,
ElevationMethod.UseElevatedProcess => rbElevationProcess.Checked = true,
_ => false
};
cbxUiType.SelectedIndex = (int)_config.UiType;
}
finally
{
_initialized = true;
}
}
private void UpdateServiceInfo()
{
var text = "Use Windows Service";
btnStartStopService.Enabled = true;
if (_winApiService.IsServiceRunning())
{
text += " (running)";
btnStartStopService.Text = "Stop";
}
else if (_winApiService.IsServiceInstalled())
{
text += " (installed, not running)";
btnStartStopService.Text = "Start";
}
else
{
text += " (not installed)";
btnStartStopService.Text = "Start";
btnStartStopService.Enabled = false;
}
rbElevationService.Text = text;
}
private void edtShortcut_KeyDown(object sender, KeyEventArgs e)
{
((TextBox)sender).Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e);
}
private void edtShortcut_KeyUp(object sender, KeyEventArgs e)
{
KeyboardShortcutDispatcher.HandleKeyboardShortcutUp(e);
}
private void btnSetShortcutScreenSaver_Click(object sender, EventArgs e)
{
var shortcut = edtBlankScreenSaverShortcut.Text.Trim();
if (!KeyboardShortcutDispatcher.ValidateShortcut(shortcut))
{
return;
}
_config.ScreenSaverShortcut = shortcut;
_keyboardShortcutDispatcher.RegisterShortcut(MainWorker.SHORTCUTID_SCREENSAVER, shortcut);
}
private void chkFixChromeFonts_CheckedChanged(object sender, EventArgs e)
{
if (_initialized)
{
_config.FixChromeFonts = chkFixChromeFonts.Checked;
var folder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_winApiAdminService.InstallChromeFix(chkFixChromeFonts.Checked, folder);
}
}
private void chkAutoInstallUpdates_CheckedChanged(object sender, EventArgs e)
{
if (_initialized)
{
_config.AutoInstallUpdates = chkAutoInstallUpdates.Checked;
}
}
private void chkMinimizeToSystemTray_CheckedChanged(object sender, EventArgs e)
{
if (_initialized)
{
_optionsService.SetMinimizeToTray(chkMinimizeToSystemTray.Checked);
}
}
private void chkCheckForUpdates_CheckedChanged(object sender, EventArgs e)
{
if (_initialized)
{
_config.CheckForUpdates = chkCheckForUpdates.Checked;
chkAutoInstallUpdates.Enabled = _config.CheckForUpdates && _config.ElevationMethod == ElevationMethod.UseService;
}
}
private void chkGdiScaling_CheckedChanged(object sender, EventArgs e)
{
_config.UseGdiScaling = chkGdiScaling.Checked;
}
private void chkStartAfterLogin_CheckedChanged(object sender, EventArgs e)
{
if (!_initialized)
{
return;
}
var enabled = chkStartAfterLogin.Checked;
_optionsService.SetStartAfterLogin(enabled);
rbElevationAdmin.Enabled = enabled;
if (!enabled && rbElevationAdmin.Checked)
{
rbElevationNone.Checked = true;
}
}
private void btnElevationInfo_Click(object sender, EventArgs e)
{
MessageForms.InfoOk(Utils.ELEVATION_MSG + @$"
NOTE: if ColorControl itself already runs as administrator this is indicated in the title: ColorControl {Application.ProductVersion} (administrator).
Currently ColorControl is {(_winApiService.IsAdministrator() ? "" : "not ")}running as administrator.
");
}
private void rbElevationNone_CheckedChanged(object sender, EventArgs e)
{
if (!_initialized)
{
return;
}
if (sender is not RadioButton button || !button.Checked)
{
return;
}
_elevationService.SetElevationMethod((ElevationMethod)Utils.ParseInt((string)button.Tag), chkStartAfterLogin.Checked);
UpdateServiceInfo();
chkAutoInstallUpdates.Enabled = _config.CheckForUpdates && _config.ElevationMethod == ElevationMethod.UseService;
}
private async void btnStartStopService_Click(object sender, EventArgs e)
{
btnStartStopService.Enabled = false;
try
{
var start = btnStartStopService.Text == "Start";
if (start)
{
_winApiAdminService.StartService();
}
else
{
_winApiAdminService.StopService();
}
var wait = 1500;
while (wait > 0)
{
await Task.Delay(100);
if (start == _winApiService.IsServiceRunning())
{
break;
}
wait -= 100;
}
}
finally
{
btnStartStopService.Enabled = true;
}
UpdateServiceInfo();
}
private void chkMinimizeOnClose_CheckedChanged(object sender, EventArgs e)
{
_config.MinimizeOnClose = chkMinimizeOnClose.Checked;
}
private void chkOptionsUseDarkMode_CheckedChanged(object sender, EventArgs e)
{
if (!_initialized)
{
return;
}
_optionsService.SetDarkMode(chkOptionsUseDarkMode.Checked);
}
private void btnOptionsAdvanced_Click(object sender, EventArgs e)
{
var processPollingIntervalField = new FieldDefinition
{
FieldType = FieldType.Numeric,
Label = "Polling interval of process monitor (milliseconds).",
SubLabel = "Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load",
MinValue = 50,
MaxValue = 5000,
Value = _config.ProcessMonitorPollingInterval
};
var useRawInputField = new FieldDefinition
{
FieldType = FieldType.CheckBox,
Label = "Use Raw Input for shortcuts (hot keys)",
SubLabel = "This enables shortcuts to work during applications/games that block certain keys (like WinKey or Control). NOTE: if the application in the foreground runs with higher privileges than ColorControl, Raw Input does not work and normal hot keys are used",
Value = _config.UseRawInput
};
var setMinTmlAndMaxTmlField = new FieldDefinition
{
FieldType = FieldType.CheckBox,
Label = "Set MinTML and MaxTML when applying color profiles",
SubLabel = "When this is enabled MinTML and MaxTML will be automatically be set to respectively the minimum luminance and the maximum luminance of the color profile",
Value = _config.SetMinTmlAndMaxTml
};
var disableErrorPopupField = new FieldDefinition
{
FieldType = FieldType.CheckBox,
Label = "Disable error popup when applying presets",
SubLabel = "Check this to disable the error popup when there was an error applying a preset",
Value = _config.DisableErrorPopupWhenApplyingPreset
};
var values = MessageForms.ShowDialog("Advanced settings", new[] { processPollingIntervalField, useRawInputField, setMinTmlAndMaxTmlField, disableErrorPopupField });
if (values?.Any() != true)
{
return;
}
_config.ProcessMonitorPollingInterval = processPollingIntervalField.ValueAsInt;
_config.UseRawInput = useRawInputField.ValueAsBool;
_config.SetMinTmlAndMaxTml = setMinTmlAndMaxTmlField.ValueAsBool;
_config.DisableErrorPopupWhenApplyingPreset = disableErrorPopupField.ValueAsBool;
_keyboardShortcutDispatcher.SetUseRawInput(_config.UseRawInput);
}
private void btnOptionsLog_Click(object sender, EventArgs e)
{
LogWindow.CreateAndShow();
}
private void chkModules_ItemCheck(object sender, ItemCheckEventArgs e)
{
if (chkModules.Items.Count != _config.Modules.Count)
{
return;
}
var index = e.Index;
if (index < 0)
{
return;
}
var module = _config.Modules[index];
module.IsActive = e.NewValue == CheckState.Checked;
}
private void miCreateHDRColorProfile_Click(object sender, EventArgs e)
{
ColorProfileWindow.CreateAndShow();
}
private void btnOptionsColorProfiles_Click(object sender, EventArgs e)
{
mnuColorProfiles.ShowCustom(btnOptionsColorProfiles);
}
private void miCreateSDRColorProfile_Click(object sender, EventArgs e)
{
ColorProfileWindow.CreateAndShow(isHDR: false);
}
private void chkStartMinimized_CheckedChanged(object sender, EventArgs e)
{
if (_initialized)
{
_config.StartMinimized = chkStartMinimized.Checked;
}
}
private void cbxUiType_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_initialized)
{
return;
}
_optionsService.SetUiType((UiType)cbxUiType.SelectedIndex);
}
}
+123
View File
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
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
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="mnuColorProfiles.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>
-97
View File
@@ -1,97 +0,0 @@
using NWin32;
using NWin32.NativeTypes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace ColorControl
{
class GraphicsService
{
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public GraphicsService()
{
Initialize();
}
~GraphicsService()
{
Uninitialize();
}
protected virtual void Initialize()
{
}
protected virtual void Uninitialize()
{
}
protected void ToggleHDR(int delay = 1000)
{
Process.Start("ms-settings:display");
Thread.Sleep(delay);
var process = Process.GetProcessesByName("SystemSettings").FirstOrDefault();
if (process != null)
{
System.Windows.Forms.SendKeys.SendWait("{TAB}");
System.Windows.Forms.SendKeys.SendWait("{TAB}");
System.Windows.Forms.SendKeys.SendWait(" ");
System.Windows.Forms.SendKeys.SendWait("%{F4}");
}
}
protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical)
{
uint i = 0;
DEVMODEA devMode;
while (NativeMethods.EnumDisplaySettingsA(displayName, i, out devMode))
{
// Also compare width with vertical and height with horizontal in case of portrait mode
if (((!portrait && devMode.dmPelsWidth == horizontal && devMode.dmPelsHeight == vertical) ||
(portrait && devMode.dmPelsWidth == vertical && devMode.dmPelsHeight == horizontal))
&& devMode.dmBitsPerPel == 32 && devMode.dmDisplayFrequency == refreshRate)
{
IntPtr bla = Marshal.AllocHGlobal(Marshal.SizeOf(devMode));
Marshal.StructureToPtr(devMode, bla, false);
var result = NativeMethods.ChangeDisplaySettingsExA(displayName, bla, IntPtr.Zero, 0, IntPtr.Zero);
if (result != NativeConstants.DISP_CHANGE_SUCCESSFUL)
{
Logger.Error($"Could not set refreshrate {refreshRate} on display {displayName} because ChangeDisplaySettingsExA returned a non-zero return code: {result}");
}
return result == NativeConstants.DISP_CHANGE_SUCCESSFUL;
}
i++;
}
Logger.Info($"Could not set refreshrate {refreshRate} on display {displayName} because EnumDisplaySettings did not report it as a valid refreshrate");
return false;
}
protected List<uint> GetAvailableRefreshRatesInternal(string displayName, bool portrait, int horizontal, int vertical)
{
var list = new List<uint>();
uint i = 0;
DEVMODEA devMode;
while (NativeMethods.EnumDisplaySettingsA(displayName, i, out devMode))
{
if ((!portrait && devMode.dmPelsWidth == horizontal && devMode.dmPelsHeight == vertical) ||
(portrait && devMode.dmPelsWidth == vertical && devMode.dmPelsHeight == horizontal))
{
list.Add(devMode.dmDisplayFrequency);
}
i++;
}
return list;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace ColorControl
{
internal interface IModulePanel
{
public void UpdateInfo();
public void Init();
}
}
-70
View File
@@ -1,70 +0,0 @@
using LgTv;
using System.Collections.Generic;
using System.Linq;
namespace ColorControl
{
class LgPreset : PresetBase
{
public string name { get; set; }
public string appId { get; set; }
public List<string> steps { get; set; }
public static List<LgApp> LgApps { get; set; }
public LgPreset()
{
steps = new List<string>();
}
public LgPreset(LgPreset preset) : this()
{
id = GetNewId();
name = preset.name + " (copy)";
appId = preset.appId;
foreach (var step in preset.steps)
{
steps.Add(step);
}
}
public LgPreset Clone()
{
var preset = new LgPreset(this);
return preset;
}
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "App|200", "Steps|400", "Shortcut" };
}
public List<string> GetDisplayValues()
{
var values = new List<string>();
values.Add(name);
var app = appId;
if (LgApps != null)
{
var item = LgApps.FirstOrDefault(x => x.appId.Equals(appId));
if (item != null)
{
app = item.title + " (" + appId + ")";
}
}
values.Add(app);
values.Add(GetStepsDisplay());
values.Add(shortcut);
return values;
}
public string GetStepsDisplay()
{
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
}
}
}
Loaded 100 of 871 files, more files were not shown because too many files have changed in this diff. Show more