fix several issues, add remote controls to new UI

This commit is contained in:
Maassoft committed 2025-05-30 17:20:37 +02:00
1 parent c6c7e1ccc0
commit 827157f30f
91 files changed
+4140 -2530

No files matched your search

+15
View File
@@ -72,6 +72,21 @@
<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>
@@ -4,8 +4,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
@@ -48,7 +48,7 @@
{
<input class="form-check-input me-1" style="font-size: 1rem; visibility: hidden" type="checkbox">
}
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
Display
</button>
</span>
@@ -264,10 +264,7 @@
</label>
</div>
}
<div class="mb-2">
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
</div>
<NullableRangeInput TValue="int" @bind-Value="Preset.SDRBrightness" Id="sdrBrightness" Label="SDR brightness" Unit="%" />
</div>
</div>
</div>
@@ -28,7 +28,7 @@
}
else
{
<div class="row mb-3">
<div class="row mb-2">
@if (!Displays.Any())
{
<span>No displays found</span>
@@ -54,6 +54,13 @@ else
}
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">
@@ -320,4 +327,19 @@ else
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();
}
}
}
@@ -9,272 +9,235 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
</div>
<div class="modal-body">
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Preset == null)
{
<div>Loading...</div>
}
else
{
if (RecentApps != null)
{
<div class="mb-2">
<label class="form-label" for="recentApp">Recent app</label>
<select class="form-select" id="recentApp" @bind="RecentAppFilename" @bind:after="RecentAppSelected">
@foreach (var app in RecentApps)
{
<option value="@app.Filename" title="@app.Path">@app.FriendlyName</option>
}
</select>
</div>
}
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
<label class="form-check-label" for="showInQuickAccess">
Show in Quick Access
</label>
</div>
<div class="mb-2">
<label class="form-label" for="path">File/URI</label>
<div class="input-group mb-2">
<input class="form-control" id="path" type="text" @bind="Preset.Path" required />
<button type="button" class="btn btn-primary" @onclick="SelectFileOnClick" disabled="@SelectingFile">
<span class="@(SelectingFile ? "spinner-border spinner-border-sm" : "")" aria-hidden="true"></span>
<span role="status">Select...</span>
</button>
</div>
<div id="selectFileHelp" class="form-text">
Selecting a file opens a file dialog outside of the browser on the system where the ColorControl application is running.
</div>
</div>
<div class="mb-2">
<label class="form-label" for="parameters">Parameters</label>
<input class="form-control" id="parameters" type="text" @bind="Preset.Parameters" />
</div>
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
</div>
<div class="modal-body">
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Preset == null)
{
<div>Loading...</div>
}
else
{
if (RecentApps != null)
{
<div class="mb-2">
<label class="form-label" for="recentApp">Recent app</label>
<select class="form-select" id="recentApp" @bind="RecentAppFilename" @bind:after="RecentAppSelected">
@foreach (var app in RecentApps)
{
<option value="@app.Filename" title="@app.Path">@app.FriendlyName</option>
}
</select>
</div>
}
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
<label class="form-check-label" for="showInQuickAccess">
Show in Quick Access
</label>
</div>
<div class="accordion accordion-flush" id="presetAccordion">
<StartProgramInput @bind-Path="Preset.Path" @bind-Parameters="Preset.Parameters" @bind-Path:after="PathChanged"></StartProgramInput>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Advanced settings
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
<label class="form-check-label" for="runAsAdministrator">
Run as administrator
</label>
</div>
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
<div class="mb-2">
<label class="form-label" for="priority">Process priority</label>
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
{
<option value="@priority.ToString()">@priority.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#autoApplySettings">
Auto-apply settings
</button>
</h2>
<div id="autoApplySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.AutoSettings.AllowAutoApply" id="autoApply">
<label class="form-check-label" for="autoApply">
Automatically execute preset when process starts
</label>
</div>
<div class="mb-2">
<label class="form-label" for="autoApplyAction">Auto-apply action</label>
<select class="form-select" id="priority" @bind="Preset.AutoSettings.ProcessAutoAction">
@foreach (var action in Enum.GetValues<ProcessAutoAction>())
{
<option value="@action.ToString()">@action.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Pre-launch" @bind-Steps="Preset.PreLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Post-launch" @bind-Steps="Preset.PostLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Finalize" @bind-Steps="Preset.FinalizeSteps" TPreset="GamePreset"></StepsInput>
</div>
</div>
</div>
<div class="accordion accordion-flush" id="presetAccordion">
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Advanced settings
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
<label class="form-check-label" for="runAsAdministrator">
Run as administrator
</label>
</div>
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
<div class="mb-2">
<label class="form-label" for="priority">Process priority</label>
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
{
<option value="@priority.ToString()">@priority.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#autoApplySettings">
Auto-apply settings
</button>
</h2>
<div id="autoApplySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="Preset.AutoSettings.AllowAutoApply" id="autoApply">
<label class="form-check-label" for="autoApply">
Automatically execute preset when process starts
</label>
</div>
<div class="mb-2">
<label class="form-label" for="autoApplyAction">Auto-apply action</label>
<select class="form-select" id="priority" @bind="Preset.AutoSettings.ProcessAutoAction">
@foreach (var action in Enum.GetValues<ProcessAutoAction>())
{
<option value="@action.ToString()">@action.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Pre-launch" @bind-Steps="Preset.PreLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Post-launch" @bind-Steps="Preset.PostLaunchSteps" TPreset="GamePreset"></StepsInput>
<StepsInput Label="Finalize" @bind-Steps="Preset.FinalizeSteps" TPreset="GamePreset"></StepsInput>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
</div>
</div>
</div>
@code {
private GamePreset? Preset;
private GamePreset? Preset;
[Parameter]
public GamePreset? PresetParam { get; set; }
[Parameter]
public GamePreset? PresetParam { get; set; }
[Parameter]
public Func<GamePreset, Task>? AfterApply { get; set; }
[Parameter]
public Func<GamePreset, Task>? AfterApply { get; set; }
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
private List<GameApp>? RecentApps;
private string RecentAppFilename = "";
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
private List<GameApp>? RecentApps;
private string RecentAppFilename = "";
public bool SelectingFile { get; set; }
public bool SelectingFile { get; set; }
private string WasValidated = "";
private bool IsVisible;
private string WasValidated = "";
private bool IsVisible;
protected override async Task OnParametersSetAsync()
{
if (PresetParam == null)
{
return;
}
protected override async Task OnParametersSetAsync()
{
if (PresetParam == null)
{
return;
}
Preset = PresetParam.CloneWithId();
Preset = PresetParam.CloneWithId();
if (Preset.id == 0)
{
RecentApps = await _rpcClientService.CallAsync<List<GameApp>>("GameService", "GetMruApps", true);
RecentAppFilename = "";
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;
}
}
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;
}
private async Task ApplyClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
if (AfterApply != null)
{
await AfterApply.Invoke(Preset);
}
IsVisible = false;
}
IsVisible = false;
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
}
private bool IsValid()
{
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
}
private async Task SelectFileOnClick(MouseEventArgs e)
{
if (Preset == null)
{
return;
}
private void RecentAppSelected()
{
if (RecentApps == null || Preset == null)
{
return;
}
SelectingFile = true;
try
{
var options = new RpcUiClientOptions
{
Timeout = 60000
};
var app = RecentApps.FirstOrDefault(a => a.Filename == RecentAppFilename);
var result = await _rpcClientService.CallWithOptionsAsync<string>("GameService", "SelectPath", options);
if (app != null)
{
Preset.name = app.FriendlyName;
Preset.Path = app.Path;
}
}
if (result != null)
{
Preset.Path = result;
if (string.IsNullOrEmpty(Preset.name))
{
Preset.name = Path.GetFileNameWithoutExtension(result);
}
}
}
finally
{
SelectingFile = false;
}
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private void PathChanged()
{
if (Preset == null)
{
return;
}
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;
}
}
if (Preset.Path != null && string.IsNullOrEmpty(Preset.name))
{
Preset.name = Path.GetFileNameWithoutExtension(Preset.Path);
}
}
}
@@ -4,7 +4,7 @@
@rendermode Constants.RenderMode
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick" NextModalId="@NextModalId"></FieldsModal>
@code {
[Parameter]
@@ -19,6 +19,9 @@
[Parameter]
public string ModalId { get; set; } = "confirmModal";
[Parameter]
public string? NextModalId { get; set; }
[Parameter]
public List<FieldDefinition>? ExtraFields { get; set; }
@@ -4,8 +4,6 @@
@rendermode Constants.RenderMode
@inject NavigationManager _navigationManager;
@if (Fields == null)
{
return;
@@ -106,16 +104,7 @@
{
var fieldId = $"fld_{Random.Shared.Next()}";
<div class="mb-2">
<label class="form-range" for="@fieldId">@field.Label (Current value: @field.IntValue)</label>
<input class="form-range" type="range" min="@((int)field.MinValue)" max="@((int)field.MaxValue)" @bind="field.IntValue" id="@fieldId">
@if (field.SubLabel != null)
{
<div id="@(fieldId)_help" class="form-text">
@field.SubLabel
</div>
}
</div>
<RangeInput TValue="int" @bind-Value="field.IntValue" Id="@fieldId" Label="@field.Label" Min="@((int)field.MinValue)" Max="@((int)field.MaxValue)" SubLabel="@field.SubLabel" />
}
}
@@ -3,8 +3,7 @@
@using ColorControl.Shared.Common;
@rendermode Constants.RenderMode
@inject NavigationManager _navigationManager;
@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">
@@ -41,6 +40,9 @@
[Parameter]
public string? ModalId { get; set; }
[Parameter]
public string? NextModalId { get; set; }
[Parameter]
public string? ConfirmCaption { get; set; } = "Yes";
@@ -120,8 +122,13 @@
}
}
private void OnClose(MouseEventArgs e)
private async Task OnClose(MouseEventArgs e)
{
IsVisible = false;
if (!string.IsNullOrEmpty(NextModalId))
{
await jsHelper.OpenModal(NextModalId);
}
}
}
@@ -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,116 @@
@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);
}
}
@@ -19,7 +19,8 @@
<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) {
@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
@@ -44,9 +45,9 @@
private Dictionary<string, bool> Modifiers = new() {
{ "Shift", false },
{ "Control", false },
{ "Alt", false },
{ "Win", false }
};
{ "Alt", false },
{ "Win", false }
};
protected override void OnParametersSet()
{
@@ -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;
}
}
}
@@ -12,22 +12,21 @@
@if (Label != null && Steps != null)
{
var index = 0;
<div class="mb-2">
<div class="d-flex align-items-center">
<label class="form-label" for="name">@Label</label>
<div class="d-flex ms-auto">
@if (NvPresets != null || SamsungPresets != null || CustomItems != null)
@if (NvPresets != null || SamsungPresets != null || LgPresets != null || CustomItems != null)
{
<div class="dropdown dropend mb-1">
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">Add</button>
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 360px">
@if (NvPresets != null && NvPresetsEnabled)
{
<div class="mb-2">
<label class="form-label" for="nvPreset">NVIDIA preset</label>
<select class="form-select" id="nvPreset" @bind="NvPresetToAddId">
<option value="">Select preset</option>
<select class="form-select" multiple id="nvPreset" @onchange="NvPresetChange">
@foreach (var preset in NvPresets)
{
<option value="@preset.id">@preset.IdOrName</option>
@@ -77,6 +76,9 @@
</div>
}
}
<StartProgramInput @bind-Path="StartProgramPath" @bind-Parameters="StartProgramParameters"></StartProgramInput>
@if (ActionItems != null)
{
foreach (var customItem in ActionItems)
@@ -85,15 +87,15 @@
<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>
}
@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>
@@ -126,10 +128,7 @@
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" title="Move right" disabled="@(stepIndex == PresetSteps.Count - 1)" @onclick="() => MoveRightOnClick(stepIndex)">&gt;</button>
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" @onclick="() => RemoveOnClick(stepIndex)">Remove</button>
</div>
<div class="mb-2">
<label class="form-label" for="delay">Delay (@(step.Delay)ms)</label>
<input class="form-range" id="delay" type="range" min="0" max="10000" step="50" @bind="step.Delay" @bind:after="() => AfterDelayChanged()">
</div>
<RangeInput TValue="int" @bind-Value="step.Delay" Id="delay" Label="Delay" Unit="ms" Max="10000" @bind-Value:after="() => AfterDelayChanged()" />
</form>
index++;
@@ -212,7 +211,6 @@
private List<SamsungPreset>? SamsungPresets { get; set; }
private List<LgPreset>? LgPresets { get; set; }
private int? NvPresetToAddId { get; set; }
private int? SamsungPresetToAddId { get; set; }
private int? LgPresetToAddId { get; set; }
@@ -222,6 +220,11 @@
private List<FieldDefinition>? ActionFields;
private string? StartProgramPath;
private string? StartProgramParameters;
private List<int> NvPresetsToAdd = [];
protected override void OnParametersSet()
{
if (Steps != null)
@@ -295,7 +298,7 @@
private async Task AddPresetOnClick()
{
AddPreset(NvPresets, "NvPreset", NvPresetToAddId);
NvPresetsToAdd.ForEach(id => AddPreset(NvPresets, "NvPreset", id));
AddPreset(SamsungPresets, "SamsungPreset", SamsungPresetToAddId);
AddPreset(LgPresets, "LgPreset", LgPresetToAddId);
@@ -328,6 +331,8 @@
}
}
AddStartProgram();
await EmitSteps();
}
@@ -343,6 +348,22 @@
PresetSteps.Add(PresetStep.Parse($"{type}({preset.name})"));
}
private void AddStartProgram()
{
if (string.IsNullOrWhiteSpace(StartProgramPath))
{
return;
}
var path = StartProgramPath;
if (!string.IsNullOrWhiteSpace(StartProgramParameters))
{
path += $"; {string.Join("; ", StartProgramParameters.Split(' '))}";
}
PresetSteps.Add(PresetStep.Parse($"StartProgram({path})"));
}
private async Task EmitSteps()
{
if (Steps != null)
@@ -427,4 +448,13 @@
return field;
}
private void NvPresetChange(ChangeEventArgs eventArgs)
{
if (eventArgs.Value is IEnumerable<string> items)
{
NvPresetsToAdd.Clear();
NvPresetsToAdd.AddRange(items.Select(i => int.Parse(i)));
}
}
}
@@ -7,6 +7,14 @@
@if (Triggers != null)
{
if (!Triggers.Any())
{
<div>
<label class="form-label">No triggers found. Click to add:</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="mb-2">
@@ -9,8 +9,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
@@ -6,7 +6,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject IHttpContextAccessor _httpContextAccessor
@@ -3,14 +3,13 @@
@using ColorControl.UI.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@using ColorControl.UI.Components.Pages.Generic
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="deviceModal" tabindex="-1">
<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">
@@ -73,7 +72,7 @@
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
@@ -165,6 +164,10 @@
</form>
</div>
<div class="modal-footer">
@if (Device?.IsCustom == true)
{
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#removeDeviceModal">Remove</button>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
</div>
@@ -172,6 +175,8 @@
</div>
</div>
<ConfirmModal ModalId="removeDeviceModal" Title="Remove device" Message="Are you sure you want to remove this device?" OnConfirm="DeviceOnRemoveConfirmed" NextModalId="lgDeviceModal" />
@code {
private LgDeviceDto? _device;
@@ -208,6 +213,21 @@
}
}
private async Task DeviceOnRemoveConfirmed()
{
if (Device == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("LgService", "RemoveDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
private async Task TestConnectionClick(MouseEventArgs e)
{
IsTestingConnection = true;
@@ -9,8 +9,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
@@ -74,7 +72,7 @@
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Steps" @bind-Steps="Preset.steps" CustomItems="CustomItems" ActionItems="CustomActions" LgPresetsEnabled="false" TPreset="LgPreset"></StepsInput>
<StepsInput Label="Steps" @bind-Steps="Preset.Steps" CustomItems="CustomItems" ActionItems="CustomActions" LgPresetsEnabled="false" TPreset="LgPreset"></StepsInput>
<div class="mb-2">
<label class="form-label" for="description">Description</label>
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
@@ -4,6 +4,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.LG
@using ColorControl.UI.Components.Pages.RemoteControl
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@@ -22,6 +23,9 @@
<div class="d-flex align-items-center">
<h1>LG controller</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#rcCanvasRight" aria-controls="rcCanvasRight">
Remote control
</button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
Buttons
@@ -64,7 +68,18 @@
</div>
</div>
<LgDevicePage Device="CurrentDevice" />
<div class="offcanvas offcanvas-end" style="width: 280px" data-bs-scroll="true" tabindex="-1" id="rcCanvasRight" aria-labelledby="rcCanvasRightRightLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="rcCanvasRightRightLabel">Remote control</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<GenericRcPage ServiceName="LgService" Device="CurrentDevice" TDevice="LgDeviceDto" TPreset="LgPreset" TButtonEnum="ButtonType" Presets="RcPresets"></GenericRcPage>
</div>
</div>
<LgDevicePage Device="CurrentDevice" AfterApply="AfterDeviceApplied" />
<LgPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@@ -84,7 +99,7 @@ else
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#lgDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
@@ -119,7 +134,7 @@ else
{
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#lgDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
@@ -221,6 +236,7 @@ else
private InvokableActionDto<LgPreset>? CurrentInvokableAction;
private PresetOrder PresetOrder;
private List<LgPreset> RcPresets = [LgPresets.InStartPreset, LgPresets.EzAdjustPreset];
protected override async Task OnInitializedAsync()
{
@@ -297,7 +313,13 @@ else
StateHasChanged();
}
private async Task RefreshDevicesOnClick(MouseEventArgs e)
private async Task AfterDeviceApplied(LgDeviceDto device)
{
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
StateHasChanged();
}
private async Task RefreshDevicesOnClick()
{
IsRefreshDisabled = true;
try
@@ -6,8 +6,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<PageTitle>Logging</PageTitle>
@@ -7,8 +7,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal modal-lg" id="ditheringModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
@@ -1,5 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@@ -26,14 +27,14 @@
<div class="modal-body">
<form class="row g-1">
@if (ProfileNames != null)
@if (Profiles != null)
{
<div class="mb-2">
<label class="form-label" for="profileName">Profile</label>
<select class="form-select" id="profileName" @bind="CurrentProfileName" @bind:after="ProfileOnChange">
@foreach (var profileName in ProfileNames)
@foreach (var profile in Profiles)
{
<option value="@profileName">@profileName</option>
<option value="@profile.Name" title="@string.Join(", ", profile.Apps)">@profile.Name</option>
}
</select>
</div>
@@ -64,10 +65,8 @@
{
if (NvSettingConstants.RangeDriverSettings.Contains(setting.SettingId) && setting.Values != null)
{
<div class="mb-2">
<label class="form-label @(setting.Value != setting.DefaultValue && setting.Value != NvSettingConstants.UnsetDwordValue ? "text-warning fw-bold" : "")" for="@setting.SettingId">@setting.SettingText (@setting.Value)</label>
<input class="form-range" type="range" min="@setting.Values.Min(v => v.Value)" max="@setting.Values.Max(v => v.Value)" @bind="setting.Value" id="@setting.SettingId">
</div>
<RangeInput TValue="uint" @bind-Value="setting.Value" Id="@setting.SettingId.ToString()" Label="@setting.SettingText" Values="setting.Values.Select(v => v.Value).ToList()"
LabelStylingFunc="value => RangeLabelStyleFunc(value, setting)" />
}
else if (setting.Values != null)
{
@@ -137,7 +136,7 @@
private List<GroupedSettings>? Groups { get; set; }
private List<NvSettingItemDto>? DriverSettings { get; set; }
private List<string>? ProfileNames { get; set; }
private List<NvProfile>? Profiles { get; set; }
private string? CurrentProfileName { get; set; }
private bool IsVisible { get; set; }
@@ -244,7 +243,7 @@
private async Task OnlyFoundProgramsOnChange()
{
ProfileNames = await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
Profiles = await _rpcClientService.CallAsync<List<NvProfile>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
}
private async Task OnFocus(FocusEventArgs e)
@@ -252,8 +251,18 @@
if (!IsVisible)
{
IsVisible = true;
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
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" : "";
}
}
@@ -1,5 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@@ -53,47 +54,33 @@
@if (OverclockSettings.Type is NvGpuOcType.Curve or NvGpuOcType.Offset)
{
<div class="mb-2">
<label class="form-label" for="coreOffset">Core offset in MHz: @OverclockSettings.GraphicsOffsetMHz.ToSignedUnitString() (Min: @GpuInfo.MinCoreDeltaInMHz.ToSignedUnitString(), Max: @GpuInfo.MaxCoreDeltaInMHz.ToSignedUnitString())</label>
<input class="form-range" type="range" step="@GpuInfo.MinCoreStepInMHz" min="@GpuInfo.CorrectedMinCoreDeltaInMHz" max="@GpuInfo.MaxCoreDeltaInMHz" @bind="OverclockSettings.GraphicsOffsetMHz" id="coreOffset">
</div>
<div class="mb-2">
<label class="form-label" for="memoryOffset">Memory offset in MHz: @OverclockSettings.MemoryOffsetMHz.ToSignedUnitString() (Min: @GpuInfo.MinMemoryDeltaInMHz.ToSignedUnitString(), Max: @GpuInfo.MaxMemoryDeltaInMHz.ToSignedUnitString())</label>
<input class="form-range" type="range" step="1" min="@GpuInfo.MinMemoryDeltaInMHz" max="@GpuInfo.MaxMemoryDeltaInMHz" @bind="OverclockSettings.MemoryOffsetMHz" id="memoryOffset">
</div>
<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)
{
<div class="mb-2">
<label class="form-label" for="maxCoreFrequency">Maximum core frequency MHz: @OverclockSettings.MaximumFrequencyMHz.ToUnitString() (Min: @GpuInfo.MinCurveFrequencyInMHz.ToUnitString(), Max: @GpuInfo.MaxCurveFrequencyInMHz.ToUnitString())</label>
<input class="form-range" type="range" step="@GpuInfo.MinCoreStepInMHz" min="@GpuInfo.MinCurveFrequencyInMHz" max="@GpuInfo.MaxCurveFrequencyInMHz" @bind="OverclockSettings.MaximumFrequencyMHz" id="maxCoreFrequency">
<div id="maxCoreFrequencyHelp" class="form-text">Setting this value to anything else than '0' will limit the maximum frequency of the curve.</div>
</div>
<div class="mb-2">
<label class="form-label" for="maxCoreVoltage">Maximum core voltage in millivolt: @OverclockSettings.MaximumVoltageMv.ToUnitString("mv") (Min: @GpuInfo.MinCurveVoltageInMv.ToUnitString("mv"), Max: @GpuInfo.MaxCurveVoltageInMv.ToUnitString("mv"))</label>
<input class="form-range" type="range" step="5" min="@GpuInfo.MinCurveVoltageInMv" max="@GpuInfo.MaxCurveVoltageInMv" @bind="OverclockSettings.MaximumVoltageMv" id="maxCoreVoltage">
<div id="maxCoreFrequencyHelp" class="form-text">Setting this value to anything else than '0' will limit the maximum voltage (and also the frequency) of the curve.</div>
</div>
<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)
{
<div class="mb-2">
<label class="form-label" for="voltageBoost">Voltage boost in %: @OverclockSettings.VoltageBoostPercent.ToUnitString("%")</label>
<input class="form-range" type="range" step="1" min="0" max="100" @bind="OverclockSettings.VoltageBoostPercent" id="voltageBoost">
</div>
<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)
{
<div class="mb-2">
<label class="form-label" for="powerLimitWatts">Power limit in watts: @(((uint)(OverclockSettings.PowerPCM / 100000f * GpuInfo.DefaultPowerInMilliWatts)).ToKiloUnitString(units: "W")) (@OverclockSettings.PowerPCM.ToKiloUnitString(units: "%")) (Min: @GpuInfo.MinPowerInMilliWatts.ToKiloUnitString(units: "W"), Max: @GpuInfo.MaxPowerInMilliWatts.ToKiloUnitString(units: "W"))</label>
<input class="form-range" type="range" step="1" min="@GpuInfo.MinimumPowerInPCM" max="@GpuInfo.MaximumPowerInPCM" @bind="OverclockSettings.PowerPCM" id="powerLimitWatts">
</div>
<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
{
<div class="mb-2">
<label class="form-label" for="powerLimitPercentage">Power limit in percentage: @OverclockSettings.PowerPCM.ToKiloUnitString(units: "%") (Min: @GpuInfo.MinimumPowerInPCM.ToKiloUnitString(units: "%"), Max: @GpuInfo.MaximumPowerInPCM.ToKiloUnitString(units: "%")</label>
<input class="form-range" type="range" step="1" min="@GpuInfo.MinimumPowerInPCM" max="@GpuInfo.MaximumPowerInPCM" @bind="OverclockSettings.PowerPCM" id="powerLimitPercentage">
</div>
<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: "%")}")" />
}
}
}
@@ -138,6 +125,11 @@
protected override async Task OnParametersSetAsync()
{
if (!IsVisible)
{
return;
}
if (_preset != Preset)
{
_preset = Preset;
@@ -0,0 +1,109 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@using static ColorControl.Shared.Common.NestedItemsBuilder
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper
<div class="modal modal-lg" id="infoModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Info</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="row g-1">
<div class="row">
@if (InfoItems != null)
{
@RenderItems(InfoItems)
}
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@code {
private bool _visible;
[Parameter]
public bool Visible { get; set; }
private string? SelectedDisplayId { get; set; }
private List<NestedItem>? InfoItems;
protected override async Task OnParametersSetAsync()
{
if (_visible != Visible)
{
_visible = Visible;
if (Visible)
{
await GetSettingsAsync();
}
}
}
protected async Task GetSettingsAsync()
{
InfoItems = await _rpcClientService.CallAsync<List<NestedItem>>("NvService", "GetInfoItems");
if (InfoItems != null)
{
InfoItems.ForEach(i => i.Expanded = true);
}
}
RenderFragment<List<NestedItem>> RenderItems => items =>
@<div class="accordion accordion-flush" id="@items.GetHashCode()">
@foreach(var item in items)
{
@if (item.NestedItems.Count > 0)
{
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button py-2 @(item.Expanded ? "show" : "collapsed")" type="button" data-bs-toggle="collapse" data-bs-target="#@item.GetHashCode()" aria-expanded="false" aria-controls="flush-collapseOne" @onclick="() => OnItemClick(item)">
@item.Value
</button>
</h2>
<div id="@item.GetHashCode()" class="accordion-collapse collapse @(item.Expanded ? "show" : "")" data-bs-parent="#@items.GetHashCode()">
<div class="accordion-body">
@if (item.Expanded)
{
@RenderItems(item.NestedItems)
}
</div>
</div>
</div>
}
else
{
<div class="mt-1" style="margin-left: 1.25rem">
@item.Value
</div>
}
}
</div>
;
private async Task OnItemClick(NestedItem item)
{
await Task.Delay(400);
var classes = await jsHelper.GetElementClassName(item.GetHashCode().ToString());
item.Expanded = classes?.Contains(" show") == true;
}
}
@@ -1,17 +1,18 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.DisplayInfo
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.UI.Services;
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@using ColorControl.Shared.Common
@using ColorControl.UI.Generics
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject JSHelper jsHelper;
@inject JSHelper jsHelper
@inject ProtectedLocalStorage _localStorage
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
@@ -109,7 +110,7 @@
{
<div class="mb-2">
<label class="form-label" for="activeResolution">Active resolution</label>
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution">
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution" @bind:after="() => AfterResolutionChange()">
<option value="0x0">Not set</option>
@foreach (var resolution in resolutions)
{
@@ -119,7 +120,7 @@
</div>
<div class="mb-2">
<label class="form-label" for="virtualResolution">Virtual resolution</label>
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution">
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution" @bind:after="() => AfterResolutionChange()">
<option value="0x0">Not set</option>
@foreach (var resolution in resolutions)
{
@@ -170,7 +171,7 @@
{
<div class="mb-2">
<label class="form-label" for="refreshRate">Refresh Rate</label>
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate">
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate" @bind:after="() => AfterRefreshRateChange()">
@foreach (var refreshRate in refreshRates)
{
<option value="@refreshRate.ToString()">@refreshRate.ToString() Hz</option>
@@ -182,6 +183,34 @@
</div>
</div>
}
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
@if (!Preset.IsDisplayPreset)
{
<input class="form-check-input me-1" style="font-size: 1rem" title="Apply these settings" type="checkbox" @bind="Preset.DpiScaling.ApplyScaling" id="applyDpiScaling">
}
<button class="accordion-button collapsed py-2 @(Preset.DpiScaling.ApplyScaling ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#dpiScalingSettings">
Dpi Scaling
</button>
</span>
</h2>
<div id="dpiScalingSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="dpiScaling">Percentage</label>
<select class="form-select" id="dpiScaling" @bind="Preset.DpiScaling.Percentage">
@foreach (var dpiScale in Shared.Native.CCD.DpiValues)
{
<option value="@dpiScale.ToString()">@dpiScale.ToString()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
@@ -256,14 +285,8 @@
</h2>
<div id="colorEnhancements" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="digitalVibrance">Digital vibrance: @Preset.ColorEnhancementSettings.DigitalVibranceLevel</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.ColorEnhancementSettings.DigitalVibranceLevel" id="digitalVibrance">
</div>
<div class="mb-2">
<label class="form-label" for="hue">Hue: @Preset.ColorEnhancementSettings.HueAngle°</label>
<input class="form-range" type="range" min="0" max="359" @bind="Preset.ColorEnhancementSettings.HueAngle" id="hue">
</div>
<RangeInput TValue="int" @bind-Value="Preset.ColorEnhancementSettings.DigitalVibranceLevel" Id="digitalVibrance" Label="Digital vibrance" />
<RangeInput TValue="int" @bind-Value="Preset.ColorEnhancementSettings.HueAngle" Id="hue" Label="Hue" Unit="°" Max="359" />
</div>
</div>
</div>
@@ -405,10 +428,8 @@
}
</select>
</div>
<div class="mb-2">
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
</div>
<NullableRangeInput TValue="int" @bind-Value="Preset.SDRBrightness" Id="sdrBrightness" Label="SDR brightness" Unit="%" />
</div>
</div>
</div>
@@ -539,11 +560,52 @@
</div>
}
<div class="accordion accordion-flush" id="presetAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
Triggers
</button>
</h2>
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<TriggerInput Triggers="Preset.Triggers"></TriggerInput>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
Steps
</button>
</h2>
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<StepsInput Label="Steps" @bind-Steps="Preset.Steps" NvPresetsEnabled="false" TPreset="NvPreset"></StepsInput>
<div class="mb-2">
<label class="form-label" for="description">Description</label>
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
</div>
</div>
</div>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
@if (Preset?.IsDisplayPreset == true)
{
<input class="form-check-input" type="checkbox" @bind="ApplyImmediately" id="directApply" @bind:after="ApplyImmediatelyAfter">
<label class="form-check-label" for="directApply">
Apply changes immediately
</label>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" disabled="@(!IsValid())" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
</div>
@@ -581,14 +643,33 @@
private bool IsGpuSettingsOpened;
private bool ShowOverclocking;
private bool ApplyImmediately;
private NvPreset? OriginalPreset;
private bool Applying;
private bool Queued;
protected override async Task OnParametersSetAsync()
{
if (!IsVisible)
{
return;
}
if ((IsDriverSettingsOpened || IsGpuSettingsOpened) && Preset != null)
{
return;
}
ApplyImmediately = false;
Preset = PresetParam == null ? null : PresetParam.CloneWithId();
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
if (Preset?.IsDisplayPreset == true)
{
Preset = displayPresets.FirstOrDefault(p => p.DisplayId == Preset.DisplayId) ?? Preset;
}
if (Preset != null && PresetParam != null)
{
resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("NvService", "GetAvailableResolutionsV2", Preset);
@@ -598,27 +679,51 @@
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
if (Preset?.IsDisplayPreset == false)
{
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
var deviceName = Preset?.IsDisplayPreset == true ? Preset.DisplayId : Preset?.DisplayId ?? displayPresets?.FirstOrDefault()?.DisplayId;
if (Preset != null)
{
ColorProfiles = await _rpcClientService.CallAsync<List<ColorProfileDto>>("ColorProfileService", "GetAllDisplayColorProfiles", deviceName);
OriginalPreset = Preset.Clone();
}
ApplyImmediately = await _localStorage.TryGet<bool>($"{nameof(NvPreset)}_{nameof(ApplyImmediately)}");
ShowOverclocking = true;
}
protected override void OnAfterRender(bool firstRender)
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// if (IsVisible && Preset?.IsDisplayPreset == false)
// {
// Preset.UpdateAutoApplySettings(keepChanges: true);
// }
await DoApplyImmediately();
}
private async Task DoApplyImmediately()
{
if (IsVisible && OriginalPreset != null && Preset?.IsDisplayPreset == true)
{
if (Applying)
{
Queued = true;
return;
}
do
{
Applying = true;
Queued = false;
Preset.UpdateAutoApplySettings(OriginalPreset, true);
if (ApplyImmediately && Preset.HasApplicableSettings)
{
await ApplyDisplayPreset();
}
OriginalPreset = Preset.Clone();
Applying = false;
}
while (Queued);
}
}
private async Task ApplyClick(MouseEventArgs e)
@@ -634,29 +739,6 @@
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;
}
else
{
Preset.DisplayConfig.Resolution = new VirtualResolution(0, 0);
}
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
if (newRefreshRate != null)
{
Preset.DisplayConfig.RefreshRate = newRefreshRate;
}
Preset.ApplyNovideoSettings = Preset.NovideoSettings.ApplyClamp;
if (Preset.IsDisplayPreset)
@@ -734,4 +816,52 @@
{
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
}
private async Task AfterResolutionChange()
{
if (Preset == null)
{
return;
}
var newResolution = resolutions?.FirstOrDefault(r => r.GetActiveString() == selectedActiveResolution);
if (newResolution != null)
{
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;
}
else
{
Preset.DisplayConfig.Resolution = new VirtualResolution(0, 0);
}
await DoApplyImmediately();
}
private async Task AfterRefreshRateChange()
{
if (Preset == null)
{
return;
}
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
if (newRefreshRate != null)
{
Preset.DisplayConfig.RefreshRate = newRefreshRate;
}
await DoApplyImmediately();
}
private async Task ApplyImmediatelyAfter()
{
await _localStorage.SetAsync($"{nameof(NvPreset)}_{nameof(ApplyImmediately)}", ApplyImmediately);
}
}
@@ -19,6 +19,7 @@
<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>
@@ -30,6 +31,7 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#ditheringModal" @onclick="() => DitheringVisible = true">Test dithering</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#driverSettingsModal" @onclick="() => CurrentDriverSettingsPreset = null">Driver settings</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#infoModal" @onclick="() => InfoVisible = true">Info</a></li>
</ul>
</div>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
@@ -75,6 +77,7 @@ else
<div class="card-body">
<small class="text-body">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</small>
<small class="text-muted">@preset.colorData</small>
<small class="text-muted">Scale: @preset.DpiScaling.ToString()</small>
@if (preset.DitherState != NvDitherState.Auto)
{
<br />
@@ -93,22 +96,7 @@ else
@if (preset.driverSettings?.Any() == true)
{
<br />
<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)">
<small>@preset.driverSettings.Count() driver settings changed</small>
</span>
<form class="dropdown-menu">
@if (DriverSettings != null)
{
<ul class="list-group p-0">
@foreach (var setting in DriverSettings)
{
<li class="list-group-item py-0 px-1"><small>@setting.FriendlyName</small></li>
}
</ul>
}
</form>
</div>
@RenderPresetSettings(preset)
}
</div>
</div>
@@ -239,7 +227,7 @@ else
}
@if (preset.applyDriverSettings)
{
<li class="list-group-item p-1">@preset.driverSettings.Count changed driver settings</li>
<li class="list-group-item p-1">@RenderPresetSettings(preset)</li>
}
@if (preset.ApplyNovideoSettings)
{
@@ -278,6 +266,7 @@ else
private NvPreset? CurrentGpuSettingsPreset;
private NvGpuInfoDto? CurrentGpuInfo;
private bool DitheringVisible;
private bool InfoVisible;
private List<NvSettingItemDto>? DriverSettings;
private PresetOrder PresetOrder;
@@ -301,9 +290,42 @@ else
}
}
RenderFragment<NvPreset> RenderPresetSettings => preset =>
@<div class="dropdown">
<span role="button" class="text-decoration-underline dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" @onclick="() => DriverSettingsOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
<small>@preset.driverSettings.Count() driver settings changed</small>
</span>
<form class="dropdown-menu" style="width: max-content">
@if (DriverSettings != null)
{
<ul class="list-group p-0">
@foreach (var settingId in preset.driverSettings.Keys)
{
var setting = DriverSettings.FirstOrDefault(s => s.SettingId == settingId);
if (setting == null)
{
continue;
}
var settingValue = setting.Values.FirstOrDefault(v => v.Value == preset.driverSettings[settingId]);
if (settingValue == null) {
continue;
}
<li class="list-group-item py-0 px-1"><small>@setting.Name: @settingValue.ValueName</small></li>
}
</ul>
}
</form>
</div>
;
private async Task PresetOnClick(NvPreset preset)
{
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
if (result)
{
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
}
private void PresetOnEditClick(NvPreset preset)
@@ -395,11 +417,6 @@ else
private async Task DriverSettingsOnClick(NvPreset preset)
{
var driverSettings = await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", Type.Missing, true);
if (driverSettings != null)
{
DriverSettings = driverSettings.Where(s => s.Value != s.DefaultValue && s.State == 2).ToList();
}
DriverSettings ??= await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", Type.Missing, true);
}
}
@@ -134,13 +134,9 @@ else
<div class="card-body">
<h5 class="card-title">Advanced</h5>
<form>
<div class="mb-3 form-check">
<label class="form-range" for="processMonitorPollingInterval">
Polling interval of process monitor (@config.ProcessMonitorPollingInterval milliseconds)
</label>
<input class="form-range" type="range" min="50" max="10000" step="50" @bind="config.ProcessMonitorPollingInterval" id="processMonitorPollingInterval" aria-describedby="processMonitorPollingIntervalHelp" @bind:after="PollingIntervalOnInput">
<div id="processMonitorPollingIntervalHelp" class="form-text">Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load</div>
</div>
<RangeInput TValue="int" Min="50" Max="10000" Step="50" @bind-Value="config.ProcessMonitorPollingInterval" Id="processMonitorPollingInterval" Label="Polling interval of process monitor" Unit=" milliseconds"
SubLabel="Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load"
/>
<div class="mb-3 form-check">
<input class="form-check-input" type="checkbox" @bind="config.UseRawInput" id="useRawInput" aria-describedby="useRawInputHelp" @oninput="UseRawInputInput">
<label class="form-check-label" for="useRawInput">
@@ -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,319 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Base
@using ColorControl.Shared.Contracts.RemoteControl
@using ColorControl.UI.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@using System.Globalization
@* @rendermode Constants.RenderMode *@
@typeparam TDevice where TDevice : BaseDeviceDto
@typeparam TPreset where TPreset : PresetBase, new()
@typeparam TButtonEnum where TButtonEnum : struct, Enum
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper
<style>
.myhover {
border-radius: 25px;
width: @(ButtonSize)%;
aspect-ratio: 1;
opacity: 0.2;
border-width: 0;
}
.myhover:hover {
opacity: 0.4;
background-color: white;
}
</style>
<GenericRcButtonPage ButtonParam="CurrentButton" AfterApply="AfterApplyButton" TPreset="TPreset" TButtonEnum="TButtonEnum" Presets="Presets"></GenericRcButtonPage>
<div>
<div>
<h5>@(Device?.Name == null ? $"No device selected" : Device.Name)</h5>
</div>
<div>
<form class="row g-1 needs-validation @WasValidated" novalidate>
@if (Remotes == null)
{
<div>Loading...</div>
}
else
{
<select class="form-select" id="remoteControl" @bind="SelectedRemoteName" @bind:after="RemoteChanged">
@foreach (var remote in Remotes)
{
<option id="@remote.Name">@remote.Name</option>
}
</select>
if (RemoteController == null)
{
<div>Select a remote</div>
}
else
{
@if (AllowEditing)
{
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
}
@* <p>Left: @CurrentX, Top: @CurrentY</p> *@
<div style="position: relative" @onclick="Button_Clicked" @oncontextmenu:preventDefault="true" @oncontextmenu="e => NewRcButtonRightClick(e)">
<img src="remotes/@RemoteController.ImageFileName" id="rcImage" class="w-100" />
@foreach (var button in RemoteController.Buttons)
{
<button class="myhover" type="button"
style="position: absolute; top: @(button.Top.ToString(CultureInfo.InvariantCulture))%; left: @(button.Left.ToString(CultureInfo.InvariantCulture))%"
@onclick="() => RcButtonOnClick(button)" @oncontextmenu="e => RcButtonRightClick(e, button)" @oncontextmenu:preventDefault="true" @oncontextmenu:stopPropagation>
</button>
}
</div>
}
}
</form>
</div>
</div>
@code {
private TDevice? _device;
[Parameter]
public TDevice? Device { get; set; }
[Parameter]
public Func<TDevice, Task>? AfterApply { get; set; }
[Parameter]
public string? ServiceName { get; set; }
[Parameter]
public List<TPreset>? Presets { get; set; }
private string WasValidated = "";
private bool IsVisible;
private string? SelectedRemoteName;
private BaseRcDto<TPreset, TButtonEnum>? RemoteController;
private List<BaseRcDto<TPreset, TButtonEnum>>? Remotes;
private double CurrentX, CurrentY;
private RectDimension? ImageDimension;
private BaseRcButtonDto<TPreset, TButtonEnum>? CurrentButton;
private double ButtonSize = 13;
private bool AllowEditing = false;
protected override async void OnParametersSet()
{
if (ServiceName != null && (_device != Device || Device == null))
{
_device = Device;
Remotes = await _rpcClientService.CallAsync<List<BaseRcDto<TPreset, TButtonEnum>>>(ServiceName, "GetRemotes");
if (Remotes?.Any() == true)
{
RemoteController = Remotes.First();
SelectedRemoteName = RemoteController.Name;
StateHasChanged();
}
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (RemoteController == null || ServiceName == null)
{
return;
}
if (!IsValid())
{
WasValidated = "was-validated";
return;
}
var result = await _rpcClientService.CallAsync<bool>(ServiceName, "UpdateRemote", RemoteController);
// if (AfterApply != null)
// {
// await AfterApply.Invoke(Device);
// }
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
}
}
private bool IsValid()
{
return true; // !string.IsNullOrEmpty(Device?.Name) && (Device?.IsCustom != true || !string.IsNullOrEmpty(Device?.IpAddress));
}
private async Task RcButtonOnClick(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (ServiceName == null)
{
return;
}
var rcButton = button.Button;
var preset = button.Preset;
if (preset == null)
{
preset = new TPreset();
preset.name = rcButton.ToString();
preset.SetSteps([rcButton.ToString()]);
}
await _rpcClientService.CallAsync<bool>(ServiceName, "ApplyPreset", preset);
}
public async Task Button_Clicked(MouseEventArgs e)
{
if (ImageDimension == null)
{
ImageDimension = await jsHelper.GetElementDimensions("rcImage");
}
if (ImageDimension != null)
{
CurrentX = Math.Round(((double)e.OffsetX / ImageDimension.Width) * 100, 4);
CurrentY = Math.Round(((double)e.OffsetY / ImageDimension.Height) * 100, 4);
return;
}
CurrentX = e.OffsetX;
CurrentY = e.OffsetY;
}
private async Task RcButtonRightClick(MouseEventArgs e, BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (!AllowEditing)
{
return;
}
CurrentButton = button;
if (e.ShiftKey)
{
AlignItemsHorizontally(button);
return;
}
if (e.CtrlKey)
{
AlignItemsVertically(button);
return;
}
await jsHelper.OpenModal("rcButtonModal");
}
private void AlignItemsHorizontally(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (RemoteController?.Buttons == null)
{
return;
}
var alignedButtons = RemoteController.Buttons.Where(b => b.Top - 2 <= button.Top && b.Top + 2 >= button.Top);
foreach (var b in alignedButtons)
{
b.Top = button.Top;
}
}
private void AlignItemsVertically(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
if (RemoteController?.Buttons == null)
{
return;
}
var alignedButtons = RemoteController.Buttons.Where(b => b.Top - 15 <= button.Top && b.Top + 15 >= button.Top && b.Left - 8 <= button.Left && b.Left + 8 >= button.Left);
foreach (var b in alignedButtons)
{
b.Left = button.Left;
}
}
private async Task NewRcButtonRightClick(MouseEventArgs e)
{
if (!AllowEditing)
{
return;
}
if (ImageDimension == null)
{
ImageDimension = await jsHelper.GetElementDimensions("rcImage");
}
if (ImageDimension != null)
{
var posX = Math.Round(((double)e.OffsetX / ImageDimension.Width) * 100, 4);
var posY = Math.Round(((double)e.OffsetY / ImageDimension.Height) * 100, 4);
posX -= (ButtonSize / 2);
posY -= ((double)ImageDimension.Width / ImageDimension.Height) * ((ButtonSize / 2) - 0.5);
CurrentButton = new BaseRcButtonDto<TPreset, TButtonEnum>
{
Top = posY,
Left = posX
};
await jsHelper.OpenModal("rcButtonModal");
}
}
private Task AfterApplyButton(BaseRcButtonDto<TPreset, TButtonEnum>? button)
{
if (RemoteController == null)
{
return Task.CompletedTask;
}
if (button == null)
{
RemoteController.Buttons.Remove(CurrentButton);
}
else if (!RemoteController.Buttons.Contains(button))
{
RemoteController.Buttons.Add(button);
}
StateHasChanged();
return Task.CompletedTask;
}
private void KeyDown(KeyboardEventArgs e)
{
}
private void KeyUp(KeyboardEventArgs e)
{
}
private void RemoteChanged()
{
RemoteController = Remotes?.FirstOrDefault(r => r.Name == SelectedRemoteName);
ImageDimension = null;
}
}
@@ -1,5 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -7,10 +8,8 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="deviceModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal" 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">
@@ -73,7 +72,7 @@
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
Device options
</button>
</h2>
@@ -165,6 +164,10 @@
</form>
</div>
<div class="modal-footer">
@if (Device?.IsCustom == true)
{
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#removeDeviceModal">Remove</button>
}
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
</div>
@@ -172,6 +175,8 @@
</div>
</div>
<ConfirmModal ModalId="removeDeviceModal" Title="Remove device" Message="Are you sure you want to remove this device?" OnConfirm="DeviceOnRemoveConfirmed" NextModalId="samsungDeviceModal" />
@code {
private SamsungDeviceDto? _device;
@@ -216,6 +221,21 @@
}
}
private async Task DeviceOnRemoveConfirmed()
{
if (Device == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "RemoveDevice", Device);
if (AfterApply != null)
{
await AfterApply.Invoke(Device);
}
}
private async Task TestConnectionClick(MouseEventArgs e)
{
IsTestingConnection = true;
@@ -9,8 +9,6 @@
@rendermode Constants.RenderMode
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
@@ -4,6 +4,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.UI.Components.Pages.RemoteControl
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@@ -22,6 +23,9 @@
<div class="d-flex align-items-center">
<h1>Samsung controller</h1>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#rcCanvasRight" aria-controls="rcCanvasRight">
Remote control
</button>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
Buttons
@@ -62,6 +66,16 @@
</div>
</div>
<div class="offcanvas offcanvas-end" style="width: 280px" data-bs-scroll="true" tabindex="-1" id="rcCanvasRight" aria-labelledby="rcCanvasRightRightLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="rcCanvasRightRightLabel">Remote control</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<GenericRcPage ServiceName="SamsungService" Device="CurrentDevice" TDevice="SamsungDeviceDto" TPreset="SamsungPreset" TButtonEnum="ButtonType"></GenericRcPage>
</div>
</div>
<SamsungDevicePage Device="CurrentDevice" AfterApply="AfterDeviceApplied" />
<SamsungPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
@@ -82,7 +96,7 @@ else
}
<span role="status">Refresh</span>
</button>
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#samsungDeviceModal" @onclick="() => AddDeviceOnClick()">
Add
</button>
</div>
@@ -117,7 +131,7 @@ else
{
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
}
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#samsungDeviceModal" @onclick="() => DeviceOnClick(device)">
Settings
</button>
</div>
@@ -210,6 +224,7 @@ else
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");
}
+22
View File
@@ -2,6 +2,14 @@
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;
@@ -26,6 +34,20 @@ public class JSHelper(IJSRuntime jsRuntime)
await InvokeVoidAsync("ShowToast", id);
}
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();
+8
View File
@@ -14,4 +14,12 @@ export function OpenModal(modalId) {
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;
}
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

+6 -16
View File
@@ -6,20 +6,13 @@
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>false</MapFileExtensions>
<UpdateUrl>https://www.maassoft.com/downloads/ColorControl/</UpdateUrl>
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<Company>Maassoft</Company>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>10.2.0.0</ApplicationVersion>
<Version>10.2.0.0</Version>
<ApplicationVersion>10.3.0.0</ApplicationVersion>
<Version>10.3.0.0</Version>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>false</BootstrapperEnabled>
@@ -30,13 +23,6 @@
<ApplicationIcon>Resources\pngbarn.ico</ApplicationIcon>
<Copyright>2020-2025 Maassoft</Copyright>
<Authors>Maassoft</Authors>
<ManifestCertificateThumbprint>CF0BC153EA664CD97AC6F234B91F45D34630958F</ManifestCertificateThumbprint>
<ManifestKeyFile>ColorControl_1_TemporaryKey.pfx</ManifestKeyFile>
<GenerateManifests>true</GenerateManifests>
<TargetZone>LocalIntranet</TargetZone>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
<SignManifests>true</SignManifests>
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
@@ -52,6 +38,8 @@
<PathMap Condition="'$(Configuration)' == 'Release'">$(MSBuildThisFileDirectory)=.\</PathMap>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\LgRemotes.json" />
<None Remove="Resources\SamsungRemotes.json" />
<None Remove="XForms\BrowserWindow.xaml" />
<None Remove="XForms\ColorProfileWindow.xaml" />
<None Remove="XForms\LogWindow.xaml" />
@@ -82,10 +70,12 @@
<Compile Update="Services\Samsung\SamsungPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<EmbeddedResource Include="Resources\LgRemotes.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>
+17 -13
View File
@@ -1,4 +1,8 @@
using ColorControl.Services.AMD;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using ColorControl.Services.AMD;
using ColorControl.Services.LG;
using ColorControl.Services.NVIDIA;
using ColorControl.Services.Samsung;
@@ -6,10 +10,6 @@ using ColorControl.Shared.Common;
using ColorControl.Shared.Contracts;
using ColorControl.Svc;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
{
@@ -23,20 +23,18 @@ namespace ColorControl
return true;
}
var useConsole = startUpParams.NoGui || existingProcess != null;
var useConsole = startUpParams.NoGui || existingProcess?.SessionId > 0;
if (!useConsole)
{
return false;
}
startUpParams.NoGui = true;
var result = false;
if (startUpParams.ExecuteHelp)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine();
Console.WriteLine($@"ColorControl CLI {Application.ProductVersion}
@@ -56,7 +54,7 @@ Options:
if (startUpParams.ExecuteLgPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing LG-preset '{startUpParams.LgPresetName}'...");
await LgService.ExecutePresetAsync(startUpParams.LgPresetName);
@@ -68,7 +66,7 @@ Options:
if (startUpParams.ExecuteSamsungPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing Samsung-preset '{startUpParams.SamsungPresetName}'...");
await SamsungService.ExecutePresetAsync(startUpParams.SamsungPresetName);
@@ -80,7 +78,7 @@ Options:
if (startUpParams.ExecuteNvidiaPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing NVIDIA-preset '{startUpParams.NvidiaPresetIdOrName}'...");
await NvService.ExecutePresetAsync(startUpParams.NvidiaPresetIdOrName);
@@ -92,7 +90,7 @@ Options:
if (startUpParams.ExecuteAmdPreset)
{
Utils.OpenConsole();
PrepareConsole(startUpParams);
Console.WriteLine($"Executing AMD-preset '{startUpParams.AmdPresetIdOrName}'...");
await AmdService.ExecutePresetAsync(startUpParams.AmdPresetIdOrName);
@@ -105,6 +103,12 @@ Options:
return result;
}
private static void PrepareConsole(StartUpParams startUpParams)
{
Utils.OpenConsole();
startUpParams.NoGui = true;
}
private static async Task StartElevated()
{
try
+442 -442
View File
@@ -29,446 +29,446 @@ using System.Windows.Forms;
namespace ColorControl
{
static class Program
{
public const string TS_TASKNAME = "ColorControl";
public static string DataDir { get; private set; }
public static string ConfigFilename { get; private set; }
public static string LogFilename { get; private set; }
public static Config Config { get; private set; }
public static GlobalContext AppContext { get; private set; }
public static bool IsRestarting { get; private set; }
public static bool UserExit = false;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
public static MainForm _mainForm;
private static LoggingRule _loggingRule;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] // autogenerated
public static void Main(string[] args)
{
// STA
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
DataDir = Utils.GetDataPath();
var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true;
var runElevated = args.Contains("--elevated");
InitLogger(runAsService, runElevated);
Logger.Debug($"Using data path: {DataDir}");
//Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName);
if (runAsService)
{
await RunService(args);
return;
}
await RunApp(args);
}
static async Task RunApp(string[] args)
{
var startTime = DateTime.Now;
var currentDomain = AppDomain.CurrentDomain;
// Handler for unhandled exceptions.
currentDomain.UnhandledException += GlobalUnhandledExceptionHandler;
// Handler for exceptions in threads behind forms.
Application.ThreadException += GlobalThreadExceptionHandler;
LoadConfig();
if (Config.UseGdiScaling)
{
var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
Logger.Debug($"Result of setting SetHighDpiMode: {result}");
}
var mutexId = $"Global\\{typeof(MainForm).GUID}";
var startUpParams = StartUpParams.Parse(args);
AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, mutexId);
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
MessageForms.Title = AppContext.ApplicationTitle;
var existingProcess = Utils.GetProcessByName("ColorControl");
try
{
if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess))
{
return;
}
}
finally
{
Utils.CloseConsole();
}
_mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated);
try
{
if (!mutexCreated)
{
if (existingProcess != null && existingProcess.Threads.Count > 0)
{
var thread = existingProcess.Threads[0];
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
return;
}
MessageBox.Show("Only one instance of this program can be active.", "ColorControl");
}
else
{
_mutex.WaitOne();
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (Debugger.IsAttached)
{
//Utils.StartService();
var winApiService = ServiceProvider.GetRequiredService<WinApiService>();
if (winApiService.IsAdministrator())
{
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.ElevatedPipe;
_ = Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None));
}
}
var worker = ServiceProvider.GetRequiredService<MainWorker>();
await worker.DoWork();
if (Debugger.IsAttached && !IsRestarting)
{
var winApiAdminService = ServiceProvider.GetRequiredService<WinApiAdminService>();
winApiAdminService.StopService();
}
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
finally
{
if (mutexCreated)
{
_mutex?.Close();
}
}
}
public static void OpenDefaultUi()
{
var _ = Config.UiType switch
{
UiType.WebDefaultBrowser => OpenNewUi(),
UiType.WebEmbedded => OpenNewUiEmbedded(),
_ => OpenMainForm()
};
}
public static Task OpenMainForm()
{
_mainForm ??= ServiceProvider.GetRequiredService<MainForm>();
_mainForm.OpenForm();
return Task.CompletedTask;
}
public static bool IsMainFormOpened()
{
return _mainForm != null;
}
public static void Exit()
{
UserExit = true;
_mainForm?.Close();
if (AppContext.MainHandle != 0)
{
NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0);
}
}
public static void Restart()
{
IsRestarting = true;
_mainForm?.CloseForRestart();
_mutex?.Close();
_mutex = null;
Application.Restart();
Environment.Exit(0);
}
public static async Task OpenNewUi()
{
if (await StartUiServer())
{
await Task.Delay(500);
}
var winApi = AppContext.ServiceProvider.GetRequiredService<WinApiAdminService>();
winApi.StartProcess(GetNewUiUrl());
}
public static string GetNewUiUrl()
{
return $"http://localhost:{(Config.UiPort > 0 ? Config.UiPort : 5000)}";
}
public static async Task OpenNewUiEmbedded()
{
if (await StartUiServer())
{
await Task.Delay(500);
}
BrowserWindow.CreateAndShow(GetNewUiUrl());
}
private static bool UiServerStarted;
public static async Task<bool> StartUiServer()
{
if (UiServerStarted)
{
return false;
}
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.MainPipe;
await backgroundService.StartAsync(CancellationToken.None);
var _ = Task.Run(() => ColorControl.UI.Blazor.Start(Config));
UiServerStarted = true;
return true;
}
private static void InitLogger(bool runAsService, bool runElevated)
{
var config = new LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : "";
LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt");
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
_loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile);
_loggingRule.RuleName = "ColorControl";
// Rules for mapping loggers to targets
config.AddRule(_loggingRule);
// Apply config
LogManager.Configuration = config;
}
private static void LoadConfig()
{
ConfigFilename = Path.Combine(DataDir, "Settings.json");
try
{
if (File.Exists(ConfigFilename))
{
var data = File.ReadAllText(ConfigFilename);
Config = JsonConvert.DeserializeObject<Config>(data);
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new Config();
var minLogLevel = LogLevel.FromString(Config.LogLevel);
if (minLogLevel != LogLevel.Trace)
{
_loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
}
}
public static IServiceProvider ServiceProvider { get; private set; }
private static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
//services.AddSingleton<NvService>();
services.RegisterSharedServices();
services.AddSingleton<MainWorker>();
services.AddSingleton<NotifyIconManager>();
services.AddSingleton<LgService>();
services.AddSingleton<GameService>();
services.AddSingleton<SamsungService>();
services.AddSingleton<AmdService>();
services.AddSingleton<PowerEventDispatcher>();
services.AddSingleton<ProcessEventDispatcher>();
services.AddSingleton<WindowMessageDispatcher>();
services.AddSingleton<KeyboardShortcutDispatcher>();
services.AddSingleton<DisplayChangeEventDispatcher>();
services.AddSingleton<MainForm>();
services.AddSingleton<LogWindow>();
services.AddSingleton<BrowserWindow>();
services.AddTransient<ColorProfileWindow>();
services.AddSingleton<RestartDetector>();
services.AddSingleton<InfoPanel>();
services.AddSingleton<OptionsPanel>();
services.AddSingleton<ElevationService>();
services.AddSingleton<UpdateManager>();
services.AddSingleton<OptionsService>();
services.AddSingleton<LoggingService>();
services.AddSingleton<AmdPanel>();
services.AddSingleton<NvPanel>();
services.AddSingleton<LgPanel>();
services.AddSingleton<SamsungPanel>();
services.AddSingleton<GamePanel>();
});
}
private static async Task RunService(string[] args)
{
var startTime = DateTime.Now;
Logger.Debug("RUNNING SERVICE");
AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime);
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ColorControl Service";
})
.ConfigureServices(services =>
{
services.RegisterSharedServices();
services.AddSingleton<PowerEventDispatcher>();
services.AddHostedService<ColorControlBackgroundService>();
})
.Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
await host.RunAsync();
}
private static void RegisterSharedServices(this IServiceCollection services)
{
services.AddSingleton(AppContext);
services.AddSingleton<SessionSwitchDispatcher>();
services.AddSingleton<WinApiAdminService>();
services.AddSingleton<WinApiService>();
services.AddSingleton<WinElevatedProcessManager>();
services.AddTransient<RpcClientService>();
services.AddTransient<RpcServerService>();
services.AddSingleton<NvService>();
services.AddSingleton<WolService>();
services.AddSingleton<ColorControlBackgroundService>();
services.AddSingleton<ServiceManager>();
services.AddSingleton<ColorProfileService>();
RpcServerService.ServiceTypes.Add(typeof(NvService));
RpcServerService.ServiceTypes.Add(typeof(GameService));
RpcServerService.ServiceTypes.Add(typeof(OptionsService));
RpcServerService.ServiceTypes.Add(typeof(LoggingService));
RpcServerService.ServiceTypes.Add(typeof(SamsungService));
RpcServerService.ServiceTypes.Add(typeof(LgService));
RpcServerService.ServiceTypes.Add(typeof(AmdService));
RpcServerService.ServiceTypes.Add(typeof(ColorProfileService));
}
public static int EnumThreadWindows(IntPtr handle, IntPtr param)
{
NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero);
return 1;
}
private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception");
}
private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
GlobalHandleException(e.Exception, "Exception in thread");
}
private static void GlobalHandleException(Exception exception, string type)
{
if (UserExit)
{
return;
}
var trace = exception.ToLogString(Environment.StackTrace);
var message = $"{type}: {trace}";
Logger.Error(message);
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void SetTheme(bool useDarkMode)
{
DarkModeUtils.SetDarkMode(useDarkMode);
WinApi.RefreshImmersiveColorPolicyState();
DarkModeUtils.InitWpfTheme();
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor);
_mainForm?.SetTheme(useDarkMode);
}
internal static NotifyIcon GetNotifyIcon()
{
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
return notifyIconManager.NotifyIcon;
}
}
static class Program
{
public const string TS_TASKNAME = "ColorControl";
public static string DataDir { get; private set; }
public static string ConfigFilename { get; private set; }
public static string LogFilename { get; private set; }
public static Config Config { get; private set; }
public static GlobalContext AppContext { get; private set; }
public static bool IsRestarting { get; private set; }
public static bool UserExit = false;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
public static MainForm _mainForm;
private static LoggingRule _loggingRule;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread] // autogenerated
public static void Main(string[] args)
{
// STA
MainAsync(args).GetAwaiter().GetResult();
}
static async Task MainAsync(string[] args)
{
DataDir = Utils.GetDataPath();
var runAsService = args.Contains("--service") || WinApiService.IsAdministratorStatic() && Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true;
var runElevated = args.Contains("--elevated");
InitLogger(runAsService, runElevated);
Logger.Debug($"Using data path: {DataDir}");
//Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName);
if (runAsService)
{
await RunService(args);
return;
}
await RunApp(args);
}
static async Task RunApp(string[] args)
{
var startTime = DateTime.Now;
var currentDomain = AppDomain.CurrentDomain;
// Handler for unhandled exceptions.
currentDomain.UnhandledException += GlobalUnhandledExceptionHandler;
// Handler for exceptions in threads behind forms.
Application.ThreadException += GlobalThreadExceptionHandler;
LoadConfig();
if (Config.UseGdiScaling)
{
var result = Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
Logger.Debug($"Result of setting SetHighDpiMode: {result}");
}
var mutexId = $"Global\\{typeof(MainForm).GUID}";
var startUpParams = StartUpParams.Parse(args);
AppContext = new GlobalContext(Config, startUpParams, DataDir, _loggingRule, null, startTime, mutexId);
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
MessageForms.Title = AppContext.ApplicationTitle;
var existingProcess = Utils.GetProcessByName("ColorControl");
try
{
if (await CommandLineHandler.HandleStartupParams(startUpParams, existingProcess))
{
return;
}
}
finally
{
Utils.CloseConsole();
}
_mutex = new Mutex(true, AppContext.MutexId, out var mutexCreated);
try
{
if (!mutexCreated)
{
if (existingProcess != null && existingProcess.Threads.Count > 0)
{
var thread = existingProcess.Threads[0];
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
return;
}
MessageBox.Show("Only one instance of this program can be active.", "ColorControl");
}
else
{
_mutex.WaitOne();
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (Debugger.IsAttached)
{
//Utils.StartService();
var winApiService = ServiceProvider.GetRequiredService<WinApiService>();
if (winApiService.IsAdministrator())
{
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.ElevatedPipe;
_ = Task.Run(async () => await backgroundService.StartAsync(CancellationToken.None));
}
}
var worker = ServiceProvider.GetRequiredService<MainWorker>();
await worker.DoWork();
if (Debugger.IsAttached && !IsRestarting)
{
var winApiAdminService = ServiceProvider.GetRequiredService<WinApiAdminService>();
winApiAdminService.StopService();
}
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
finally
{
if (mutexCreated)
{
_mutex?.Close();
}
}
}
public static void OpenDefaultUi()
{
var _ = Config.UiType switch
{
UiType.WebDefaultBrowser => OpenNewUi(),
UiType.WebEmbedded => OpenNewUiEmbedded(),
_ => OpenMainForm()
};
}
public static Task OpenMainForm()
{
_mainForm ??= ServiceProvider.GetRequiredService<MainForm>();
_mainForm.OpenForm();
return Task.CompletedTask;
}
public static bool IsMainFormOpened()
{
return _mainForm != null;
}
public static void Exit()
{
UserExit = true;
_mainForm?.Close();
if (AppContext.MainHandle != 0)
{
NativeMethods.SendMessageW(AppContext.MainHandle, NativeConstants.WM_CLOSE, 0, 0);
}
}
public static void Restart()
{
IsRestarting = true;
_mainForm?.CloseForRestart();
_mutex?.Close();
_mutex = null;
Application.Restart();
Environment.Exit(0);
}
public static async Task OpenNewUi()
{
if (await StartUiServer())
{
await Task.Delay(500);
}
var winApi = AppContext.ServiceProvider.GetRequiredService<WinApiAdminService>();
winApi.StartProcess(GetNewUiUrl());
}
public static string GetNewUiUrl()
{
return $"http://localhost:{(Config.UiPort > 0 ? Config.UiPort : 5000)}";
}
public static async Task OpenNewUiEmbedded()
{
if (await StartUiServer())
{
await Task.Delay(500);
}
BrowserWindow.CreateAndShow(GetNewUiUrl());
}
private static bool UiServerStarted;
public static async Task<bool> StartUiServer()
{
if (UiServerStarted)
{
return false;
}
var backgroundService = ServiceProvider.GetRequiredService<ColorControlBackgroundService>();
backgroundService.PipeName = PipeUtils.MainPipe;
await backgroundService.StartAsync(CancellationToken.None);
var _ = Task.Run(() => ColorControl.UI.Blazor.Start(Config));
UiServerStarted = true;
return true;
}
private static void InitLogger(bool runAsService, bool runElevated)
{
var config = new LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : runElevated ? "_elevated" : "";
LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt");
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
_loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile);
_loggingRule.RuleName = "ColorControl";
// Rules for mapping loggers to targets
config.AddRule(_loggingRule);
// Apply config
LogManager.Configuration = config;
}
private static void LoadConfig()
{
ConfigFilename = Path.Combine(DataDir, "Settings.json");
try
{
if (File.Exists(ConfigFilename))
{
var data = File.ReadAllText(ConfigFilename);
Config = JsonConvert.DeserializeObject<Config>(data);
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new Config();
var minLogLevel = LogLevel.FromString(Config.LogLevel);
if (minLogLevel != LogLevel.Trace)
{
_loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal);
LogManager.ReconfigExistingLoggers();
}
}
public static IServiceProvider ServiceProvider { get; private set; }
private static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
//services.AddSingleton<NvService>();
services.RegisterSharedServices();
services.AddSingleton<MainWorker>();
services.AddSingleton<NotifyIconManager>();
services.AddSingleton<LgService>();
services.AddSingleton<GameService>();
services.AddSingleton<SamsungService>();
services.AddSingleton<AmdService>();
services.AddSingleton<PowerEventDispatcher>();
services.AddSingleton<ProcessEventDispatcher>();
services.AddSingleton<WindowMessageDispatcher>();
services.AddSingleton<KeyboardShortcutDispatcher>();
services.AddSingleton<DisplayChangeEventDispatcher>();
services.AddSingleton<MainForm>();
services.AddSingleton<LogWindow>();
services.AddSingleton<BrowserWindow>();
services.AddTransient<ColorProfileWindow>();
services.AddSingleton<RestartDetector>();
services.AddSingleton<InfoPanel>();
services.AddSingleton<OptionsPanel>();
services.AddSingleton<ElevationService>();
services.AddSingleton<UpdateManager>();
services.AddSingleton<OptionsService>();
services.AddSingleton<LoggingService>();
services.AddSingleton<AmdPanel>();
services.AddSingleton<NvPanel>();
services.AddSingleton<LgPanel>();
services.AddSingleton<SamsungPanel>();
services.AddSingleton<GamePanel>();
});
}
private static async Task RunService(string[] args)
{
var startTime = DateTime.Now;
Logger.Debug("RUNNING SERVICE");
AppContext = new GlobalContext(null, new StartUpParams(), DataDir, _loggingRule, null, startTime);
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ColorControl Service";
})
.ConfigureServices(services =>
{
services.RegisterSharedServices();
services.AddSingleton<PowerEventDispatcher>();
services.AddHostedService<ColorControlBackgroundService>();
})
.Build();
ServiceProvider = host.Services;
AppContext.ServiceProvider = ServiceProvider;
await host.RunAsync();
}
private static void RegisterSharedServices(this IServiceCollection services)
{
services.AddSingleton(AppContext);
services.AddSingleton<SessionSwitchDispatcher>();
services.AddSingleton<WinApiAdminService>();
services.AddSingleton<WinApiService>();
services.AddSingleton<WinElevatedProcessManager>();
services.AddTransient<RpcClientService>();
services.AddTransient<RpcServerService>();
services.AddSingleton<NvService>();
services.AddSingleton<WolService>();
services.AddSingleton<ColorControlBackgroundService>();
services.AddSingleton<ServiceManager>();
services.AddSingleton<ColorProfileService>();
RpcServerService.ServiceTypes.Add(typeof(NvService));
RpcServerService.ServiceTypes.Add(typeof(GameService));
RpcServerService.ServiceTypes.Add(typeof(OptionsService));
RpcServerService.ServiceTypes.Add(typeof(LoggingService));
RpcServerService.ServiceTypes.Add(typeof(SamsungService));
RpcServerService.ServiceTypes.Add(typeof(LgService));
RpcServerService.ServiceTypes.Add(typeof(AmdService));
RpcServerService.ServiceTypes.Add(typeof(ColorProfileService));
}
public static int EnumThreadWindows(IntPtr handle, IntPtr param)
{
NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero);
return 1;
}
private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
{
GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception");
}
private static void GlobalThreadExceptionHandler(object sender, ThreadExceptionEventArgs e)
{
GlobalHandleException(e.Exception, "Exception in thread");
}
private static void GlobalHandleException(Exception exception, string type)
{
if (UserExit)
{
return;
}
var trace = exception.ToLogString(Environment.StackTrace);
var message = $"{type}: {trace}";
Logger.Error(message);
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void SetTheme(bool useDarkMode)
{
DarkModeUtils.SetDarkMode(useDarkMode);
WinApi.RefreshImmersiveColorPolicyState();
DarkModeUtils.InitWpfTheme();
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
DarkModeUtils.SetContextMenuForeColor(notifyIconManager.NotifyIcon.ContextMenuStrip, FormUtils.CurrentForeColor);
_mainForm?.SetTheme(useDarkMode);
}
internal static NotifyIcon GetNotifyIcon()
{
var notifyIconManager = ServiceProvider.GetRequiredService<NotifyIconManager>();
return notifyIconManager.NotifyIcon;
}
}
}
+1
View File
@@ -0,0 +1 @@
[{"Name":"MR20GA","ImageFileName":"LG_MR20GA.jpg","Buttons":[{"Name":null,"Button":23,"Preset":null,"Top":32.506480223994814,"Left":16.7759},{"Name":null,"Button":24,"Preset":null,"Top":39.65128022399482,"Left":17.6379},{"Name":null,"Button":12,"Preset":null,"Top":13.376880223994814,"Left":19.3621},{"Name":null,"Button":13,"Preset":null,"Top":13.376880223994814,"Left":41.7759},{"Name":null,"Button":22,"Preset":null,"Top":7.038780223994813,"Left":22.8103},{"Name":null,"Button":14,"Preset":null,"Top":13.376880223994814,"Left":64.6207},{"Name":null,"Button":15,"Preset":null,"Top":17.986380223994814,"Left":19.3621},{"Name":null,"Button":16,"Preset":null,"Top":17.986380223994814,"Left":41.7759},{"Name":null,"Button":17,"Preset":null,"Top":17.986380223994814,"Left":65.0517},{"Name":null,"Button":18,"Preset":null,"Top":22.365480223994815,"Left":19.3621},{"Name":null,"Button":19,"Preset":null,"Top":22.365480223994815,"Left":41.7759},{"Name":null,"Button":20,"Preset":null,"Top":22.365480223994815,"Left":65.0517},{"Name":null,"Button":21,"Preset":null,"Top":26.629280223994815,"Left":41.7759},{"Name":null,"Button":32,"Preset":null,"Top":26.744580223994816,"Left":64.6207},{"Name":null,"Button":25,"Preset":null,"Top":32.39128022399481,"Left":42.6379},{"Name":null,"Button":0,"Preset":null,"Top":44.49128022399481,"Left":12.465499999999999},{"Name":null,"Button":26,"Preset":null,"Top":44.49128022399481,"Left":72.3793},{"Name":null,"Button":29,"Preset":null,"Top":32.506480223994814,"Left":67.2069},{"Name":null,"Button":30,"Preset":null,"Top":39.42078022399482,"Left":67.6379},{"Name":null,"Button":4,"Preset":null,"Top":48.17888022399482,"Left":42.2069},{"Name":null,"Button":5,"Preset":null,"Top":61.89228022399482,"Left":43.069},{"Name":null,"Button":6,"Preset":null,"Top":55.093180223994814,"Left":17.2069},{"Name":null,"Button":7,"Preset":null,"Top":55.20848022399482,"Left":68.931},{"Name":null,"Button":2,"Preset":null,"Top":54.97798022399481,"Left":43.069},{"Name":null,"Button":1,"Preset":null,"Top":65.0036802239948,"Left":12.8966},{"Name":null,"Button":40,"Preset":null,"Top":64.7732802239948,"Left":72.8103},{"Name":null,"Button":42,"Preset":null,"Top":70.1894802239948,"Left":15.482800000000001},{"Name":null,"Button":41,"Preset":null,"Top":69.95898022399481,"Left":71.5172},{"Name":null,"Button":35,"Preset":null,"Top":79.6389802239948,"Left":44.3621},{"Name":null,"Button":31,"Preset":null,"Top":26.744580223994816,"Left":18.931},{"Name":null,"Button":36,"Preset":null,"Top":79.86948022399481,"Left":66.3448},{"Name":null,"Button":8,"Preset":null,"Top":75.2599802239948,"Left":15.0517},{"Name":null,"Button":9,"Preset":null,"Top":74.7989802239948,"Left":33.5862},{"Name":null,"Button":10,"Preset":null,"Top":75.14468022399481,"Left":52.9828},{"Name":null,"Button":11,"Preset":null,"Top":75.02948022399481,"Left":71.0862}]},{"Name":"MR23GN","ImageFileName":"LG_MR23GN.jpg","Buttons":[{"Name":null,"Button":22,"Preset":null,"Top":3.2806882676774514,"Left":30.569000000000003},{"Name":null,"Button":12,"Preset":null,"Top":8.671588267677452,"Left":16.3448},{"Name":null,"Button":13,"Preset":null,"Top":8.671588267677452,"Left":43.931},{"Name":null,"Button":14,"Preset":null,"Top":8.671588267677452,"Left":71.0862},{"Name":null,"Button":15,"Preset":null,"Top":13.581088267677451,"Left":16.3448},{"Name":null,"Button":16,"Preset":null,"Top":13.773588267677452,"Left":43.931},{"Name":null,"Button":17,"Preset":null,"Top":13.869888267677451,"Left":71.5172},{"Name":null,"Button":18,"Preset":null,"Top":18.77938826767745,"Left":16.3448},{"Name":null,"Button":19,"Preset":null,"Top":18.68318826767745,"Left":43.931},{"Name":null,"Button":20,"Preset":null,"Top":18.68318826767745,"Left":71.0862},{"Name":null,"Button":21,"Preset":null,"Top":23.88148826767745,"Left":43.931},{"Name":null,"Button":40,"Preset":null,"Top":23.78518826767745,"Left":16.3448},{"Name":null,"Button":32,"Preset":null,"Top":24.073988267677453,"Left":70.6552},{"Name":null,"Button":23,"Preset":null,"Top":29.272288267677453,"Left":15.482800000000001},{"Name":null,"Button":24,"Preset":null,"Top":37.069788267677446,"Left":15.913799999999998},{"Name":null,"Button":25,"Preset":null,"Top":33.02668826767745,"Left":43.069},{"Name":null,"Button":29,"Preset":null,"Top":29.368588267677453,"Left":71.0862},{"Name":null,"Button":24,"Preset":null,"Top":37.16608826767745,"Left":71.0862},{"Name":null,"Button":0,"Preset":null,"Top":42.171888267677446,"Left":15.482800000000001},{"Name":null,"Button":2,"Preset":null,"Top":51.894688267677445,"Left":43.069},{"Name":null,"Button":6,"Preset":null,"Top":51.50958826767745,"Left":15.0517},{"Name":null,"Button":7,"Preset":null,"Top":51.894688267677445,"Left":70.2241},{"Name":null,"Button":4,"Preset":null,"Top":45.15608826767745,"Left":42.6379},{"Name":null,"Button":5,"Preset":null,"Top":57.86308826767745,"Left":42.6379},{"Name":null,"Button":26,"Preset":null,"Top":60.94358826767745,"Left":71.0862},{"Name":null,"Button":1,"Preset":null,"Top":60.94358826767745,"Left":15.051Line truncated
@@ -0,0 +1 @@
[{"Name":"DefaultRC","ImageFileName":"Samsung_BN59-01315B.jpg","Buttons":[{"Name":null,"Button":26,"Preset":null,"Top":10.623333333333333,"Left":16.0},{"Name":null,"Button":13,"Preset":null,"Top":4.25,"Left":66.2604},{"Name":null,"Button":27,"Preset":null,"Top":10.623333333333333,"Left":42.2249},{"Name":null,"Button":28,"Preset":null,"Top":10.623333333333333,"Left":66.2604},{"Name":null,"Button":0,"Preset":null,"Top":4.373333333333333,"Left":16.0},{"Name":null,"Button":22,"Preset":null,"Top":35.456633333333336,"Left":16.4852},{"Name":null,"Button":23,"Preset":null,"Top":43.873333333333335,"Left":16.7811},{"Name":null,"Button":24,"Preset":null,"Top":34.790033333333334,"Left":41.3373},{"Name":null,"Button":12,"Preset":null,"Top":79.87333333333333,"Left":11.1598},{"Name":null,"Button":8,"Preset":null,"Top":69.70663333333333,"Left":14.414200000000001},{"Name":null,"Button":7,"Preset":null,"Top":63.20663333333333,"Left":41.3373},{"Name":null,"Button":10,"Preset":null,"Top":49.540033333333334,"Left":41.3373},{"Name":null,"Button":9,"Preset":null,"Top":69.70663333333333,"Left":66.1893},{"Name":null,"Button":14,"Preset":null,"Top":56.206633333333336,"Left":65.5976},{"Name":null,"Button":16,"Preset":null,"Top":79.87333333333333,"Left":31.278100000000002},{"Name":null,"Button":1,"Preset":null,"Top":56.623333333333335,"Left":41.3373},{"Name":null,"Button":3,"Preset":null,"Top":63.20663333333333,"Left":15.3018},{"Name":null,"Button":4,"Preset":null,"Top":63.20663333333333,"Left":67.3728},{"Name":null,"Button":2,"Preset":null,"Top":69.70663333333333,"Left":41.3373},{"Name":null,"Button":29,"Preset":null,"Top":15.841666666666667,"Left":16.0},{"Name":null,"Button":30,"Preset":null,"Top":15.841666666666667,"Left":40.929},{"Name":null,"Button":31,"Preset":null,"Top":15.841666666666667,"Left":66.2604},{"Name":null,"Button":32,"Preset":null,"Top":21.508366666666664,"Left":16.0},{"Name":null,"Button":33,"Preset":null,"Top":21.508366666666664,"Left":41.3373},{"Name":null,"Button":34,"Preset":null,"Top":21.508366666666664,"Left":66.2604},{"Name":null,"Button":25,"Preset":null,"Top":27.008366666666664,"Left":41.3373},{"Name":null,"Button":17,"Preset":null,"Top":75.09166666666667,"Left":12.526599999999998},{"Name":null,"Button":18,"Preset":null,"Top":75.09166666666667,"Left":30.573999999999998},{"Name":null,"Button":19,"Preset":null,"Top":75.09166666666667,"Left":50.1006},{"Name":null,"Button":20,"Preset":null,"Top":75.09166666666667,"Left":68.1479},{"Name":null,"Button":11,"Preset":null,"Top":42.041965520561135,"Left":40.7273}]},{"Name":"ServiceRC","ImageFileName":"Samsung_AA81-00243A.jpg","Buttons":[{"Name":null,"Button":16,"Preset":null,"Top":71.10894129883307,"Left":22.3828},{"Name":null,"Button":45,"Preset":null,"Top":35.282841298833084,"Left":15.2984},{"Name":null,"Button":0,"Preset":null,"Top":7.5743412988330805,"Left":20.748},{"Name":null,"Button":13,"Preset":null,"Top":7.79084129883308,"Left":67.8869},{"Name":null,"Button":15,"Preset":null,"Top":17.965041298833082,"Left":73.3365},{"Name":null,"Button":9,"Preset":null,"Top":35.39104129883308,"Left":74.9714},{"Name":null,"Button":7,"Preset":null,"Top":27.05684129883308,"Left":44.4537},{"Name":null,"Button":1,"Preset":null,"Top":19.480341298833082,"Left":44.9986},{"Name":null,"Button":3,"Preset":null,"Top":26.94864129883308,"Left":25.6526},{"Name":null,"Button":4,"Preset":null,"Top":26.94864129883308,"Left":64.3447},{"Name":null,"Button":2,"Preset":null,"Top":34.41694129883308,"Left":45.5436},{"Name":null,"Button":12,"Preset":null,"Top":17.423841298833082,"Left":15.8433},{"Name":null,"Button":26,"Preset":null,"Top":51.30174129883308,"Left":22.3828},{"Name":null,"Button":27,"Preset":null,"Top":50.97704129883308,"Left":46.361},{"Name":null,"Button":28,"Preset":null,"Top":51.08524129883308,"Left":68.9768},{"Name":null,"Button":29,"Preset":null,"Top":57.90414129883308,"Left":22.6553},{"Name":null,"Button":30,"Preset":null,"Top":57.68764129883308,"Left":45.8161},{"Name":null,"Button":31,"Preset":null,"Top":57.79594129883308,"Left":68.7044},{"Name":null,"Button":32,"Preset":null,"Top":64.61474129883307,"Left":22.1104},{"Name":null,"Button":33,"Preset":null,"Top":64.50654129883307,"Left":45.2711},{"Name":null,"Button":34,"Preset":null,"Top":64.29004129883307,"Left":68.9768},{"Name":null,"Button":25,"Preset":null,"Top":70.89244129883308,"Left":45.8161}]}]
@@ -307,6 +307,7 @@ public class ColorProfileService
ColorProfiles = colorProfiles,
DefaultSdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, false),
DefaultHdrColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, true),
UsePerUserSettings = CCD.GetUsePerUserDisplayProfiles(d.DisplayName)
};
})
.Where(d => !onlyHdrEnabled.HasValue || onlyHdrEnabled == d.IsHdrSupportedAndEnabled)
@@ -168,7 +168,7 @@ public class NotifyIconManager
_lgTrayMenu.Visible = _serviceManager.LgService != null;
if (_lgTrayMenu.Visible)
{
var presets = _serviceManager.LgService.GetPresets().Where(x => !string.IsNullOrEmpty(x.appId) || x.steps.Any());
var presets = _serviceManager.LgService.GetPresets().Where(x => !string.IsNullOrEmpty(x.appId) || x.Steps.Any());
_lgTrayMenu.DropDownItems.Clear();
+2 -4
View File
@@ -245,6 +245,7 @@ namespace ColorControl.Services.Common
// Hack to convert incorrect triggers
json = json.Replace(@"""Triggers"":0", @"""Triggers"":[]");
json = json.Replace(@"""steps"":[", @"""Steps"":[");
_presets = JsonConvert.DeserializeObject<List<T>>(json, _jsonConverters.ToArray());
@@ -281,10 +282,7 @@ namespace ColorControl.Services.Common
Logger.Error($"General error while loading presets, reverting to empty list: {ex.Message}");
_loadPresetsError = ex.Message;
}
if (_presets == null)
{
_presets = new List<T>();
}
_presets ??= [];
AfterPresetsLoaded();
}
@@ -89,7 +89,9 @@ namespace ColorControl.Services.GameLauncher
private Task<bool> HandleStartedProcess(Process process)
{
GamePreset preset;
GamePreset preset = null;
var presets = _presets.Where(p => p.AutoSettings.AllowAutoApply);
if (_winApiService.IsAdministrator())
{
@@ -102,15 +104,16 @@ namespace ColorControl.Services.GameLauncher
var fileName = mainModule.FileName;
preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
}
else
{
var name = process.ProcessName.Replace("-Win64-Shipping", "");
preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
preset = presets.FirstOrDefault(p => !Path.GetDirectoryName(p.Path).Equals("") && fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
}
if (preset == null || !preset.AutoSettings.AllowAutoApply)
if (preset == null)
{
var name = process.ProcessName.Replace("-Win64-Shipping", "");
preset = presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
}
if (preset == null)
{
return Task.FromResult(false);
}
+6 -6
View File
@@ -275,10 +275,10 @@ namespace ColorControl.Services.LG
AddInvokableAction("turnScreenOff", TurnScreenOffAction);
AddInvokableAction("turnScreenOn", TurnScreenOnAction);
AddInternalPresetAction(new LgPreset("InStart", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "inStart" }));
AddInternalPresetAction(new LgPreset("EzAdjust", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "ezAdjust" }));
AddInternalPresetAction(LgPresets.InStartPreset);
AddInternalPresetAction(LgPresets.EzAdjustPreset);
//AddInternalPresetAction(new LgPreset("PictureCheck", "com.webos.app.factorywin", null, new { id = "executeFactory", irKey = "pCheck" }));
AddInternalPresetAction(new LgPreset("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true }));
AddInternalPresetAction(LgPresets.SoftwareUpdatePreset);
AddSetDeviceConfigAction("HDMI_1_icon", typeof(HdmiIcon), "HDMI 1 icon");
AddSetDeviceConfigAction("HDMI_2_icon", typeof(HdmiIcon), "HDMI 2 icon");
@@ -707,7 +707,7 @@ namespace ColorControl.Services.LG
{
var hasApp = !string.IsNullOrEmpty(preset.appId);
var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
var hasWOL = preset.Steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
if (hasWOL)
{
@@ -770,7 +770,7 @@ namespace ColorControl.Services.LG
}
}
if (preset.steps.Any())
if (preset.Steps.Any())
{
if (hasApp)
{
@@ -803,7 +803,7 @@ namespace ColorControl.Services.LG
{
LgWebOsMouseService mouse = null;
foreach (var step in preset.steps)
foreach (var step in preset.Steps)
{
var keySpec = step.Split(':');
+2 -2
View File
@@ -184,7 +184,7 @@ namespace ColorControl.Services.LG
var lgApps = _lgService?.GetApps();
cbxLgApps.SelectedIndex = lgApps == null ? -1 : lgApps.FindIndex(x => x.appId.Equals(preset.appId));
edtShortcutLg.Text = preset.shortcut;
edtStepsLg.Text = preset.steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
edtStepsLg.Text = preset.Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
var index = -1;
@@ -291,7 +291,7 @@ namespace ColorControl.Services.LG
var text = edtStepsLg.Text;
Utils.ParseWords(preset.steps, text);
Utils.ParseWords(preset.Steps, text);
AddOrUpdateItemLg();
+101 -10
View File
@@ -26,7 +26,8 @@ namespace ColorControl.Services.LG
{
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static string LgListAppsJson = "listApps.json";
public const string LgListAppsJson = "listApps.json";
public const string RemotesFilename = "LgRemotes.json";
public override string ServiceName => "LG";
@@ -67,6 +68,9 @@ namespace ColorControl.Services.LG
private readonly WinApiAdminService _winApiAdminService;
private LgGameBar _gameBarForm;
private List<LgRcDto> _remotes;
private string _remotesFilename;
public ProcessChangedEventArgs CurrentProcessEvent => _processEventDispatcher?.MonitorContext;
public static readonly int SHORTCUTID_LGQA = -202;
@@ -99,6 +103,75 @@ namespace ColorControl.Services.LG
LoadConfig();
LoadPresets();
LoadRemoteControlButtons();
LoadRemotes();
}
private void LoadRemotes()
{
_remotesFilename = Path.Combine(_dataPath, RemotesFilename);
try
{
var remotesExists = File.Exists(_remotesFilename);
if (remotesExists)
{
try
{
var json = File.ReadAllText(_remotesFilename);
_remotes = JsonConvert.DeserializeObject<List<LgRcDto>>(json);
}
catch (Exception ex1)
{
Logger.Error($"Error while loading remotes, reverting to default remotes: {ex1.Message}");
}
}
}
catch (Exception ex)
{
Logger.Error($"General error while loading remotes, reverting to empty list: {ex.Message}");
}
_remotes ??= [];
var defaultRemotes = GetDefaultRemotes();
_remotes.AddRange(defaultRemotes.Where(r => !_remotes.Any(sr => sr.Name == r.Name)));
}
protected static List<LgRcDto> GetDefaultRemotes()
{
var json = Utils.GetResourceFile("LgRemotes.json");
return JsonConvert.DeserializeObject<List<LgRcDto>>(json);
}
public List<LgRcDto> GetRemotes()
{
return _remotes;
}
public bool UpdateRemote(LgRcDto remoteSpec)
{
var remote = _remotes.FirstOrDefault(r => r.Name == remoteSpec.Name);
if (remote != null)
{
remote.Update(remoteSpec);
}
else
{
_remotes.Add(remoteSpec);
}
GlobalSave();
return true;
}
public void DeleteRemote(string remoteName)
{
_remotes.RemoveAll(r => r.Name == remoteName);
}
private string LgPreset_GetAppName(string appId)
@@ -175,6 +248,7 @@ namespace ColorControl.Services.LG
SaveConfig();
SavePresets();
SaveRemoteControlButtons();
Utils.WriteObject(_remotesFilename, _remotes);
SendConfigToService();
}
@@ -297,7 +371,7 @@ namespace ColorControl.Services.LG
preset.shortcut = shortcut;
if (appId == null)
{
preset.steps.Add(step == null ? name : step);
preset.Steps.Add(step == null ? name : step);
}
return preset;
@@ -323,7 +397,7 @@ namespace ColorControl.Services.LG
{
var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress);
var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
var pnpDevices = Utils.GetPnpDevices(Config.DeviceSearchKey);
var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new LgDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList();
var autoIpAddresses = pnpDevices.Select(d => d.IpAddress);
@@ -370,13 +444,11 @@ namespace ColorControl.Services.LG
}
else
{
if (afterStartUp)
var connectTask = SelectedDevice.Connect(afterStartUp ? 3 : 1);
if (!afterStartUp)
{
var _ = SelectedDevice.Connect();
}
else
{
return await SelectedDevice.Connect(1);
return await connectTask;
}
}
}
@@ -1267,9 +1339,28 @@ namespace ColorControl.Services.LG
return true;
}
public bool RemoveDevice(LgDeviceDto deviceDto)
{
if (deviceDto == null)
{
return false;
}
var device = MatchDevice(deviceDto);
if (device?.IsCustom != true)
{
return false;
}
RemoveCustomDevice(device);
return true;
}
private LgDevice MatchDevice(LgDeviceDto deviceDto)
{
return Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress);
return Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress && d.IpAddress == deviceDto.IpAddress) ??
Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress);
}
}
}
@@ -69,7 +69,7 @@ namespace ColorControl.Services.LG
if (preset != null)
{
if (preset.steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active)
if (preset.Steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active)
{
Logger.Debug("Executing WOL instead of POWER");
+91 -18
View File
@@ -31,6 +31,7 @@ using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using static ColorControl.Shared.Common.NestedItemsBuilder;
using static ColorControl.Shared.Contracts.NVIDIA.NvHdrSettings;
namespace ColorControl.Services.NVIDIA
@@ -174,10 +175,10 @@ namespace ColorControl.Services.NVIDIA
MainWindow.CreateAndShow(false);
}
// TODO: implement later
//_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged);
//_powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Resume, PowerModeResume);
//_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged);
_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Suspend, PowerModeChanged);
_powerEventDispatcher.RegisterAsyncEventHandler(PowerEventDispatcher.Event_Resume, PowerModeResume);
_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Startup, PowerModeChanged);
_powerEventDispatcher.RegisterEventHandler(PowerEventDispatcher.Event_Shutdown, PowerModeChanged);
}
public override List<string> GetInfo()
@@ -421,6 +422,11 @@ namespace ColorControl.Services.NVIDIA
}
}
if (preset.DpiScaling.ApplyScaling)
{
CCD.SetDpiScaling(preset.DpiScaling.Percentage, display.Name);
}
if (preset.applyOther)
{
if (!string.IsNullOrWhiteSpace(preset.ColorProfileSettings.ProfileName))
@@ -468,6 +474,8 @@ namespace ColorControl.Services.NVIDIA
result = ApplyOverclocking(preset.ocSettings);
}
await ExecuteStepsAsync(preset, _globalContext);
//SetMonitorScaling();
//CCD.SetScaling(100);
@@ -1442,6 +1450,8 @@ namespace ColorControl.Services.NVIDIA
{
var list = new List<NvPreset>();
RefreshProfileSettings();
var displayInfos = GetSimpleDisplayInfos();
foreach (var displayInfo in displayInfos)
@@ -1493,6 +1503,12 @@ namespace ColorControl.Services.NVIDIA
preset.ColorProfileSettings.ProfileName = CCD.GetDisplayDefaultColorProfile(display.Name, preset.HDREnabled);
preset.DisplayConfig = CCD.GetDisplayConfig(display.Name);
preset.DpiScaling.Percentage = CCD.GetDpiScalingInPercentage(display.Name);
var novideoSettings = MainWindow.GetSettings(preset.DisplayId);
preset.NovideoSettings.ApplyClamp = novideoSettings.clamped;
preset.NovideoSettings.ColorSpace = (NovideoColorSpace)novideoSettings.targetColorSpace;
var ditherInfo = GetDithering(display);
@@ -1590,6 +1606,7 @@ namespace ColorControl.Services.NVIDIA
return new NvSettingItemDto
{
Name = settingMeta.SettingName,
GroupName = s.GroupName,
IsApiExposed = s.IsApiExposed,
IsSettingHidden = s.IsSettingHidden,
@@ -1608,40 +1625,39 @@ namespace ColorControl.Services.NVIDIA
}).ToList();
}
public List<string> GetDriverProfileNames(bool onlyFoundPrograms = false)
public List<NvProfile> GetDriverProfileNames(bool onlyFoundPrograms = false)
{
if (onlyFoundPrograms)
{
var profileNames = new List<string>();
var profiles = new List<NvProfile>();
// First check game service for programs
profileNames.AddRange(GetProfileNamesOfGamePresets());
profiles.AddRange(GetProfileNamesOfGamePresets());
// Other ways: check MRU in registry:
// - Computer\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU\exe
// - Computer\HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\Shell\MuiCache
profileNames.AddRange(GetProfileNamesOfMru());
profiles.AddRange(GetProfileNamesOfMru());
profileNames = profileNames.Distinct().ToList();
profileNames.Sort();
profiles = profiles.DistinctBy(p => p.Name).OrderBy(p => p.Name).ToList();
profileNames.Insert(0, _baseProfileName);
profiles.Insert(0, new NvProfile { IsBase = true, Name = _baseProfileName });
return profileNames;
return profiles;
}
var baseProfileName = "";
var allProfiles = DrsServiceLocator.SettingService.GetProfileNames(ref baseProfileName).Where(s => !s.StartsWith("0x")).OrderBy(s => s).ToList();
return allProfiles;
return allProfiles.Select(p => new NvProfile { Name = p }).ToList();
}
private List<string> GetProfileNamesOfGamePresets()
private List<NvProfile> GetProfileNamesOfGamePresets()
{
if (_serviceManager.GameService == null)
{
return new List<string>();
return [];
}
var gamePresets = _serviceManager.GameService.GetPresets();
@@ -1652,11 +1668,11 @@ namespace ColorControl.Services.NVIDIA
return profiles;
}
private List<string> GetProfileNamesOfMru()
private List<NvProfile> GetProfileNamesOfMru()
{
if (_serviceManager.GameService == null)
{
return new List<string>();
return [];
}
var apps = _serviceManager.GameService.GetMruApps();
@@ -1853,6 +1869,21 @@ namespace ColorControl.Services.NVIDIA
return true;
}
public List<NestedItem> GetInfoItems()
{
var items = new List<NestedItem>();
var displays = Display.GetDisplays();
for (var i = 0; i < displays.Length; i++)
{
var display = displays[i];
var item = NestedItemsBuilder.CreateTree(display, $"Display[{i}]");
items.Add(item);
}
return items;
}
internal Display ResolveDisplay(NvPreset preset)
{
if (preset.Display == null)
@@ -1868,7 +1899,7 @@ namespace ColorControl.Services.NVIDIA
Logger.Debug($"PowerModeChanged: {e.State}");
// Wait
await Task.Delay(30000);
await Task.Delay(5000);
await ExecutePresetsForEvent(PresetTriggerType.Resume);
}
@@ -1884,6 +1915,11 @@ namespace ColorControl.Services.NVIDIA
_ = ExecutePresetsForEvent(PresetTriggerType.Standby);
break;
}
case PowerOnOffState.StartUp:
{
_ = ExecutePresetsForEvent(PresetTriggerType.Startup);
break;
}
case PowerOnOffState.ShutDown:
{
_ = ExecutePresetsForEvent(PresetTriggerType.Shutdown);
@@ -1937,5 +1973,42 @@ namespace ColorControl.Services.NVIDIA
return string.Join(useNewLines ? "\r\n" : ", ", values);
}
private async Task ExecuteStepsAsync(NvPreset preset, Shared.Common.GlobalContext appContext)
{
foreach (var step in preset.Steps)
{
var keySpec = step.Split(':');
var delay = 100;
var key = step;
if (keySpec.Length >= 2)
{
delay = Utils.ParseInt(keySpec[1]);
if (delay > 0)
{
key = keySpec[0];
}
}
var index = key.IndexOf("(");
string[] parameters = null;
if (index > -1)
{
var keyValue = key.Split('(');
key = keyValue[0];
parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';');
}
if (parameters != null)
{
await _serviceManager.HandleExternalServiceAsync(key, parameters);
}
if (delay > 0)
{
await Task.Delay(delay);
}
}
}
}
}
@@ -1,11 +1,9 @@
using Newtonsoft.Json;
using System;
using System.Net.Security;
using System;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ColorControl.Services.Samsung
{
@@ -47,7 +45,10 @@ namespace ColorControl.Services.Samsung
{
Logger.Debug($"Connecting to {uri}");
await _clientWebSocket.ConnectAsync(uri, CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(connectTimeout));
var source = new CancellationTokenSource();
source.CancelAfter(connectTimeout);
await _clientWebSocket.ConnectAsync(uri, source.Token);
Logger.Debug($"Connected to {uri}");
ConnectionClosed = false;
@@ -74,11 +75,9 @@ namespace ColorControl.Services.Samsung
private void CreateWebSocket()
{
_clientWebSocket = new ClientWebSocket();
_clientWebSocket.Options.RemoteCertificateValidationCallback += CertificationValidationCallback;
_clientWebSocket.Options.RemoteCertificateValidationCallback += (_, _, _, _) => true;
}
private bool CertificationValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) => true;
public async Task SendMessageAsync(string message, bool reconnect = true)
{
try
+107 -3
View File
@@ -44,8 +44,13 @@ namespace ColorControl.Services.Samsung
private List<SamsungApp> _samsungApps = new List<SamsungApp>();
private Shared.Common.GlobalContext _appContext;
public const string RemotesFilename = "SamsungRemotes.json";
public List<SamsungDevice> Devices { get; private set; }
private List<SamsungRcDto> _samsungRemotes;
private string _remotesFilename;
public static readonly int SHORTCUTID_SAMSUNGQA = -204;
public SamsungService(
@@ -71,6 +76,75 @@ namespace ColorControl.Services.Samsung
LoadConfig();
LoadPresets();
LoadRemotes();
}
private void LoadRemotes()
{
_remotesFilename = Path.Combine(_dataPath, RemotesFilename);
try
{
var remotesExists = File.Exists(_remotesFilename);
if (remotesExists)
{
try
{
var json = File.ReadAllText(_remotesFilename);
_samsungRemotes = JsonConvert.DeserializeObject<List<SamsungRcDto>>(json);
}
catch (Exception ex1)
{
Logger.Error($"Error while loading remotes, reverting to default remotes: {ex1.Message}");
}
}
}
catch (Exception ex)
{
Logger.Error($"General error while loading remotes, reverting to empty list: {ex.Message}");
}
_samsungRemotes ??= [];
var defaultRemotes = GetDefaultRemotes();
_samsungRemotes.AddRange(defaultRemotes.Where(r => !_samsungRemotes.Any(sr => sr.Name == r.Name)));
}
protected static List<SamsungRcDto> GetDefaultRemotes()
{
var json = Utils.GetResourceFile("SamsungRemotes.json");
return JsonConvert.DeserializeObject<List<SamsungRcDto>>(json);
}
public List<SamsungRcDto> GetRemotes()
{
return _samsungRemotes;
}
public bool UpdateRemote(SamsungRcDto remoteSpec)
{
var remote = _samsungRemotes.FirstOrDefault(r => r.Name == remoteSpec.Name);
if (remote != null)
{
remote.Update(remoteSpec);
}
else
{
_samsungRemotes.Add(remoteSpec);
}
GlobalSave();
return true;
}
public void DeleteRemote(string remoteName)
{
_samsungRemotes.RemoveAll(r => r.Name == remoteName);
}
private string SamsungPreset_GetDeviceName(string macAddress)
@@ -90,7 +164,7 @@ namespace ColorControl.Services.Samsung
{
var samsungService = Program.ServiceProvider.GetRequiredService<SamsungService>();
await samsungService.RefreshDevices(afterStartUp: true);
await samsungService.RefreshDevices(connect: false, afterStartUp: true);
var result = await samsungService.ApplyPreset(presetName);
@@ -265,7 +339,7 @@ namespace ColorControl.Services.Samsung
{
var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress);
var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey, Utils.DEV_CLASS_DISPLAY_TV_LCD);
var pnpDevices = Utils.GetPnpDevices(Config.DeviceSearchKey, Utils.DEV_CLASS_DISPLAY_TV_LCD);
var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new SamsungDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList();
var autoIpAddresses = pnpDevices.Select(d => d.IpAddress);
@@ -308,7 +382,12 @@ namespace ColorControl.Services.Samsung
}
else
{
var _ = SelectedDevice.ConnectAsync();
var connectTask = SelectedDevice.ConnectAsync();
if (!afterStartUp)
{
return await connectTask;
}
}
}
@@ -341,6 +420,7 @@ namespace ColorControl.Services.Samsung
{
SavePresets();
SaveConfig();
Utils.WriteObject(_remotesFilename, _samsungRemotes);
}
public SamsungDevice GetPresetDevice(SamsungPreset preset)
@@ -966,5 +1046,29 @@ namespace ColorControl.Services.Samsung
return true;
}
public bool RemoveDevice(SamsungDeviceDto deviceDto)
{
if (deviceDto == null)
{
return false;
}
var device = MatchDevice(deviceDto);
if (device?.IsCustom != true)
{
return false;
}
RemoveCustomDevice(device);
return true;
}
private SamsungDevice MatchDevice(SamsungDeviceDto deviceDto)
{
return Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress && d.IpAddress == deviceDto.IpAddress) ??
Devices?.FirstOrDefault(d => string.IsNullOrEmpty(deviceDto.MacAddress) ? d.IpAddress == deviceDto.IpAddress : d.MacAddress == deviceDto.MacAddress);
}
}
}
+3 -1
View File
@@ -435,7 +435,9 @@ namespace LgTv
var lunauri = "luna://com.webos.settingsservice/setSystemSettings";
var json = @"{ ""category"": """ + category + @""", ""settings"": { """ + key + @""": " + jsonValue + @" } }";
var extra = key == "truMotionMode" ? @", ""current_app"": true " : "";
var json = @"{ ""category"": """ + category + @""", ""settings"": { """ + key + @""": " + jsonValue + @" }" + extra + " }";
var @params = JObject.Parse(json);
+1147 -1137
View File
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -68,7 +68,11 @@ public class MHC2Wrapper
SDRTransferFunction = deviceContext.ExtraInfoTag?.SDRTransferFunction ?? SDRTransferFunction.PurePower,
SDRBrightnessBoost = deviceContext.ExtraInfoTag?.SDRBrightnessBoost ?? 0,
ShadowDetailBoost = deviceContext.ExtraInfoTag?.ShadowDetailBoost ?? 0,
Gamma = deviceContext.ExtraInfoTag?.Gamma ?? 2.2
Gamma = deviceContext.ExtraInfoTag?.Gamma ?? 2.2,
ToneMappingFromLuminance = deviceContext.ExtraInfoTag?.ToneMappingFromLuminance ?? 400,
ToneMappingToLuminance = deviceContext.ExtraInfoTag?.ToneMappingToLuminance ?? 400,
HdrGammaMultiplier = deviceContext.ExtraInfoTag?.HdrGammaMultiplier ?? 1,
HdrBrightnessMultiplier = deviceContext.ExtraInfoTag?.HdrBrightnessMultiplier ?? 1
};
}
}
+14 -6
View File
@@ -1,4 +1,5 @@
using nspector.Common.Helper;
using ColorControl.Shared.Contracts.NVIDIA;
using nspector.Common.Helper;
using nspector.Common.Meta;
using nspector.Native.NVAPI2;
using System.Diagnostics;
@@ -227,9 +228,9 @@ namespace nspector.Common
return name;
}
public List<string> GetProfileNamesByApps(List<string> appNames)
public List<NvProfile> GetProfileNamesByApps(List<string> appNames)
{
var profileNames = new List<string>();
var profiles = new List<NvProfile>();
DrsSession((hSession) =>
{
@@ -238,15 +239,22 @@ namespace nspector.Common
{
var apps = GetProfileApplications(hSession, hProfile, true);
if (apps?.Any(a => appNames.Any(pn => a.appName.Equals(pn, StringComparison.OrdinalIgnoreCase))) == true)
if (apps?.Any(a => appNames.Any(pn => a.appName.Contains(pn, StringComparison.OrdinalIgnoreCase))) == true)
{
var profile = GetProfileInfo(hSession, hProfile);
profileNames.Add(profile.profileName);
var nvProfile = new NvProfile
{
Name = profile.profileName,
Apps = apps.Select(a => a.appName).ToList()
};
profiles.Add(nvProfile);
}
}
});
return profileNames;
return profiles;
}
public void CreateProfile(string profileName, string? applicationName = null)
+4
View File
@@ -17,6 +17,10 @@
<PackageReference Include="System.Resources.Extensions" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="frmBitEditor.cs">
<SubType>Form</SubType>
+5 -1
View File
@@ -7931,7 +7931,11 @@
<UserfriendlyName>Preset J</UserfriendlyName>
<HexValue>0x0000000A</HexValue>
</CustomSettingValue>
</SettingValues>
<CustomSettingValue>
<UserfriendlyName>Preset K</UserfriendlyName>
<HexValue>0x0000000B</HexValue>
</CustomSettingValue>
</SettingValues>
<SettingMasks />
</CustomSetting>
-1
View File
@@ -17,7 +17,6 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<Title>NvAPIWrapper (for NVAPI 410)</Title>
<PackageId>NvAPIWrapper.Net</PackageId>
<AssemblyOriginatorKeyFile>OpenSourceStrongNameSignKey.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>..\Debug</OutputPath>
+3 -3
View File
@@ -4,9 +4,9 @@ Easily change NVIDIA and AMD display settings, control LG and Samsung tv's, Game
## Installation
Just extract the .zip file in a folder of your preference. Run ColorControl.exe to start the application.
Requires .NET 8 with the following runtimes:
* .NET Desktop Runtime 8
* .NET ASP.NET Core Runtime 8
Requires .NET 9 with the following runtimes:
* .NET Desktop Runtime 9
* .NET ASP.NET Core Runtime 9
ColorControl stores its settings in the following folder:
* C:\Users\username\AppData\Roaming\Maassoft\ColorControl
+103
View File
@@ -0,0 +1,103 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
namespace ColorControl.Shared.Common;
public class NestedItemsBuilder
{
public class NestedItem
{
public bool Expanded { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public List<NestedItem> NestedItems { get; set; } = [];
}
public static NestedItem CreateTree(object obj, string text)
{
var serialized = JsonConvert.SerializeObject(obj, Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Error = delegate (object sender, ErrorEventArgs args)
{
//errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = [new Newtonsoft.Json.Converters.StringEnumConverter()]
});
var dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(serialized);
var root = new NestedItem
{
Value = text
};
BuildTree(dic, root);
return root;
}
private static void BuildTree(object item, NestedItem node)
{
if (item is KeyValuePair<string, object> kv)
{
var keyValueNode = new NestedItem();
keyValueNode.Name = kv.Key;
keyValueNode.Value = kv.Key + ": " + GetValueAsString(kv.Value);
node.NestedItems.Add(keyValueNode);
BuildTree(kv.Value, keyValueNode);
}
else if (item.GetType() == typeof(JArray))
{
var list = (JArray)item;
int index = 0;
foreach (object value in list)
{
var arrayItem = new NestedItem();
arrayItem.Name = $"[{index}]";
arrayItem.Value = $"[{index}]";
node.NestedItems.Add(arrayItem);
BuildTree(value, arrayItem);
index++;
}
}
else if (item is Dictionary<string, object> dictionary)
{
foreach (KeyValuePair<string, object> d in dictionary)
{
BuildTree(d, node);
}
}
else if (item.GetType() == typeof(JObject))
{
var jobject = item as JObject;
foreach (var property in jobject.Properties())
{
BuildTree(new KeyValuePair<string, object>(property.Name, property.Value), node);
}
}
}
private static string GetValueAsString(object value)
{
if (value == null)
return "null";
var type = value.GetType();
if (type.IsArray)
{
return "[]";
}
if (value.GetType() == typeof(JArray))
{
var arr = value as JArray;
return $"[{arr.Count}]";
}
if (type.IsGenericType || value.GetType() == typeof(JObject))
{
return "{}";
}
return value.ToString();
}
}
+2 -2
View File
@@ -138,12 +138,12 @@ The best and suggested method to provide this is via a Windows Service. Only whe
return values;
}
public static async Task<List<PnpDev>> GetPnpDevices(string deviceName, string category = null)
public static List<PnpDev> GetPnpDevices(string deviceName, string category = null)
{
var devices = new List<PnpDev>();
var queryString = $"SELECT * FROM Win32_PnPEntity WHERE PNPClass='DigitalMediaDevices' AND NAME LIKE '%{deviceName}%'";
using ManagementObjectSearcher mos = new ManagementObjectSearcher(queryString);
using var mos = new ManagementObjectSearcher(queryString);
foreach (var mo in mos.Get().OfType<ManagementObject>())
{
+31
View File
@@ -0,0 +1,31 @@
namespace ColorControl.Shared.Contracts.Base;
public abstract class BaseDeviceDto
{
public string Name { get; set; }
public string IpAddress { get; set; }
public string MacAddress { get; set; }
public bool IsCustom { get; set; }
public string Token { get; set; }
public bool IsDummy { get; set; }
public bool PoweredOn { get; set; }
public DateTimeOffset PoweredOffAt { get; set; }
public bool IsConnected { get; set; }
public bool IsSelected { get; set; }
public override string ToString()
{
if (IpAddress != null)
{
return $"{Name} ({IpAddress})";
}
return Name;
}
}
+1
View File
@@ -29,6 +29,7 @@ public class DisplayConfig
RefreshRate = new Rational(displayConfig.RefreshRate.Numerator, displayConfig.RefreshRate.Denominator);
Scaling = displayConfig.Scaling;
Rotation = displayConfig.Rotation;
IsPrimary = displayConfig.IsPrimary;
}
public string GetResolutionDesc()
@@ -63,6 +63,7 @@ public class DisplayInfoDto
public string DeviceName { get; set; }
public string DevicePath { get; set; }
public string DisplayId { get; set; }
public bool UsePerUserSettings { get; set; }
public bool IsAdvancedColorSupported { get; set; }
public bool IsAdvancedColorEnabled { get; set; }
+23
View File
@@ -0,0 +1,23 @@
namespace ColorControl.Shared.Contracts;
public class DpiScaling
{
public bool ApplyScaling { get; set; }
public uint Percentage { get; set; }
public DpiScaling()
{
Percentage = 100;
}
public DpiScaling(DpiScaling dpiScaling)
{
ApplyScaling = dpiScaling.ApplyScaling;
Percentage = dpiScaling.Percentage;
}
public override string ToString()
{
return $"{Percentage:0}%";
}
}
+104 -97
View File
@@ -6,133 +6,140 @@ namespace ColorControl.Shared.Contracts.Game;
public enum GameStepType
{
[Description("Pre-launch steps")]
PreLaunch = 0,
[Description("Post-launch steps")]
PostLaunch = 1,
[Description("Finalize steps")]
Finalize = 2,
[Description("Pre-launch steps")]
PreLaunch = 0,
[Description("Post-launch steps")]
PostLaunch = 1,
[Description("Finalize steps")]
Finalize = 2,
}
public enum GamePriorityClass
{
Idle = NativeConstants.IDLE_PRIORITY_CLASS,
BelowNormal = NativeConstants.BELOW_NORMAL_PRIORITY_CLASS,
Normal = NativeConstants.NORMAL_PRIORITY_CLASS,
AboveNormal = NativeConstants.ABOVE_NORMAL_PRIORITY_CLASS,
High = NativeConstants.HIGH_PRIORITY_CLASS,
RealTime = NativeConstants.REALTIME_PRIORITY_CLASS
Idle = NativeConstants.IDLE_PRIORITY_CLASS,
BelowNormal = NativeConstants.BELOW_NORMAL_PRIORITY_CLASS,
Normal = NativeConstants.NORMAL_PRIORITY_CLASS,
AboveNormal = NativeConstants.ABOVE_NORMAL_PRIORITY_CLASS,
High = NativeConstants.HIGH_PRIORITY_CLASS,
RealTime = NativeConstants.REALTIME_PRIORITY_CLASS
}
public enum ProcessAutoAction
{
None = 0,
Suspend = 1,
Restart = 2
None = 0,
Suspend = 1,
Restart = 2
}
public class AutoSettings
{
public bool AllowAutoApply { get; set; }
public ProcessAutoAction ProcessAutoAction { get; set; }
public bool AllowAutoApply { get; set; }
public ProcessAutoAction ProcessAutoAction { get; set; }
public void Update(AutoSettings autoSettings)
{
AllowAutoApply = autoSettings.AllowAutoApply;
ProcessAutoAction = autoSettings.ProcessAutoAction;
}
}
public class GamePreset : PresetBase
{
public string Path { get; set; }
public string Parameters { get; set; }
public bool RunAsAdministrator { get; set; }
public List<string> PreLaunchSteps { get; set; }
public List<string> PostLaunchSteps { get; set; }
public List<string> FinalizeSteps { get; set; }
public uint ProcessAffinityMask { get; set; }
public uint ProcessPriorityClass { get; set; }
public string Path { get; set; }
public string Parameters { get; set; }
public bool RunAsAdministrator { get; set; }
public List<string> PreLaunchSteps { get; set; }
public List<string> PostLaunchSteps { get; set; }
public List<string> FinalizeSteps { get; set; }
public uint ProcessAffinityMask { get; set; }
public uint ProcessPriorityClass { get; set; }
[JsonIgnore]
public GamePriorityClass GameProcessPriorityClass
{
get
{
return (GamePriorityClass)ProcessPriorityClass;
}
set
{
ProcessPriorityClass = (uint)value;
}
}
[JsonIgnore]
public GamePriorityClass GameProcessPriorityClass
{
get
{
return (GamePriorityClass)ProcessPriorityClass;
}
set
{
ProcessPriorityClass = (uint)value;
}
}
public AutoSettings AutoSettings { get; set; }
public AutoSettings AutoSettings { get; set; }
public GamePreset() : base()
{
PreLaunchSteps = new List<string>();
PostLaunchSteps = new List<string>();
FinalizeSteps = new List<string>();
AutoSettings = new AutoSettings();
}
public GamePreset() : base()
{
PreLaunchSteps = new List<string>();
PostLaunchSteps = new List<string>();
FinalizeSteps = new List<string>();
AutoSettings = new AutoSettings();
}
public GamePreset(GamePreset preset) : this()
{
id = GetNewId();
public GamePreset(GamePreset preset) : this()
{
id = GetNewId();
Update(preset);
}
Update(preset);
}
public void Update(GamePreset preset)
{
name = preset.name;
ShowInQuickAccess = preset.ShowInQuickAccess;
Path = preset.Path;
Parameters = preset.Parameters;
RunAsAdministrator = preset.RunAsAdministrator;
ProcessAffinityMask = preset.ProcessAffinityMask;
ProcessPriorityClass = preset.ProcessPriorityClass;
PreLaunchSteps.Clear();
PreLaunchSteps.AddRange(preset.PreLaunchSteps);
PostLaunchSteps.Clear();
PostLaunchSteps.AddRange(preset.PostLaunchSteps);
FinalizeSteps.Clear();
FinalizeSteps.AddRange(preset.FinalizeSteps);
}
public void Update(GamePreset preset)
{
name = preset.name;
ShowInQuickAccess = preset.ShowInQuickAccess;
Path = preset.Path;
Parameters = preset.Parameters;
RunAsAdministrator = preset.RunAsAdministrator;
ProcessAffinityMask = preset.ProcessAffinityMask;
ProcessPriorityClass = preset.ProcessPriorityClass;
AutoSettings.Update(preset.AutoSettings);
PreLaunchSteps.Clear();
PreLaunchSteps.AddRange(preset.PreLaunchSteps);
PostLaunchSteps.Clear();
PostLaunchSteps.AddRange(preset.PostLaunchSteps);
FinalizeSteps.Clear();
FinalizeSteps.AddRange(preset.FinalizeSteps);
}
public GamePreset Clone()
{
var preset = new GamePreset(this);
public GamePreset Clone()
{
var preset = new GamePreset(this);
preset.name += " (copy)";
preset.name += " (copy)";
return preset;
}
return preset;
}
public GamePreset CloneWithId()
{
var preset = new GamePreset(this);
preset.SetId(id);
preset.name = name;
preset.shortcut = shortcut;
public GamePreset CloneWithId()
{
var preset = new GamePreset(this);
preset.SetId(id);
preset.name = name;
preset.shortcut = shortcut;
return preset;
}
return preset;
}
public static string[] GetColumnNames()
{
return new[] { "Name|160", "File/URI|400", "Parameters|200", "Pre-launch steps|300", "Post-launch steps|300", "Finalize steps|300" };
}
public static string[] GetColumnNames()
{
return new[] { "Name|160", "File/URI|400", "Parameters|200", "Pre-launch steps|300", "Post-launch steps|300", "Finalize steps|300" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>
{
name,
Path,
Parameters,
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>
{
name,
Path,
Parameters,
string.Join(", ", PreLaunchSteps),
string.Join(", ", PostLaunchSteps),
string.Join(", ", FinalizeSteps)
};
string.Join(", ", PreLaunchSteps),
string.Join(", ", PostLaunchSteps),
string.Join(", ", FinalizeSteps)
};
return values;
}
return values;
}
}
+4 -27
View File
@@ -1,32 +1,9 @@
namespace ColorControl.Shared.Contracts.LG;
using ColorControl.Shared.Contracts.Base;
public class LgDeviceDto
namespace ColorControl.Shared.Contracts.LG;
public class LgDeviceDto : BaseDeviceDto
{
public string Name { get; set; }
public string IpAddress { get; set; }
public string MacAddress { get; set; }
public bool IsCustom { get; set; }
public string Token { get; set; }
public LgDeviceOptions Options { get; set; } = new();
public bool IsDummy { get; set; }
public bool PoweredOn { get; set; }
public DateTimeOffset PoweredOffAt { get; set; }
public bool IsConnected { get; set; }
public bool IsSelected { get; set; }
public override string ToString()
{
if (IpAddress != null)
{
return $"{Name} ({IpAddress})";
}
return Name;
}
}
+90 -92
View File
@@ -4,117 +4,115 @@ namespace ColorControl.Shared.Contracts.LG;
public class LgPreset : PresetBase
{
public static event GetValueByKey GetDeviceName;
public static event GetValueByKey GetAppName;
public static event GetValueByKey GetDeviceName;
public static event GetValueByKey GetAppName;
public string appId { get; set; }
public List<string> steps { get; set; }
public string DeviceMacAddress { get; set; }
public dynamic AppParams { get; set; }
public string appId { get; set; }
public string DeviceMacAddress { get; set; }
public dynamic AppParams { get; set; }
[JsonIgnore]
public int X { get; set; }
[JsonIgnore]
public int Y { get; set; }
[JsonIgnore]
public int X { get; set; }
[JsonIgnore]
public int Y { get; set; }
public LgPreset() : base()
{
steps = new List<string>();
}
public LgPreset() : base()
{
}
public LgPreset(LgPreset preset) : this()
{
id = GetNewId();
public LgPreset(LgPreset preset) : this()
{
id = GetNewId();
Update(preset);
Update(preset);
name = preset.name + " (copy)";
shortcut = null;
}
name = preset.name + " (copy)";
shortcut = null;
}
public LgPreset(string name, string appId, IEnumerable<string> steps = null, dynamic appParams = null) : this()
{
this.name = name;
this.appId = appId;
if (steps != null)
{
this.steps.AddRange(steps);
}
AppParams = appParams;
}
public LgPreset(string name, string appId, IEnumerable<string> steps = null, dynamic appParams = null) : this()
{
this.name = name;
this.appId = appId;
if (steps != null)
{
Steps.AddRange(steps);
}
AppParams = appParams;
}
public LgPreset Clone()
{
var preset = new LgPreset(this);
public LgPreset Clone()
{
var preset = new LgPreset(this);
return preset;
}
return preset;
}
public LgPreset CloneWithId()
{
var preset = new LgPreset(this);
preset.SetId(id);
preset.name = name;
preset.shortcut = shortcut;
public LgPreset CloneWithId()
{
var preset = new LgPreset(this);
preset.SetId(id);
preset.name = name;
preset.shortcut = shortcut;
return preset;
}
return preset;
}
public void Update(LgPreset preset)
{
name = preset.name;
appId = preset.appId;
shortcut = preset.shortcut;
steps.Clear();
steps.AddRange(preset.steps);
Description = preset.Description;
DeviceMacAddress = preset.DeviceMacAddress;
ShowInQuickAccess = preset.ShowInQuickAccess;
public void Update(LgPreset preset)
{
name = preset.name;
appId = preset.appId;
shortcut = preset.shortcut;
Steps.Clear();
Steps.AddRange(preset.Steps);
Description = preset.Description;
DeviceMacAddress = preset.DeviceMacAddress;
ShowInQuickAccess = preset.ShowInQuickAccess;
var trigger = preset.Triggers.FirstOrDefault();
if (trigger != null)
{
UpdateTrigger(trigger.Trigger, trigger.Conditions, trigger.IncludedProcessesAsString, trigger.ExcludedProcessesAsString, trigger.ConnectedDisplaysRegex);
}
}
var trigger = preset.Triggers.FirstOrDefault();
if (trigger != null)
{
UpdateTrigger(trigger.Trigger, trigger.Conditions, trigger.IncludedProcessesAsString, trigger.ExcludedProcessesAsString, trigger.ConnectedDisplaysRegex);
}
}
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "Device|140", "App|200", "Steps|400", "Shortcut", "Trigger" };
}
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "Device|140", "App|200", "Steps|400", "Shortcut", "Trigger" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
values.Add(name);
var deviceString = "Global";
if (!string.IsNullOrEmpty(DeviceMacAddress))
{
deviceString = GetDeviceName?.Invoke(DeviceMacAddress) ?? DeviceMacAddress;
}
values.Add(deviceString);
var deviceString = "Global";
if (!string.IsNullOrEmpty(DeviceMacAddress))
{
deviceString = GetDeviceName?.Invoke(DeviceMacAddress) ?? DeviceMacAddress;
}
values.Add(deviceString);
var app = appId;
if (!string.IsNullOrEmpty(appId))
{
app = GetAppName?.Invoke(appId) ?? appId;
}
values.Add(app);
values.Add(GetStepsDisplay());
values.Add(shortcut);
var app = appId;
if (!string.IsNullOrEmpty(appId))
{
app = GetAppName?.Invoke(appId) ?? appId;
}
values.Add(app);
values.Add(GetStepsDisplay());
values.Add(shortcut);
if (Triggers.Any())
{
values.Add(Triggers.First().ToString());
}
if (Triggers.Any())
{
values.Add(Triggers.First().ToString());
}
return values;
}
return values;
}
public string GetStepsDisplay()
{
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
}
public string GetStepsDisplay()
{
return Steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace ColorControl.Shared.Contracts.LG;
public static class LgPresets
{
public static readonly LgPreset InStartPreset = new("InStart", "com.webos.app.factorywin", ["0", "4", "1", "3"], new { id = "executeFactory", irKey = "inStart" });
public static readonly LgPreset EzAdjustPreset = new("EzAdjust", "com.webos.app.factorywin", ["0", "4", "1", "3"], new { id = "executeFactory", irKey = "ezAdjust" });
public static readonly LgPreset SoftwareUpdatePreset = new("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true });
}
+8
View File
@@ -0,0 +1,8 @@

using ColorControl.Shared.Contracts.RemoteControl;
namespace ColorControl.Shared.Contracts.LG;
public class LgRcDto : BaseRcDto<LgPreset, ButtonType>
{
}
@@ -66,6 +66,19 @@ namespace ColorControl.Shared.Contracts.NVIDIA
PowerPCM = settings.PowerPCM;
}
public bool Equals(NvGpuOcSettings other)
{
return PCIIdentifier == other.PCIIdentifier &&
MemoryOffsetKHz == other.MemoryOffsetKHz &&
Type == other.Type &&
GraphicsOffsetKHz == other.GraphicsOffsetKHz &&
MaximumFrequencyKHz == other.MaximumFrequencyKHz &&
MaximumVoltageUv == other.MaximumVoltageUv &&
FrequencyPreferred == other.FrequencyPreferred &&
VoltageBoostPercent == other.VoltageBoostPercent &&
PowerPCM == other.PowerPCM;
}
public override string ToString()
{
var value = "";
+23 -1
View File
@@ -84,6 +84,7 @@ namespace ColorControl.Shared.Contracts.NVIDIA
public NvHdrSettings HdrSettings { get; set; }
public bool ApplyNovideoSettings { get; set; }
public NovideoSettings NovideoSettings { get; set; }
public DpiScaling DpiScaling { get; set; }
public bool SetDitherRegistryKey { get; set; }
public bool RestartDriver { get; set; }
@@ -121,6 +122,7 @@ namespace ColorControl.Shared.Contracts.NVIDIA
ColorEnhancementSettings = new NvColorEnhancementSettings();
HdrSettings = new NvHdrSettings();
NovideoSettings = new NovideoSettings();
DpiScaling = new DpiScaling();
}
public NvPreset(ColorData colorData) : this()
@@ -150,10 +152,11 @@ namespace ColorControl.Shared.Contracts.NVIDIA
applyDriverSettings = keepChanges && applyDriverSettings || driverSettings.Any(s => !currentSettings.driverSettings.Any(o => o.Key == s.Key && o.Value == s.Value));
applyHdmiSettings = keepChanges && applyHdmiSettings || HdmiInfoFrameSettings.IsDifferent(currentSettings.HdmiInfoFrameSettings);
ApplyNovideoSettings = keepChanges && ApplyNovideoSettings || (NovideoSettings.ApplyClamp && !HDREnabled || applyHDR && !HDREnabled && !NovideoSettings.ApplyClamp);
applyOverclocking = keepChanges && applyOverclocking || ocSettings.Any();
applyOverclocking = keepChanges && applyOverclocking || !HasEqualOverclockingSettings(currentSettings);
ApplyColorEnhancements = keepChanges && ApplyColorEnhancements ||
ColorEnhancementSettings.DigitalVibranceLevel != currentSettings.ColorEnhancementSettings.DigitalVibranceLevel ||
ColorEnhancementSettings.HueAngle != currentSettings.ColorEnhancementSettings.HueAngle;
DpiScaling.ApplyScaling = keepChanges && DpiScaling.ApplyScaling || DpiScaling.Percentage != currentSettings.DpiScaling.Percentage;
}
public void Update(NvPreset preset)
@@ -200,7 +203,17 @@ namespace ColorControl.Shared.Contracts.NVIDIA
applyOverclocking = preset.applyOverclocking;
ocSettings = preset.ocSettings.Select(s => new NvGpuOcSettings(s)).ToList();
DpiScaling = new DpiScaling(preset.DpiScaling);
MonitorConnectionType = preset.MonitorConnectionType;
var trigger = preset.Triggers.FirstOrDefault();
if (trigger != null)
{
UpdateTrigger(trigger.Trigger, trigger.Conditions, trigger.IncludedProcessesAsString, trigger.ExcludedProcessesAsString, trigger.ConnectedDisplaysRegex);
}
Steps.Clear();
Steps.AddRange(preset.Steps);
}
public NvPreset Clone()
@@ -220,6 +233,15 @@ namespace ColorControl.Shared.Contracts.NVIDIA
return preset;
}
public bool HasApplicableSettings => applyColorData || applyDithering || applyDriverSettings || applyHdmiSettings || applyHDR || applyOther ||
applyOverclocking || ApplyColorEnhancements || ApplyNovideoSettings ||
DisplayConfig.ApplyRefreshRate || DisplayConfig.ApplyResolution || DpiScaling.ApplyScaling;
public bool HasEqualOverclockingSettings(NvPreset preset)
{
return preset.ocSettings.All(poc => ocSettings.Any(oc => oc.Equals(poc)));
}
public static string[] GetColumnNames()
{
return new[] { "Name", "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Color enhancements", "Refresh rate|120", "Resolution|150", "Dithering", "HDR", "Driver settings|300", "HDMI settings|200", "Other|200", "Overclocking|300", "Shortcut", "Apply on startup" };
+14
View File
@@ -0,0 +1,14 @@
namespace ColorControl.Shared.Contracts.NVIDIA;
public class NvProfile
{
public bool IsBase { get; set; }
public string Name { get; set; }
public List<string> Apps { get; set; }
public NvProfile()
{
Apps = [];
}
}
+38 -36
View File
@@ -4,65 +4,67 @@ namespace ColorControl.Shared.Contracts.NVIDIA;
public enum NvSettingType
{
DwordType,
BinaryType
DwordType,
BinaryType
}
public class NvSettingItemDto
{
public uint SettingId { get; set; }
public uint SettingId { get; set; }
public string SettingText { get; set; }
public string SettingText { get; set; }
public string ValueText { get; set; }
public string ValueText { get; set; }
public string ValueRaw { get; set; }
public string ValueRaw { get; set; }
[JsonIgnore]
public bool ValueChanged { get; set; }
[JsonIgnore]
public bool ValueChanged { get; set; }
private uint _value;
private uint _value;
public uint Value
{
get
{
return _value;
}
public uint Value
{
get
{
return _value;
}
set
{
_value = value;
ValueChanged = true;
}
}
set
{
_value = value;
ValueChanged = true;
}
}
public string GroupName { get; set; }
public string GroupName { get; set; }
public int State { get; set; }
public int State { get; set; }
public bool IsStringValue { get; set; }
public bool IsStringValue { get; set; }
public bool IsApiExposed { get; set; }
public bool IsApiExposed { get; set; }
public bool IsSettingHidden { get; set; }
public bool IsSettingHidden { get; set; }
public NvSettingType SettingType { get; set; }
public NvSettingType SettingType { get; set; }
public uint DefaultValue { get; set; }
public uint DefaultValue { get; set; }
public List<NvSettingItemValue> Values { get; set; }
public List<NvSettingItemValue> Values { get; set; }
public string FriendlyName { get; set; }
public string Name { get; set; }
public override string ToString()
{
return string.Format("{0}; 0x{1:X8}; {2}; {3}; {4};", State, SettingId, SettingText, ValueText, ValueRaw);
}
public string FriendlyName { get; set; }
public override string ToString()
{
return string.Format("{0}; 0x{1:X8}; {2}; {3}; {4};", State, SettingId, SettingText, ValueText, ValueRaw);
}
}
public class NvSettingItemValue
{
public uint Value { get; set; }
public string ValueName { get; set; }
public uint Value { get; set; }
public string ValueName { get; set; }
}
+8 -1
View File
@@ -185,6 +185,7 @@ public abstract class PresetBase
public string name { get; set; }
public string shortcut { get; set; }
public List<PresetTrigger> Triggers { get; set; }
public List<string> Steps { get; set; }
public string Description { get; set; }
public bool ShowInQuickAccess { get; set; }
public DateTime? LastUsed { get; set; }
@@ -194,7 +195,8 @@ public abstract class PresetBase
protected PresetBase()
{
Triggers = new List<PresetTrigger>();
Triggers = [];
Steps = [];
}
public int id
@@ -289,4 +291,9 @@ public abstract class PresetBase
return Triggers.First();
}
public void SetSteps(List<string> steps)
{
Steps = steps;
}
}
@@ -0,0 +1,30 @@
namespace ColorControl.Shared.Contracts.RemoteControl;
public class BaseRcButtonDto<TPreset, TButtonEnum> where TPreset : PresetBase where TButtonEnum : Enum
{
public string Name { get; set; }
public TButtonEnum Button { get; set; }
public TPreset Preset { get; set; }
public double Top { get; set; }
public double Left { get; set; }
public BaseRcButtonDto()
{
}
public BaseRcButtonDto(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
Update(button);
}
public void Update(BaseRcButtonDto<TPreset, TButtonEnum> button)
{
Name = button.Name;
Button = button.Button;
Preset = button.Preset;
Top = button.Top;
Left = button.Left;
}
}
@@ -0,0 +1,17 @@

namespace ColorControl.Shared.Contracts.RemoteControl;
public class BaseRcDto<TPreset, TButtonEnum> where TPreset : PresetBase where TButtonEnum : Enum
{
public string Name { get; set; }
public string ImageFileName { get; set; }
public List<BaseRcButtonDto<TPreset, TButtonEnum>> Buttons { get; set; }
public void Update(BaseRcDto<TPreset, TButtonEnum> remoteSpec)
{
Name = remoteSpec.Name;
ImageFileName = remoteSpec.ImageFileName;
Buttons = remoteSpec.Buttons;
}
}
@@ -11,6 +11,7 @@ public enum ButtonType
KEY_CHDOWN,
KEY_ENTER,
KEY_RETURN,
KEY_EXIT,
KEY_HOME,
KEY_CH_LIST,
KEY_MENU,
+4 -27
View File
@@ -1,32 +1,9 @@
namespace ColorControl.Shared.Contracts.Samsung;
using ColorControl.Shared.Contracts.Base;
public class SamsungDeviceDto
namespace ColorControl.Shared.Contracts.Samsung;
public class SamsungDeviceDto : BaseDeviceDto
{
public string Name { get; set; }
public string IpAddress { get; set; }
public string MacAddress { get; set; }
public bool IsCustom { get; set; }
public string Token { get; set; }
public SamsungDeviceOptions Options { get; set; } = new();
public bool IsDummy { get; set; }
public bool PoweredOn { get; set; }
public DateTimeOffset PoweredOffAt { get; set; }
public bool IsConnected { get; set; }
public bool IsSelected { get; set; }
public override string ToString()
{
if (IpAddress != null)
{
return $"{Name} ({IpAddress})";
}
return Name;
}
}
@@ -5,12 +5,10 @@ public class SamsungPreset : PresetBase
public static event GetValueByKey GetDeviceName;
public string AppId { get; set; }
public List<string> Steps { get; set; }
public string DeviceMacAddress { get; set; }
public SamsungPreset() : base()
{
Steps = new List<string>();
}
public SamsungPreset(SamsungPreset preset) : this()
+9 -10
View File
@@ -1,23 +1,22 @@
namespace ColorControl.Shared.Contracts.Samsung;
public static class SamsungFactoryMenuPresets
{
public static SamsungPreset Preset1 = new("ServiceMenuStep1", null, ["KEY_INFO", "KEY_FACTORY:2000"]);
public static SamsungPreset PresetAdvanced = new("ServiceMenuAdvanced", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_9", "KEY_8"]);
public static SamsungPreset PresetAdvancedMonitor = new("ServiceMenuAdvancedMonitor", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_3", "KEY_8"]);
public static SamsungPreset Exit = new("ServiceMenuStep3", null, ["KEY_FACTORY:1500", "KEY_FACTORY"]);
public static SamsungPreset KeyFactory = new("ServiceMenuKeyFactory", null, ["KEY_FACTORY"]);
public static readonly SamsungPreset Preset1 = new("ServiceMenuStep1", null, ["KEY_INFO", "KEY_FACTORY:2000"]);
public static readonly SamsungPreset PresetAdvanced = new("ServiceMenuAdvanced", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_9", "KEY_8"]);
public static readonly SamsungPreset PresetAdvancedMonitor = new("ServiceMenuAdvancedMonitor", null, ["KEY_UP", "KEY_0", "KEY_0", "KEY_3", "KEY_8"]);
public static readonly SamsungPreset Exit = new("ServiceMenuStep3", null, ["KEY_FACTORY:1500", "KEY_FACTORY"]);
public static readonly SamsungPreset KeyFactory = new("ServiceMenuKeyFactory", null, ["KEY_FACTORY"]);
}
public static class SamsungHospitalityMenuPresets
{
public static SamsungPreset Preset1 = new("ServiceMenuStep1", null, ["KEY_MUTE:600", "KEY_1:600", "KEY_1:600", "KEY_9:600", "KEY_ENTER:2000"]);
public static SamsungPreset Preset2 = new("ServiceMenuStep2", null, ["KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_UP"]);
public static SamsungPreset Preset3 = new("ServiceMenuStep3", null, ["KEY_VOLUP:5100:Press", "KEY_VOLUP:500:Release"]);
public static readonly SamsungPreset Preset1 = new("ServiceMenuStep1", null, ["KEY_MUTE:600", "KEY_1:600", "KEY_1:600", "KEY_9:600", "KEY_ENTER:2000"]);
public static readonly SamsungPreset Preset2 = new("ServiceMenuStep2", null, ["KEY_1", "KEY_2", "KEY_3", "KEY_4", "KEY_UP"]);
public static readonly SamsungPreset Preset3 = new("ServiceMenuStep3", null, ["KEY_VOLUP:5100:Press", "KEY_VOLUP:500:Release"]);
}
public static class SamsungGenericPresets
{
public static SamsungPreset Reboot = new("ServiceMenuStep4", null, ["KEY_POWER:5000", "WOL"]);
public static readonly SamsungPreset Reboot = new("ServiceMenuStep4", null, ["KEY_POWER:5000", "WOL"]);
}
+8
View File
@@ -0,0 +1,8 @@

using ColorControl.Shared.Contracts.RemoteControl;
namespace ColorControl.Shared.Contracts.Samsung;
public class SamsungRcDto : BaseRcDto<SamsungPreset, ButtonType>
{
}
@@ -1,8 +1,7 @@
using ColorControl.Shared.Common;
using ColorControl.Shared.Forms;
using ColorControl.Shared.Services;
using System.Diagnostics;
using System.Diagnostics;
using System.Management;
using ColorControl.Shared.Common;
using ColorControl.Shared.Forms;
namespace ColorControl.Shared.EventDispatcher;
@@ -46,6 +46,13 @@ public class WindowMessageDispatcher : EventDispatcher<WindowMessageEventArgs>
public WindowMessageDispatcher(GlobalContext globalContext)
{
// If no GUI is active (using CLI), creating a form may block connections started by the ClientWebSocket.
// In that case, skip creating the form and return.
if (globalContext.StartUpParams.NoGui)
{
return;
}
MessageForm = new DummyForm();
MessageForm.OnMessage += MessageForm_OnMessage;
_globalContext = globalContext;
+19 -91
View File
@@ -1,100 +1,28 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using ColorControl.Shared.Common;
using static ColorControl.Shared.Common.NestedItemsBuilder;
namespace ColorControl.Shared.Forms
namespace ColorControl.Shared.Forms;
public class TreeNodeBuilder
{
public class TreeNodeBuilder
public static TreeNode CreateTree(object obj, string text)
{
public string Name { get; set; }
public string Value { get; set; }
public List<TreeNode> Children { get; set; } = new List<TreeNode>();
var nestedItem = NestedItemsBuilder.CreateTree(obj, text);
public static TreeNode CreateTree(object obj, string text)
return BuildTree(nestedItem);
}
private static TreeNode BuildTree(NestedItem nestedItem)
{
var treeNode = new TreeNode
{
var serialized = JsonConvert.SerializeObject(obj, Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
Error = delegate (object sender, ErrorEventArgs args)
{
//errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled = true;
},
Converters = new[] { new Newtonsoft.Json.Converters.StringEnumConverter() }
});
var dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(serialized);
var root = new TreeNode();
root.Text = text;
BuildTree(dic, root);
return root;
}
Text = nestedItem.Value,
Name = nestedItem.Name
};
var nodes = nestedItem.NestedItems.Select(BuildTree);
private static void BuildTree(object item, TreeNode node)
{
if (item is KeyValuePair<string, object>)
{
KeyValuePair<string, object> kv = (KeyValuePair<string, object>)item;
TreeNode keyValueNode = new TreeNode();
keyValueNode.Name = kv.Key;
keyValueNode.Text = kv.Key + ": " + GetValueAsString(kv.Value);
node.Nodes.Add(keyValueNode);
BuildTree(kv.Value, keyValueNode);
}
else if (item.GetType() == typeof(JArray))
{
var list = (JArray)item;
int index = 0;
foreach (object value in list)
{
TreeNode arrayItem = new TreeNode();
arrayItem.Name = $"[{index}]";
arrayItem.Text = $"[{index}]";
node.Nodes.Add(arrayItem);
BuildTree(value, arrayItem);
index++;
}
}
else if (item is Dictionary<string, object>)
{
Dictionary<string, object> dictionary = (Dictionary<string, object>)item;
foreach (KeyValuePair<string, object> d in dictionary)
{
BuildTree(d, node);
}
}
else if (item.GetType() == typeof(JObject))
{
var jobject = item as JObject;
foreach (var property in jobject.Properties())
{
BuildTree(new KeyValuePair<string, object>(property.Name, property.Value), node);
}
}
}
treeNode.Nodes.AddRange([.. nodes]);
private static string GetValueAsString(object value)
{
if (value == null)
return "null";
var type = value.GetType();
if (type.IsArray)
{
return "[]";
}
if (value.GetType() == typeof(JArray))
{
var arr = value as JArray;
return $"[{arr.Count}]";
}
if (type.IsGenericType || value.GetType() == typeof(JObject))
{
return "{}";
}
return value.ToString();
}
return treeNode;
}
}
+57 -18
View File
@@ -1,12 +1,12 @@
using ColorControl.Shared.Contracts;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ColorControl.Shared.Contracts;
using ColorControl.Shared.Forms;
using MHC2Gen;
using NStandard;
using NWin32;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace ColorControl.Shared.Native
{
@@ -357,6 +357,12 @@ namespace ColorControl.Shared.Native
public static bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true, bool isHdrColorProfile = false)
{
var usePerUser = GetUsePerUserDisplayProfiles(displayName);
if (!usePerUser)
{
SetUsePerUserDisplayProfiles(displayName, true);
}
var adapterId = GetAdapterId(displayName);
var sourceId = GetSourceId(displayName);
@@ -754,7 +760,27 @@ namespace ColorControl.Shared.Native
);
}
public static DISPLAYCONFIG_SOURCE_DPI_SCALE_GET GetScaling(string displayName = null)
public static readonly uint[] DpiValues = [100, 125, 150, 175, 200, 225, 250, 300, 350, 400, 450, 500];
public static uint GetDpiScalingInPercentage(string displayName = null)
{
var dpiInfo = GetDpiScaling(displayName);
var dpiIndex = Math.Abs(dpiInfo.minScaleRel) + dpiInfo.curScaleRel;
if (dpiIndex < 0)
{
dpiIndex = 0;
}
else if (dpiIndex >= DpiValues.Length)
{
dpiIndex = DpiValues.Length - 1;
}
return DpiValues[dpiIndex];
}
public static DISPLAYCONFIG_SOURCE_DPI_SCALE_GET GetDpiScaling(string displayName = null)
{
return ExecuteForModeConfig((modeInfo) =>
{
@@ -781,23 +807,36 @@ namespace ColorControl.Shared.Native
);
}
public static bool SetScaling(int percentage, string displayName = null)
public static bool SetDpiScaling(uint percentage, string displayName = null)
{
var dpiInfo = GetScaling(displayName);
var dpiInfo = GetDpiScaling(displayName);
if (percentage == dpiInfo.curScaleRel)
var minAbs = Math.Abs(dpiInfo.minScaleRel);
if (DpiValues.Length < (minAbs + dpiInfo.maxScaleRel + 1))
{
return false;
}
var dpiIndex = DpiValues.IndexOf(percentage);
if (dpiIndex < 0)
{
return false;
}
var scaleRel = dpiInfo.minScaleRel + dpiIndex;
if (scaleRel == dpiInfo.curScaleRel)
{
return true;
}
if (percentage < dpiInfo.minScaleRel)
{
percentage = dpiInfo.minScaleRel;
}
else if (percentage > dpiInfo.maxScaleRel)
{
percentage = dpiInfo.maxScaleRel;
}
//if (percentage < dpiInfo.minScaleRel)
//{
// percentage = dpiInfo.minScaleRel;
//}
//else if (percentage > dpiInfo.maxScaleRel)
//{
// percentage = dpiInfo.maxScaleRel;
//}
return ExecuteForModeConfig((modeInfo) =>
{
@@ -811,7 +850,7 @@ namespace ColorControl.Shared.Native
setPacket.header.adapterId = modeInfo.adapterId;
setPacket.header.id = modeInfo.id;
setPacket.scaleRel = -7;
setPacket.scaleRel = scaleRel;
var err = NativeMethods.DisplayConfigSetDeviceInfo(ref setPacket);
+25 -3
View File
@@ -1,8 +1,8 @@
using ColorControl.Shared.Common;
using System.ComponentModel;
using System.Windows;
using ColorControl.Shared.Common;
using ColorControl.Shared.XForms;
using Microsoft.Win32;
using System.ComponentModel;
using System.Windows;
namespace novideo_srgb
{
@@ -160,6 +160,16 @@ namespace novideo_srgb
_novideoWindow?.ApplyClamp(monitorId, clamp, targetColorSpace);
}
public static (bool clamped, int targetColorSpace) GetSettings(string monitorId)
{
if (_novideoWindow == null)
{
return (false, 0);
}
return _novideoWindow.GetSettingsForMonitor(monitorId);
}
private void ApplyClamp(string monitorId, bool clamp, int targetColorSpace)
{
var monitor = _viewModel.Monitors.FirstOrDefault(m => monitorId == null || m.Path.Contains(monitorId));
@@ -172,5 +182,17 @@ namespace novideo_srgb
monitor.Target = targetColorSpace;
monitor.Clamped = clamp;
}
private (bool clamped, int targetColorSpace) GetSettingsForMonitor(string monitorId)
{
var monitor = _viewModel.Monitors.FirstOrDefault(m => monitorId == null || m.Path.Contains(monitorId));
if (monitor == null)
{
return (false, 0);
}
return (monitor.Clamped, monitor.Target);
}
}
}