mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 08:32:18 -05:00
fix some Nvidia issues, improve color profiles, add GPU info to new UI
This commit is contained in:
1 parent
f42a46e53f
commit
899e49c2d9
30 files changed
+1997
-1604
No files matched your search
@@ -116,7 +116,7 @@ else
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
|
||||
@@ -261,7 +261,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary" @onclick="InstallOnClick" disabled="@(ColorProfile == null)">Install and set as default</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="CloseOnClick">Close</button>
|
||||
<button type="submit" class="btn btn-primary" @onclick="ApplyClick" disabled="@(ColorProfile == null)">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,7 +305,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorProfile = await _rpcClientService.CallAsync<ColorProfileDto>("ColorProfileService", "GetColorProfile", ColorProfileName);
|
||||
ColorProfile = await _rpcClientService.CallAsync<ColorProfileDto>("ColorProfileService", "GetColorProfile", ColorProfileName, DisplayInfo?.IsHdrSupportedAndEnabled ?? false);
|
||||
}
|
||||
|
||||
if (ColorProfile != null)
|
||||
@@ -390,6 +390,16 @@
|
||||
IsInstalled = true;
|
||||
}
|
||||
|
||||
private async Task CloseOnClick()
|
||||
{
|
||||
IsVisible = false;
|
||||
|
||||
if (ColorProfile != null && AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(ColorProfile);
|
||||
}
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
|
||||
@@ -210,7 +210,7 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetDisplayDefaultColorProfile", SelectedDisplay.DeviceName, colorProfile);
|
||||
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetDisplayDefaultColorProfile", SelectedDisplay.DeviceName, colorProfile, Type.Missing, SelectedDisplay.IsHdrSupportedAndEnabled);
|
||||
if (result)
|
||||
{
|
||||
SelectedDisplay.DefaultColorProfile = colorProfile;
|
||||
@@ -250,7 +250,7 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "RemoveDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName);
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "RemoveDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, SelectedDisplay.IsHdrSupportedAndEnabled);
|
||||
|
||||
SelectedColorProfileName = null;
|
||||
|
||||
@@ -265,7 +265,7 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "AddDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, false);
|
||||
await _rpcClientService.CallAsync<bool>("ColorProfileService", "AddDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, false, SelectedDisplay.IsHdrSupportedAndEnabled);
|
||||
|
||||
SelectedColorProfileName = null;
|
||||
|
||||
|
||||
@@ -21,113 +21,125 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="path">File/URI</label>
|
||||
<div class="input-group mb-2">
|
||||
<input class="form-control" id="path" type="text" @bind="Preset.Path" required />
|
||||
<button type="button" class="btn btn-primary" @onclick="SelectFileOnClick" disabled="@SelectingFile">
|
||||
<span class="@(SelectingFile ? "spinner-border spinner-border-sm" : "")" aria-hidden="true"></span>
|
||||
<span role="status">Select...</span>
|
||||
</button>
|
||||
@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 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 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>
|
||||
<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="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
|
||||
Advanced settings
|
||||
<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>
|
||||
</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 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="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
|
||||
Advanced settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
|
||||
<label class="form-check-label" for="runAsAdministrator">
|
||||
Run as administrator
|
||||
</label>
|
||||
</div>
|
||||
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="priority">Process priority</label>
|
||||
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
|
||||
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
|
||||
{
|
||||
<option value="@priority.ToString()">@priority.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 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>
|
||||
<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 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>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -148,6 +160,8 @@
|
||||
public Func<GamePreset, Task>? AfterApply { get; set; }
|
||||
|
||||
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
|
||||
private List<GameApp>? RecentApps;
|
||||
private string RecentAppFilename = "";
|
||||
|
||||
public bool SelectingFile { get; set; }
|
||||
|
||||
@@ -162,6 +176,21 @@
|
||||
}
|
||||
|
||||
Preset = PresetParam.CloneWithId();
|
||||
|
||||
if (Preset.id == 0)
|
||||
{
|
||||
RecentApps = await _rpcClientService.CallAsync<List<GameApp>>("GameService", "GetMruApps", true);
|
||||
RecentAppFilename = "";
|
||||
|
||||
if (RecentApps != null)
|
||||
{
|
||||
RecentApps.Insert(0, new GameApp { Filename = "", FriendlyName = "Choose a recent app to fill values" });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RecentApps = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
@@ -183,7 +212,7 @@
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
|
||||
|
||||
IsVisible = false;
|
||||
}
|
||||
|
||||
@@ -224,6 +253,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -76,7 +76,7 @@ else
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
|
||||
@@ -122,6 +122,8 @@
|
||||
<button type="button" class="btn btn-outline-primary dropdown-toggle text-break text-wrap" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">@step.Raw</button>
|
||||
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
|
||||
<div class="mb-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" title="Move left" disabled="@(stepIndex == 0)" @onclick="() => MoveLeftOnClick(stepIndex)"><</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" title="Move right" disabled="@(stepIndex == PresetSteps.Count - 1)" @onclick="() => MoveRightOnClick(stepIndex)">></button>
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" @onclick="() => RemoveOnClick(stepIndex)">Remove</button>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
@@ -260,6 +262,32 @@
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task MoveLeftOnClick(int index)
|
||||
{
|
||||
if (PresetSteps.Count > index && index > 0)
|
||||
{
|
||||
var step = PresetSteps[index];
|
||||
|
||||
PresetSteps.RemoveAt(index);
|
||||
PresetSteps.Insert(index - 1, step);
|
||||
}
|
||||
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task MoveRightOnClick(int index)
|
||||
{
|
||||
if (PresetSteps.Count > index + 1)
|
||||
{
|
||||
var step = PresetSteps[index];
|
||||
|
||||
PresetSteps.RemoveAt(index);
|
||||
PresetSteps.Insert(index + 1, step);
|
||||
}
|
||||
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task AfterDelayChanged()
|
||||
{
|
||||
await EmitSteps();
|
||||
|
||||
@@ -178,7 +178,7 @@ else
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
|
||||
@@ -96,6 +96,39 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
<h2>Display Adapters</h2>
|
||||
|
||||
@if (GpuInfos == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@if (!GpuInfos.Any())
|
||||
{
|
||||
<span>No display adapters found</span>
|
||||
}
|
||||
@foreach (var gpuInfo in GpuInfos)
|
||||
{
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-baseline">
|
||||
<span>
|
||||
@gpuInfo.Name
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-body">GPU: @gpuInfo.GpuFrequencyInKhz.ToUnitString() @@ @gpuInfo.VoltageInMv mV, @Utils.ToUnitString(values: [gpuInfo.GpuTemperature, gpuInfo.HotSpotTemperature])</small>
|
||||
<small class="text-body">Memory: @gpuInfo.GpuFrequencyInKhz.ToUnitString(), @Utils.ToUnitString(values: [gpuInfo.MemoryTemperature])</small>
|
||||
<small class="text-muted">Power: @(gpuInfo.PowerInWatts)W</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
@@ -142,7 +175,7 @@ else
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@@ -207,6 +240,7 @@ else
|
||||
@code {
|
||||
private List<NvPreset>? Presets;
|
||||
private List<NvPreset>? DisplayPresets;
|
||||
private List<NvGpuInfoDto>? GpuInfos;
|
||||
|
||||
private NvPreset? CurrentPreset;
|
||||
private NvPreset? CurrentDriverSettingsPreset;
|
||||
@@ -218,6 +252,7 @@ else
|
||||
{
|
||||
await RefreshPresets();
|
||||
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
GpuInfos = await _rpcClientService.CallAsync<List<NvGpuInfoDto>>("NvService", "GetGpuInfos");
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
|
||||
@@ -176,7 +176,7 @@ else
|
||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
|
||||
</span>
|
||||
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<PublisherName>Maassoft</PublisherName>
|
||||
<Company>Maassoft</Company>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>10.0.1.0</ApplicationVersion>
|
||||
<Version>10.0.1.0</Version>
|
||||
<ApplicationVersion>10.0.2.0</ApplicationVersion>
|
||||
<Version>10.0.2.0</Version>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
|
||||
+331
-329
@@ -21,340 +21,342 @@ using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private static bool SystemShutdown = false;
|
||||
private static bool EndSession = false;
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private static bool SystemShutdown = false;
|
||||
private static bool EndSession = false;
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ElevationService _elevationService;
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ElevationService _elevationService;
|
||||
|
||||
private NvPanel _nvPanel;
|
||||
private NvPanel _nvPanel;
|
||||
|
||||
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
private readonly UpdateManager _updateManager;
|
||||
private readonly WindowMessageDispatcher _windowMessageDispatcher;
|
||||
private Config _config;
|
||||
private readonly KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
private readonly UpdateManager _updateManager;
|
||||
private readonly WindowMessageDispatcher _windowMessageDispatcher;
|
||||
private Config _config;
|
||||
|
||||
private LgPanel _lgPanel;
|
||||
private SamsungPanel _samsungPanel;
|
||||
private LgPanel _lgPanel;
|
||||
private SamsungPanel _samsungPanel;
|
||||
|
||||
private AmdPanel _amdPanel;
|
||||
private bool _skipResize;
|
||||
private AmdPanel _amdPanel;
|
||||
private bool _skipResize;
|
||||
|
||||
private GamePanel _gamePanel;
|
||||
|
||||
public MainForm(GlobalContext globalContext, ServiceManager serviceManager, IServiceProvider serviceProvider, ElevationService elevationService,
|
||||
KeyboardShortcutDispatcher keyboardShortcutDispatcher, UpdateManager updateManager, WindowMessageDispatcher windowMessageDispatcher)
|
||||
{
|
||||
InitializeComponent();
|
||||
private GamePanel _gamePanel;
|
||||
|
||||
Icon = Resources.AppIcon;
|
||||
|
||||
_serviceManager = serviceManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_elevationService = elevationService;
|
||||
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
|
||||
_updateManager = updateManager;
|
||||
_windowMessageDispatcher = windowMessageDispatcher;
|
||||
_config = Program.Config;
|
||||
|
||||
Text = globalContext.ApplicationTitleAdmin;
|
||||
|
||||
LoadConfig();
|
||||
|
||||
MessageForms.MainForm = this;
|
||||
|
||||
InitModules();
|
||||
|
||||
Task.Run(AfterInitialized);
|
||||
|
||||
if (_config.UseDarkMode)
|
||||
{
|
||||
SetTheme(_config.UseDarkMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitModules()
|
||||
{
|
||||
var _ = tcMain.Handle;
|
||||
|
||||
_nvPanel = InitPanel<NvPanel>("NVIDIA controller");
|
||||
_amdPanel = InitPanel<AmdPanel>("AMD controller");
|
||||
_lgPanel = InitPanel<LgPanel>("LG controller");
|
||||
_samsungPanel = InitPanel<SamsungPanel>("Samsung controller");
|
||||
_gamePanel = InitPanel<GamePanel>("Game launcher");
|
||||
|
||||
tcMain.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private T InitPanel<T>(string displayName) where T : UserControl, IModulePanel
|
||||
{
|
||||
var module = _config.Modules.FirstOrDefault(m => m.IsActive && m.DisplayName == displayName);
|
||||
if (module == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var panel = _serviceProvider.GetRequiredService<T>();
|
||||
panel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
panel.Init();
|
||||
|
||||
var tabPage = new TabPage(module.DisplayName);
|
||||
tcMain.TabPages.Insert(tcMain.TabPages.Count - 2, tabPage);
|
||||
|
||||
tabPage.Controls.Add(panel);
|
||||
|
||||
panel.Size = tabPage.ClientSize;
|
||||
panel.BackColor = SystemColors.Window;
|
||||
|
||||
//Logger.Debug($"Panel {panel.GetType().Name}, size: {panel.Size.Width}x{panel.Size.Height}");
|
||||
|
||||
return panel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"Error creating panel {typeof(T).Name}: {ex.ToLogString()}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenForm()
|
||||
{
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (Program.UserExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(SystemShutdown || EndSession) && _config.MinimizeOnClose)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = FormWindowState.Minimized;
|
||||
Program.UserExit = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalSave();
|
||||
|
||||
if (SystemShutdown)
|
||||
{
|
||||
Logger.Debug($"MainForm_FormClosing: SystemShutdown");
|
||||
}
|
||||
|
||||
Program.Exit();
|
||||
}
|
||||
|
||||
private void GlobalSave()
|
||||
{
|
||||
_nvPanel?.Save();
|
||||
_amdPanel?.Save();
|
||||
_lgPanel?.Save();
|
||||
_gamePanel?.Save();
|
||||
_samsungPanel?.Save();
|
||||
|
||||
if (WindowState != FormWindowState.Minimized)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == NativeConstants.WM_QUERYENDSESSION)
|
||||
{
|
||||
SystemShutdown = true;
|
||||
EndSession = true;
|
||||
_windowMessageDispatcher.DispatchEvent(WindowMessageDispatcher.Event_WindowMessageQueryEndSession, new WindowMessageEventArgs(m));
|
||||
}
|
||||
else if (m.Msg == Utils.WM_BRINGTOFRONT)
|
||||
{
|
||||
Logger.Debug("WM_BRINGTOFRONT message received, opening form");
|
||||
OpenForm();
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private void MainForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (_skipResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
if (_config.MinimizeToTray)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
else if (WindowState == FormWindowState.Normal && _config != null)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_skipResize = true;
|
||||
try
|
||||
{
|
||||
Width = _config.FormWidth;
|
||||
Height = _config.FormHeight;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
|
||||
_elevationService.CheckElevationMethod();
|
||||
}
|
||||
|
||||
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private void InitSelectedTab()
|
||||
{
|
||||
if (tcMain.SelectedTab == tabInfo)
|
||||
{
|
||||
InitInfoTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab == tabOptions)
|
||||
{
|
||||
InitOptionsTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOptionsTab()
|
||||
{
|
||||
if (tabOptions.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _serviceProvider.GetRequiredService<OptionsPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabOptions.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
}
|
||||
|
||||
private void InitInfoTab()
|
||||
{
|
||||
if (tabInfo.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var control = _serviceProvider.GetRequiredService<InfoPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabInfo.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
//control.BackColor = SystemColors.Window;
|
||||
}
|
||||
|
||||
private void MainForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AfterInitialized()
|
||||
{
|
||||
if (_nvPanel != null)
|
||||
{
|
||||
await _nvPanel.AfterInitialized();
|
||||
}
|
||||
if (_amdPanel != null)
|
||||
{
|
||||
await _amdPanel.AfterInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Deactivate(object sender, EventArgs e)
|
||||
{
|
||||
GlobalSave();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeBegin(object sender, EventArgs e)
|
||||
{
|
||||
SuspendLayout();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeEnd(object sender, EventArgs e)
|
||||
{
|
||||
ResumeLayout(true);
|
||||
}
|
||||
|
||||
private async void MainForm_Click(object sender, EventArgs e)
|
||||
{
|
||||
_serviceManager.NvService?.TestResolution();
|
||||
}
|
||||
|
||||
public void SetTheme(bool toDark)
|
||||
{
|
||||
this.UpdateTheme(toDark);
|
||||
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private async void btnUpdate_Click(object sender, EventArgs e)
|
||||
{
|
||||
await _updateManager.InstallUpdate();
|
||||
}
|
||||
|
||||
internal void CloseForRestart()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if ((keyData == (Keys.Control | Keys.PageUp) || keyData == (Keys.Control | Keys.PageDown)) && KeyboardShortcutDispatcher.IsShortcutControlFocused())
|
||||
{
|
||||
var control = this.FindFocusedControl() as TextBox;
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
var keyEvent = new KeyEventArgs(keyData);
|
||||
|
||||
control.Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(keyEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
}
|
||||
public MainForm(GlobalContext globalContext, ServiceManager serviceManager, IServiceProvider serviceProvider, ElevationService elevationService,
|
||||
KeyboardShortcutDispatcher keyboardShortcutDispatcher, UpdateManager updateManager, WindowMessageDispatcher windowMessageDispatcher)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Icon = Resources.AppIcon;
|
||||
|
||||
_serviceManager = serviceManager;
|
||||
_serviceProvider = serviceProvider;
|
||||
_elevationService = elevationService;
|
||||
_keyboardShortcutDispatcher = keyboardShortcutDispatcher;
|
||||
_updateManager = updateManager;
|
||||
_windowMessageDispatcher = windowMessageDispatcher;
|
||||
_config = Program.Config;
|
||||
|
||||
Text = globalContext.ApplicationTitleAdmin;
|
||||
|
||||
LoadConfig();
|
||||
|
||||
MessageForms.MainForm = this;
|
||||
|
||||
InitModules();
|
||||
|
||||
Task.Run(AfterInitialized);
|
||||
|
||||
if (_config.UseDarkMode)
|
||||
{
|
||||
SetTheme(_config.UseDarkMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitModules()
|
||||
{
|
||||
var _ = tcMain.Handle;
|
||||
|
||||
_nvPanel = InitPanel<NvPanel>("NVIDIA controller");
|
||||
_amdPanel = InitPanel<AmdPanel>("AMD controller");
|
||||
_lgPanel = InitPanel<LgPanel>("LG controller");
|
||||
_samsungPanel = InitPanel<SamsungPanel>("Samsung controller");
|
||||
_gamePanel = InitPanel<GamePanel>("Game launcher");
|
||||
|
||||
tcMain.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private T InitPanel<T>(string displayName) where T : UserControl, IModulePanel
|
||||
{
|
||||
var module = _config.Modules.FirstOrDefault(m => m.IsActive && m.DisplayName == displayName);
|
||||
if (module == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var panel = _serviceProvider.GetRequiredService<T>();
|
||||
panel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
panel.Init();
|
||||
|
||||
var tabPage = new TabPage(module.DisplayName);
|
||||
tcMain.TabPages.Insert(tcMain.TabPages.Count - 2, tabPage);
|
||||
|
||||
tabPage.Controls.Add(panel);
|
||||
|
||||
panel.Size = tabPage.ClientSize;
|
||||
panel.BackColor = SystemColors.Window;
|
||||
|
||||
//Logger.Debug($"Panel {panel.GetType().Name}, size: {panel.Size.Width}x{panel.Size.Height}");
|
||||
|
||||
return panel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Debug($"Error creating panel {typeof(T).Name}: {ex.ToLogString()}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenForm()
|
||||
{
|
||||
Show();
|
||||
WindowState = FormWindowState.Normal;
|
||||
Activate();
|
||||
|
||||
btnUpdate.Visible = _updateManager.UpdateAvailable();
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (Program.UserExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(SystemShutdown || EndSession) && _config.MinimizeOnClose)
|
||||
{
|
||||
e.Cancel = true;
|
||||
WindowState = FormWindowState.Minimized;
|
||||
Program.UserExit = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GlobalSave();
|
||||
|
||||
if (SystemShutdown)
|
||||
{
|
||||
Logger.Debug($"MainForm_FormClosing: SystemShutdown");
|
||||
}
|
||||
|
||||
Program.Exit();
|
||||
}
|
||||
|
||||
private void GlobalSave()
|
||||
{
|
||||
_nvPanel?.Save();
|
||||
_amdPanel?.Save();
|
||||
_lgPanel?.Save();
|
||||
_gamePanel?.Save();
|
||||
_samsungPanel?.Save();
|
||||
|
||||
if (WindowState != FormWindowState.Minimized)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
if (m.Msg == NativeConstants.WM_QUERYENDSESSION)
|
||||
{
|
||||
SystemShutdown = true;
|
||||
EndSession = true;
|
||||
_windowMessageDispatcher.DispatchEvent(WindowMessageDispatcher.Event_WindowMessageQueryEndSession, new WindowMessageEventArgs(m));
|
||||
}
|
||||
else if (m.Msg == Utils.WM_BRINGTOFRONT)
|
||||
{
|
||||
Logger.Debug("WM_BRINGTOFRONT message received, opening form");
|
||||
OpenForm();
|
||||
}
|
||||
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private void MainForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (_skipResize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
if (_config.MinimizeToTray)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
else if (WindowState == FormWindowState.Normal && _config != null)
|
||||
{
|
||||
_config.FormWidth = Width;
|
||||
_config.FormHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_skipResize = true;
|
||||
try
|
||||
{
|
||||
Width = _config.FormWidth;
|
||||
Height = _config.FormHeight;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_skipResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
|
||||
_elevationService.CheckElevationMethod();
|
||||
}
|
||||
|
||||
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private void InitSelectedTab()
|
||||
{
|
||||
if (tcMain.SelectedTab == tabInfo)
|
||||
{
|
||||
InitInfoTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab == tabOptions)
|
||||
{
|
||||
InitOptionsTab();
|
||||
}
|
||||
else if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitOptionsTab()
|
||||
{
|
||||
if (tabOptions.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var control = _serviceProvider.GetRequiredService<OptionsPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabOptions.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
}
|
||||
|
||||
private void InitInfoTab()
|
||||
{
|
||||
if (tabInfo.Controls.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var control = _serviceProvider.GetRequiredService<InfoPanel>();
|
||||
control.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
|
||||
tabInfo.Controls.Add(control);
|
||||
|
||||
control.Size = tabInfo.ClientSize;
|
||||
//control.BackColor = SystemColors.Window;
|
||||
}
|
||||
|
||||
private void MainForm_Activated(object sender, EventArgs e)
|
||||
{
|
||||
if (tcMain.SelectedTab?.Controls.Count > 0 && tcMain.SelectedTab.Controls[0] is IModulePanel panel)
|
||||
{
|
||||
panel.UpdateInfo();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AfterInitialized()
|
||||
{
|
||||
if (_nvPanel != null)
|
||||
{
|
||||
await _nvPanel.AfterInitialized();
|
||||
}
|
||||
if (_amdPanel != null)
|
||||
{
|
||||
await _amdPanel.AfterInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_Deactivate(object sender, EventArgs e)
|
||||
{
|
||||
GlobalSave();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeBegin(object sender, EventArgs e)
|
||||
{
|
||||
SuspendLayout();
|
||||
}
|
||||
|
||||
private void MainForm_ResizeEnd(object sender, EventArgs e)
|
||||
{
|
||||
ResumeLayout(true);
|
||||
}
|
||||
|
||||
private async void MainForm_Click(object sender, EventArgs e)
|
||||
{
|
||||
_serviceManager.NvService?.TestResolution();
|
||||
}
|
||||
|
||||
public void SetTheme(bool toDark)
|
||||
{
|
||||
this.UpdateTheme(toDark);
|
||||
|
||||
InitSelectedTab();
|
||||
}
|
||||
|
||||
private async void btnUpdate_Click(object sender, EventArgs e)
|
||||
{
|
||||
await _updateManager.InstallUpdate();
|
||||
}
|
||||
|
||||
internal void CloseForRestart()
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if ((keyData == (Keys.Control | Keys.PageUp) || keyData == (Keys.Control | Keys.PageDown)) && KeyboardShortcutDispatcher.IsShortcutControlFocused())
|
||||
{
|
||||
var control = this.FindFocusedControl() as TextBox;
|
||||
|
||||
if (control != null)
|
||||
{
|
||||
var keyEvent = new KeyEventArgs(keyData);
|
||||
|
||||
control.Text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(keyEvent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,6 +342,7 @@ namespace ColorControl.Services.AMD
|
||||
var preset = GetSelectedAmdPreset();
|
||||
|
||||
var newPreset = preset.Clone();
|
||||
newPreset.IsDisplayPreset = false;
|
||||
AddOrUpdateItemAmd(newPreset);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ namespace ColorControl.Services.AMD
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
protected override void AfterPresetsLoaded()
|
||||
{
|
||||
// Force disable display preset on presets
|
||||
_presets.ForEach(p => p.IsDisplayPreset = false);
|
||||
}
|
||||
|
||||
public static async Task<bool> ExecutePresetAsync(string idOrName)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -15,329 +15,329 @@ namespace ColorControl.Services.Common;
|
||||
|
||||
public class ColorProfileService
|
||||
{
|
||||
private readonly GlobalContext _globalContext;
|
||||
private readonly RpcClientService _rpcClientService;
|
||||
private readonly WinApiService _winApiService;
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly GlobalContext _globalContext;
|
||||
private readonly RpcClientService _rpcClientService;
|
||||
private readonly WinApiService _winApiService;
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
|
||||
public ColorProfileService(GlobalContext globalContext, RpcClientService rpcClientService, WinApiService winApiService, ServiceManager serviceManager, WinApiAdminService winApiAdminService)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_rpcClientService = rpcClientService;
|
||||
_winApiService = winApiService;
|
||||
_serviceManager = serviceManager;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
public ColorProfileService(GlobalContext globalContext, RpcClientService rpcClientService, WinApiService winApiService, ServiceManager serviceManager, WinApiAdminService winApiAdminService)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_rpcClientService = rpcClientService;
|
||||
_winApiService = winApiService;
|
||||
_serviceManager = serviceManager;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
|
||||
_rpcClientService.Name = nameof(ColorProfileService);
|
||||
}
|
||||
_rpcClientService.Name = nameof(ColorProfileService);
|
||||
}
|
||||
|
||||
public bool InstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.InstallColorProfile(profileName);
|
||||
}
|
||||
public bool InstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.InstallColorProfile(profileName);
|
||||
}
|
||||
|
||||
public bool UninstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.UninstallColorProfile(profileName);
|
||||
}
|
||||
public bool UninstallColorProfile(string profileName)
|
||||
{
|
||||
return CCD.UninstallColorProfile(profileName);
|
||||
}
|
||||
|
||||
public bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true)
|
||||
{
|
||||
return CCD.AddDisplayColorProfile(displayName, profileName, setAsDefault);
|
||||
}
|
||||
public bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true, bool isHdrColorProfile = false)
|
||||
{
|
||||
return CCD.AddDisplayColorProfile(displayName, profileName, setAsDefault, isHdrColorProfile);
|
||||
}
|
||||
|
||||
|
||||
public bool RemoveDisplayColorProfile(string displayName, string profileName)
|
||||
{
|
||||
return CCD.RemoveDisplayColorProfile(displayName, profileName);
|
||||
}
|
||||
public bool RemoveDisplayColorProfile(string displayName, string profileName, bool isHdrColorProfile = false)
|
||||
{
|
||||
return CCD.RemoveDisplayColorProfile(displayName, profileName, isHdrColorProfile);
|
||||
}
|
||||
|
||||
public bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true)
|
||||
{
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, fixMaxTML);
|
||||
}
|
||||
public bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true, bool isHdrColorProfile = false)
|
||||
{
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, fixMaxTML, isHdrColorProfile);
|
||||
}
|
||||
|
||||
public string GetDisplayDefaultColorProfile(string displayName, COLORPROFILESUBTYPE profileSubType = COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE)
|
||||
{
|
||||
return CCD.GetDisplayDefaultColorProfile(displayName, profileSubType);
|
||||
}
|
||||
public string GetDisplayDefaultColorProfile(string displayName, bool isHdrColorProfile)
|
||||
{
|
||||
return CCD.GetDisplayDefaultColorProfile(displayName, isHdrColorProfile);
|
||||
}
|
||||
|
||||
public List<string> GetDisplayColorProfiles(string displayName)
|
||||
{
|
||||
if (displayName != null && !displayName.StartsWith("\\\\"))
|
||||
{
|
||||
displayName = GetDisplayNameByDisplayId(displayName);
|
||||
}
|
||||
public List<string> GetDisplayColorProfiles(string displayName)
|
||||
{
|
||||
if (displayName != null && !displayName.StartsWith("\\\\"))
|
||||
{
|
||||
displayName = GetDisplayNameByDisplayId(displayName);
|
||||
}
|
||||
|
||||
return CCD.GetDisplayColorProfiles(displayName);
|
||||
}
|
||||
return CCD.GetDisplayColorProfiles(displayName);
|
||||
}
|
||||
|
||||
public List<string> GetAllColorProfileNames()
|
||||
{
|
||||
if (!_winApiService.IsAdministrator())
|
||||
{
|
||||
return _rpcClientService.Call<List<string>>("GetAllColorProfileNames");
|
||||
}
|
||||
public List<string> GetAllColorProfileNames()
|
||||
{
|
||||
if (!_winApiService.IsAdministrator())
|
||||
{
|
||||
return _rpcClientService.Call<List<string>>("GetAllColorProfileNames");
|
||||
}
|
||||
|
||||
return CCD.GetAllColorProfileNames();
|
||||
}
|
||||
return CCD.GetAllColorProfileNames();
|
||||
}
|
||||
|
||||
private string GetDisplayNameByDisplayId(string displayName)
|
||||
{
|
||||
var deviceName = default(string);
|
||||
private string GetDisplayNameByDisplayId(string displayName)
|
||||
{
|
||||
var deviceName = default(string);
|
||||
|
||||
Display.GetDisplays().FirstOrDefault(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
Display.GetDisplays().FirstOrDefault(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
|
||||
if (ccdDisplayInfo.DisplayId == displayName)
|
||||
{
|
||||
deviceName = ccdDisplayInfo.DisplayName;
|
||||
if (ccdDisplayInfo.DisplayId == displayName)
|
||||
{
|
||||
deviceName = ccdDisplayInfo.DisplayName;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return deviceName;
|
||||
}
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
public bool GetUsePerUserDisplayProfiles(string displayName)
|
||||
{
|
||||
return CCD.GetUsePerUserDisplayProfiles(displayName);
|
||||
}
|
||||
public bool GetUsePerUserDisplayProfiles(string displayName)
|
||||
{
|
||||
return CCD.GetUsePerUserDisplayProfiles(displayName);
|
||||
}
|
||||
|
||||
public bool SetUsePerUserDisplayProfiles(string displayName, bool usePerUserProfiles)
|
||||
{
|
||||
return CCD.SetUsePerUserDisplayProfiles(displayName, usePerUserProfiles);
|
||||
}
|
||||
public bool SetUsePerUserDisplayProfiles(string displayName, bool usePerUserProfiles)
|
||||
{
|
||||
return CCD.SetUsePerUserDisplayProfiles(displayName, usePerUserProfiles);
|
||||
}
|
||||
|
||||
public bool DeleteColorProfile(string name)
|
||||
{
|
||||
return CCD.UninstallColorProfile(name);
|
||||
}
|
||||
public bool DeleteColorProfile(string name)
|
||||
{
|
||||
return CCD.UninstallColorProfile(name);
|
||||
}
|
||||
|
||||
public ColorProfileDto GetColorProfile(string name)
|
||||
{
|
||||
var data = MHC2Wrapper.LoadProfile(name, true);
|
||||
public ColorProfileDto GetColorProfile(string name, bool isHdrProfile = true)
|
||||
{
|
||||
var data = MHC2Wrapper.LoadProfile(name, isHdrProfile);
|
||||
|
||||
return new ColorProfileDto
|
||||
{
|
||||
Name = name,
|
||||
HasExtraInfo = data.HasExtraInfo,
|
||||
BlackLuminance = data.BlackLuminance,
|
||||
ColorGamut = data.ColorGamut,
|
||||
Description = data.Description,
|
||||
DevicePrimaries = new RgbPrimariesDto(data.DevicePrimaries),
|
||||
Gamma = data.Gamma,
|
||||
IsHDRProfile = data.IsHDRProfile,
|
||||
MaxCLL = data.MaxCLL,
|
||||
MinCLL = data.MinCLL,
|
||||
SDRBrightnessBoost = data.SDRBrightnessBoost,
|
||||
SDRMaxBrightness = data.SDRMaxBrightness,
|
||||
SDRMinBrightness = data.SDRMinBrightness,
|
||||
SDRTransferFunction = data.SDRTransferFunction,
|
||||
ShadowDetailBoost = data.ShadowDetailBoost,
|
||||
WhiteLuminance = data.WhiteLuminance,
|
||||
};
|
||||
}
|
||||
return new ColorProfileDto
|
||||
{
|
||||
Name = name,
|
||||
HasExtraInfo = data.HasExtraInfo,
|
||||
BlackLuminance = data.BlackLuminance,
|
||||
ColorGamut = data.ColorGamut,
|
||||
Description = data.Description,
|
||||
DevicePrimaries = new RgbPrimariesDto(data.DevicePrimaries),
|
||||
Gamma = data.Gamma,
|
||||
IsHDRProfile = data.IsHDRProfile,
|
||||
MaxCLL = data.MaxCLL,
|
||||
MinCLL = data.MinCLL,
|
||||
SDRBrightnessBoost = data.SDRBrightnessBoost,
|
||||
SDRMaxBrightness = data.SDRMaxBrightness,
|
||||
SDRMinBrightness = data.SDRMinBrightness,
|
||||
SDRTransferFunction = data.SDRTransferFunction,
|
||||
ShadowDetailBoost = data.ShadowDetailBoost,
|
||||
WhiteLuminance = data.WhiteLuminance,
|
||||
};
|
||||
}
|
||||
|
||||
public bool UpdateColorProfile(ColorProfileDto colorProfile, string displayName = null)
|
||||
{
|
||||
var command = new GenerateProfileCommand
|
||||
{
|
||||
Description = colorProfile.Description,
|
||||
IsHDRProfile = colorProfile.IsHDRProfile,
|
||||
MinCLL = colorProfile.MinCLL,
|
||||
MaxCLL = colorProfile.MaxCLL,
|
||||
BlackLuminance = colorProfile.BlackLuminance,
|
||||
WhiteLuminance = colorProfile.WhiteLuminance,
|
||||
SDRMinBrightness = colorProfile.SDRMinBrightness,
|
||||
SDRMaxBrightness = colorProfile.SDRMaxBrightness,
|
||||
SDRTransferFunction = colorProfile.SDRTransferFunction,
|
||||
SDRBrightnessBoost = colorProfile.SDRBrightnessBoost,
|
||||
ShadowDetailBoost = colorProfile.ShadowDetailBoost,
|
||||
ColorGamut = colorProfile.ColorGamut,
|
||||
Gamma = colorProfile.Gamma,
|
||||
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
|
||||
};
|
||||
public bool UpdateColorProfile(ColorProfileDto colorProfile, string displayName = null)
|
||||
{
|
||||
var command = new GenerateProfileCommand
|
||||
{
|
||||
Description = colorProfile.Description,
|
||||
IsHDRProfile = colorProfile.IsHDRProfile,
|
||||
MinCLL = colorProfile.MinCLL,
|
||||
MaxCLL = colorProfile.MaxCLL,
|
||||
BlackLuminance = colorProfile.BlackLuminance,
|
||||
WhiteLuminance = colorProfile.WhiteLuminance,
|
||||
SDRMinBrightness = colorProfile.SDRMinBrightness,
|
||||
SDRMaxBrightness = colorProfile.SDRMaxBrightness,
|
||||
SDRTransferFunction = colorProfile.SDRTransferFunction,
|
||||
SDRBrightnessBoost = colorProfile.SDRBrightnessBoost,
|
||||
ShadowDetailBoost = colorProfile.ShadowDetailBoost,
|
||||
ColorGamut = colorProfile.ColorGamut,
|
||||
Gamma = colorProfile.Gamma,
|
||||
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
|
||||
};
|
||||
|
||||
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
|
||||
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
|
||||
|
||||
var tempFilename = Path.GetFullPath(colorProfile.Name, Path.GetTempPath());
|
||||
var tempFilename = Path.GetFullPath(colorProfile.Name, Path.GetTempPath());
|
||||
|
||||
if (File.Exists(tempFilename))
|
||||
{
|
||||
File.Delete(tempFilename);
|
||||
}
|
||||
if (File.Exists(tempFilename))
|
||||
{
|
||||
File.Delete(tempFilename);
|
||||
}
|
||||
|
||||
File.WriteAllBytes(tempFilename, bytes);
|
||||
File.WriteAllBytes(tempFilename, bytes);
|
||||
|
||||
_winApiAdminService.UninstallColorProfile(tempFilename);
|
||||
_winApiAdminService.UninstallColorProfile(tempFilename);
|
||||
|
||||
var result = CCD.InstallColorProfile(tempFilename);
|
||||
var result = CCD.InstallColorProfile(tempFilename);
|
||||
|
||||
if (result && displayName != null)
|
||||
{
|
||||
CCD.AddDisplayColorProfile(displayName, colorProfile.Name, false);
|
||||
}
|
||||
if (result && displayName != null)
|
||||
{
|
||||
CCD.AddDisplayColorProfile(displayName, colorProfile.Name, false, command.IsHDRProfile);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool InstallAndActivateColorProfile(ColorProfileDto colorProfile, string displayName)
|
||||
{
|
||||
var installed = UpdateColorProfile(colorProfile);
|
||||
public bool InstallAndActivateColorProfile(ColorProfileDto colorProfile, string displayName)
|
||||
{
|
||||
var installed = UpdateColorProfile(colorProfile);
|
||||
|
||||
if (!installed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!installed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var profileName = colorProfile.Name;
|
||||
var profileName = colorProfile.Name;
|
||||
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, true);
|
||||
}
|
||||
return CCD.SetDisplayDefaultColorProfile(displayName, profileName, _globalContext.Config.SetMinTmlAndMaxTml, colorProfile.IsHDRProfile);
|
||||
}
|
||||
|
||||
public List<DisplayInfoDto> GetDisplayInfos(bool? onlyHdrEnabled = null)
|
||||
{
|
||||
return Display.GetDisplays().Select(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(d.DisplayName);
|
||||
var colorParams = CCD.GetColorParams(d.DisplayName);
|
||||
public List<DisplayInfoDto> GetDisplayInfos(bool? onlyHdrEnabled = null)
|
||||
{
|
||||
return Display.GetDisplays().Select(d =>
|
||||
{
|
||||
var ccdDisplayInfo = CCD.GetDisplayInfo(d.DisplayName);
|
||||
var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(d.DisplayName);
|
||||
var colorParams = CCD.GetColorParams(d.DisplayName);
|
||||
|
||||
var displayColors = new List<DisplayColorInfo>
|
||||
{
|
||||
new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Custom,
|
||||
CustomName = "sRGB",
|
||||
RgbPrimaries = new RgbPrimariesDto(RgbPrimaries.sRGB),
|
||||
BlackLuminance = 0,
|
||||
WhiteLuminance = 100,
|
||||
MinCLL = 0,
|
||||
MaxCLL = 100
|
||||
}
|
||||
};
|
||||
var displayColors = new List<DisplayColorInfo>
|
||||
{
|
||||
new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Custom,
|
||||
CustomName = "sRGB",
|
||||
RgbPrimaries = new RgbPrimariesDto(RgbPrimaries.sRGB),
|
||||
BlackLuminance = 0,
|
||||
WhiteLuminance = 100,
|
||||
MinCLL = 0,
|
||||
MaxCLL = 100
|
||||
}
|
||||
};
|
||||
|
||||
if (!colorParams.Equals(default(ColorParams)))
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Windows,
|
||||
RgbPrimaries = ColorParamsToRgbPrimaries(colorParams),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
if (!colorParams.Equals(default(ColorParams)))
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.Windows,
|
||||
RgbPrimaries = ColorParamsToRgbPrimaries(colorParams),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
|
||||
var edid = GetEDIDInternal(d.DevicePath);
|
||||
if (edid != null)
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.EDID,
|
||||
RgbPrimaries = EdidToRgbPrimaries(edid),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
var edid = GetEDIDInternal(d.DevicePath);
|
||||
if (edid != null)
|
||||
{
|
||||
displayColors.Add(new DisplayColorInfo
|
||||
{
|
||||
DisplayPrimariesSource = MHC2Gen.DisplayPrimariesSource.EDID,
|
||||
RgbPrimaries = EdidToRgbPrimaries(edid),
|
||||
BlackLuminance = (double)colorParams.MinLuminance / 10000,
|
||||
WhiteLuminance = (double)colorParams.MaxLuminance / 10000,
|
||||
MinCLL = (double)colorParams.MinLuminance / 10000,
|
||||
MaxCLL = (double)colorParams.MaxLuminance / 10000
|
||||
});
|
||||
}
|
||||
|
||||
return new DisplayInfoDto
|
||||
{
|
||||
DisplayName = ccdDisplayInfo.ExtendedName,
|
||||
DevicePath = ccdDisplayInfo.DevicePath,
|
||||
DisplayId = ccdDisplayInfo.DisplayId,
|
||||
IsActive = true,
|
||||
DeviceName = d.DisplayName,
|
||||
AdapterName = d.Adapter.DeviceName,
|
||||
IsAdvancedColorEnabled = advancedColor.isEnabled,
|
||||
IsAdvancedColorSupported = advancedColor.isSupported,
|
||||
DisplayColors = displayColors,
|
||||
ColorProfiles = GetDisplayColorProfiles(d.DisplayName),
|
||||
DefaultColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, advancedColor.isEnabled ? COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE : COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE)
|
||||
};
|
||||
})
|
||||
.Where(d => !onlyHdrEnabled.HasValue || onlyHdrEnabled == d.IsHdrSupportedAndEnabled)
|
||||
.ToList();
|
||||
}
|
||||
return new DisplayInfoDto
|
||||
{
|
||||
DisplayName = ccdDisplayInfo.ExtendedName,
|
||||
DevicePath = ccdDisplayInfo.DevicePath,
|
||||
DisplayId = ccdDisplayInfo.DisplayId,
|
||||
IsActive = true,
|
||||
DeviceName = d.DisplayName,
|
||||
AdapterName = d.Adapter.DeviceName,
|
||||
IsAdvancedColorEnabled = advancedColor.isEnabled,
|
||||
IsAdvancedColorSupported = advancedColor.isSupported,
|
||||
DisplayColors = displayColors,
|
||||
ColorProfiles = GetDisplayColorProfiles(d.DisplayName),
|
||||
DefaultColorProfile = GetDisplayDefaultColorProfile(d.DisplayName, advancedColor.isEnabled)
|
||||
};
|
||||
})
|
||||
.Where(d => !onlyHdrEnabled.HasValue || onlyHdrEnabled == d.IsHdrSupportedAndEnabled)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public RgbPrimariesDto ColorParamsToRgbPrimaries(ColorParams colorParams)
|
||||
{
|
||||
var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
|
||||
public RgbPrimariesDto ColorParamsToRgbPrimaries(ColorParams colorParams)
|
||||
{
|
||||
var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
|
||||
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.RedPointX / divider,
|
||||
Y = (double)colorParams.RedPointY / divider
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.GreenPointX / divider,
|
||||
Y = (double)colorParams.GreenPointY / divider
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.BluePointX / divider,
|
||||
Y = (double)colorParams.BluePointY / divider
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.WhitePointX / divider,
|
||||
Y = (double)colorParams.WhitePointY / divider
|
||||
}
|
||||
};
|
||||
}
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.RedPointX / divider,
|
||||
Y = (double)colorParams.RedPointY / divider
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.GreenPointX / divider,
|
||||
Y = (double)colorParams.GreenPointY / divider
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.BluePointX / divider,
|
||||
Y = (double)colorParams.BluePointY / divider
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = (double)colorParams.WhitePointX / divider,
|
||||
Y = (double)colorParams.WhitePointY / divider
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public RgbPrimariesDto EdidToRgbPrimaries(EDID edid)
|
||||
{
|
||||
var coords = edid.DisplayParameters.ChromaticityCoordinates;
|
||||
public RgbPrimariesDto EdidToRgbPrimaries(EDID edid)
|
||||
{
|
||||
var coords = edid.DisplayParameters.ChromaticityCoordinates;
|
||||
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = coords.RedX,
|
||||
Y = coords.RedY
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = coords.GreenX,
|
||||
Y = coords.GreenY
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = coords.BlueX,
|
||||
Y = coords.BlueY
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = coords.WhiteX,
|
||||
Y = coords.WhiteY
|
||||
}
|
||||
};
|
||||
}
|
||||
return new RgbPrimariesDto
|
||||
{
|
||||
Red = new ColorPoint
|
||||
{
|
||||
X = coords.RedX,
|
||||
Y = coords.RedY
|
||||
},
|
||||
Green = new ColorPoint
|
||||
{
|
||||
X = coords.GreenX,
|
||||
Y = coords.GreenY
|
||||
},
|
||||
Blue = new ColorPoint
|
||||
{
|
||||
X = coords.BlueX,
|
||||
Y = coords.BlueY
|
||||
},
|
||||
White = new ColorPoint
|
||||
{
|
||||
X = coords.WhiteX,
|
||||
Y = coords.WhiteY
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static EDID GetEDIDInternal(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryPath = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\";
|
||||
registryPath += string.Join("\\", path.Split('#').Skip(1).Take(2));
|
||||
return new EDID((byte[])Registry.GetValue(registryPath + "\\Device Parameters", "EDID", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static EDID GetEDIDInternal(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryPath = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\";
|
||||
registryPath += string.Join("\\", path.Split('#').Skip(1).Take(2));
|
||||
return new EDID((byte[])Registry.GetValue(registryPath + "\\Device Parameters", "EDID", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,136 +3,158 @@ using ColorControl.Shared.Contracts;
|
||||
using ColorControl.Shared.Forms;
|
||||
using ColorControl.Shared.Native;
|
||||
using NWin32;
|
||||
using NWin32.NativeTypes;
|
||||
using Shared.Native;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ColorControl.Services.Common
|
||||
{
|
||||
abstract class GraphicsService<T> : ServiceBase<T> where T : PresetBase, new()
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
abstract class GraphicsService<T> : ServiceBase<T> where T : PresetBase, new()
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
private int _lastSetSDRBrightness = -1;
|
||||
private int _lastReadSDRBrightness = -1;
|
||||
private int _lastSetSDRBrightness = -1;
|
||||
private int _lastReadSDRBrightness = -1;
|
||||
|
||||
public GraphicsService(GlobalContext globalContext) : base(globalContext)
|
||||
{
|
||||
}
|
||||
public GraphicsService(GlobalContext globalContext) : base(globalContext)
|
||||
{
|
||||
}
|
||||
|
||||
~GraphicsService()
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
~GraphicsService()
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
public abstract bool HasDisplaysAttached(bool reinitialize = false);
|
||||
public abstract bool HasDisplaysAttached(bool reinitialize = false);
|
||||
|
||||
public bool SetMode(string displayName, VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false)
|
||||
{
|
||||
var displayConfig = CCD.GetDisplayConfig(displayName);
|
||||
public bool SetMode(string displayName, VirtualResolution resolution = null, Rational refreshRate = null, bool updateRegistry = false)
|
||||
{
|
||||
var displayConfig = CCD.GetDisplayConfig(displayName);
|
||||
|
||||
if (refreshRate != null)
|
||||
{
|
||||
displayConfig.ApplyRefreshRate = true;
|
||||
displayConfig.RefreshRate = refreshRate;
|
||||
}
|
||||
if (refreshRate != null)
|
||||
{
|
||||
displayConfig.ApplyRefreshRate = true;
|
||||
displayConfig.RefreshRate = refreshRate;
|
||||
}
|
||||
|
||||
if (resolution != null)
|
||||
{
|
||||
displayConfig.ApplyResolution = true;
|
||||
displayConfig.Resolution = resolution;
|
||||
}
|
||||
if (resolution != null)
|
||||
{
|
||||
displayConfig.ApplyResolution = true;
|
||||
displayConfig.Resolution = resolution;
|
||||
}
|
||||
|
||||
return SetMode(displayName, displayConfig, updateRegistry);
|
||||
}
|
||||
return SetMode(displayName, displayConfig, updateRegistry);
|
||||
}
|
||||
|
||||
public bool SetMode(string displayName, DisplayConfig displayConfig, bool updateRegistry = false)
|
||||
{
|
||||
var currentDisplayConfig = CCD.GetDisplayConfig(displayName);
|
||||
public bool SetMode(string displayName, DisplayConfig displayConfig, bool updateRegistry = false)
|
||||
{
|
||||
var currentDisplayConfig = CCD.GetDisplayConfig(displayName);
|
||||
|
||||
if (displayConfig.ApplyRefreshRate)
|
||||
{
|
||||
currentDisplayConfig.RefreshRate = displayConfig.RefreshRate;
|
||||
}
|
||||
if (displayConfig.ApplyRefreshRate)
|
||||
{
|
||||
currentDisplayConfig.RefreshRate = displayConfig.RefreshRate;
|
||||
}
|
||||
|
||||
if (displayConfig.ApplyResolution)
|
||||
{
|
||||
currentDisplayConfig.Resolution = displayConfig.Resolution;
|
||||
}
|
||||
if (displayConfig.ApplyResolution)
|
||||
{
|
||||
currentDisplayConfig.Resolution = displayConfig.Resolution;
|
||||
}
|
||||
|
||||
if (displayConfig.Scaling != CCD.DisplayConfigScaling.Zero)
|
||||
{
|
||||
currentDisplayConfig.Scaling = displayConfig.Scaling;
|
||||
}
|
||||
if (displayConfig.Scaling != CCD.DisplayConfigScaling.Zero)
|
||||
{
|
||||
currentDisplayConfig.Scaling = displayConfig.Scaling;
|
||||
}
|
||||
|
||||
if (displayConfig.Rotation != CCD.DisplayConfigRotation.Zero)
|
||||
{
|
||||
currentDisplayConfig.Rotation = displayConfig.Rotation;
|
||||
}
|
||||
if (displayConfig.Rotation != CCD.DisplayConfigRotation.Zero)
|
||||
{
|
||||
currentDisplayConfig.Rotation = displayConfig.Rotation;
|
||||
}
|
||||
|
||||
return CCD.SetDisplayConfig(displayName, currentDisplayConfig, updateRegistry);
|
||||
}
|
||||
return CCD.SetDisplayConfig(displayName, currentDisplayConfig, updateRegistry);
|
||||
}
|
||||
|
||||
public void SetSDRBrightness(string displayName, int brightnessPercent)
|
||||
{
|
||||
var hmon = FormUtils.GetMonitorForDisplayName(displayName);
|
||||
public void SetSDRBrightness(string displayName, int brightnessPercent)
|
||||
{
|
||||
var hmon = FormUtils.GetMonitorForDisplayName(displayName);
|
||||
|
||||
if (hmon == IntPtr.Zero)
|
||||
{
|
||||
hmon = NativeMethods.MonitorFromWindow(0, 1);
|
||||
}
|
||||
if (hmon == IntPtr.Zero)
|
||||
{
|
||||
hmon = NativeMethods.MonitorFromWindow(0, 1);
|
||||
}
|
||||
|
||||
var brightness = 1 + (double)brightnessPercent / 20;
|
||||
var brightness = 1 + (double)brightnessPercent / 20;
|
||||
|
||||
WinApi.DwmpSDRToHDRBoostPtr(hmon, brightness);
|
||||
WinApi.DwmpSDRToHDRBoostPtr(hmon, brightness);
|
||||
|
||||
_lastSetSDRBrightness = brightnessPercent;
|
||||
}
|
||||
_lastSetSDRBrightness = brightnessPercent;
|
||||
}
|
||||
|
||||
public int GetSDRBrightness(string displayName)
|
||||
{
|
||||
var whiteLevel = CCD.GetSDRWhiteLevel(displayName);
|
||||
public int GetSDRBrightness(string displayName)
|
||||
{
|
||||
var whiteLevel = CCD.GetSDRWhiteLevel(displayName);
|
||||
|
||||
if (whiteLevel >= 1000)
|
||||
{
|
||||
var newBrightnessPercent = (int)(whiteLevel - 1000) / 50;
|
||||
if (whiteLevel >= 1000)
|
||||
{
|
||||
var newBrightnessPercent = (int)(whiteLevel - 1000) / 50;
|
||||
|
||||
if (_lastSetSDRBrightness >= 0 && newBrightnessPercent == _lastReadSDRBrightness)
|
||||
{
|
||||
return _lastSetSDRBrightness;
|
||||
}
|
||||
if (_lastSetSDRBrightness >= 0 && newBrightnessPercent == _lastReadSDRBrightness)
|
||||
{
|
||||
return _lastSetSDRBrightness;
|
||||
}
|
||||
|
||||
_lastReadSDRBrightness = newBrightnessPercent;
|
||||
_lastReadSDRBrightness = newBrightnessPercent;
|
||||
|
||||
return newBrightnessPercent;
|
||||
}
|
||||
return newBrightnessPercent;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected List<Rational> GetAvailableRefreshRatesV2(string displayName, int horizontal, int vertical)
|
||||
{
|
||||
var dxWrapper = new DXWrapper();
|
||||
protected List<Rational> GetAvailableRefreshRatesV2(string displayName, int horizontal, int vertical)
|
||||
{
|
||||
var dxWrapper = new DXWrapper();
|
||||
|
||||
var modes = dxWrapper.GetModes(displayName, (uint)horizontal, (uint)vertical);
|
||||
var modes = dxWrapper.GetModes(displayName, (uint)horizontal, (uint)vertical);
|
||||
|
||||
var refreshRates = modes.Select(m => m.RefreshRate).DistinctBy(r => r.ToString())
|
||||
.Select(r => new Rational(r.Numerator, r.Denominator));
|
||||
var refreshRates = modes.Select(m => m.RefreshRate).DistinctBy(r => r.ToString())
|
||||
.Select(r => new Rational(r.Numerator, r.Denominator));
|
||||
|
||||
return refreshRates.ToList();
|
||||
}
|
||||
return refreshRates.ToList();
|
||||
}
|
||||
|
||||
protected List<VirtualResolution> GetAvailableResolutionsInternalV2(string displayName, Rational refreshRate = null)
|
||||
{
|
||||
var dxWrapper = new DXWrapper();
|
||||
protected List<VirtualResolution> GetAvailableResolutionsInternalV2(string displayName, Rational refreshRate = null)
|
||||
{
|
||||
var dxWrapper = new DXWrapper();
|
||||
|
||||
var modes = dxWrapper.GetModes(displayName);
|
||||
var modes = dxWrapper.GetModes(displayName);
|
||||
|
||||
var refreshRates = modes.DistinctBy(m => $"{m.Resolution.width}x{m.Resolution.height}")
|
||||
.Select(m => new VirtualResolution(m.Resolution.width, m.Resolution.height));
|
||||
var refreshRates = modes.DistinctBy(m => $"{m.Resolution.width}x{m.Resolution.height}")
|
||||
.Select(m => new VirtualResolution(m.Resolution.width, m.Resolution.height));
|
||||
|
||||
return refreshRates.ToList();
|
||||
}
|
||||
}
|
||||
return refreshRates.ToList();
|
||||
}
|
||||
|
||||
protected bool SetMonitorScaling()
|
||||
{
|
||||
var devMode = new DEVMODEW();
|
||||
devMode.dmSize = (ushort)Marshal.SizeOf<DEVMODEW>();
|
||||
devMode.dmFields = NativeConstants.DM_LOGPIXELS;
|
||||
devMode.dmLogPixels = 96 * 96 / 100;
|
||||
|
||||
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf<DEVMODEW>());
|
||||
|
||||
Marshal.StructureToPtr(devMode, ptr, false);
|
||||
|
||||
if (NativeMethods.ChangeDisplaySettingsExW(null, ptr, 0, 0, 0) == NativeConstants.DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
var result = NativeMethods.SystemParametersInfoW(0x009F, uint.MaxValue - 6, 0, NativeConstants.SPIF_SENDCHANGE | NativeConstants.SPIF_UPDATEINIFILE);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,6 +279,13 @@ namespace ColorControl.Services.Common
|
||||
{
|
||||
_presets = new List<T>();
|
||||
}
|
||||
|
||||
AfterPresetsLoaded();
|
||||
}
|
||||
|
||||
protected virtual void AfterPresetsLoaded()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected void SavePresets()
|
||||
|
||||
@@ -12,212 +12,214 @@ namespace ColorControl.Services.Common;
|
||||
|
||||
public class UpdateManager
|
||||
{
|
||||
private readonly GlobalContext _globalContext;
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly NotifyIconManager _notifyIconManager;
|
||||
private readonly WinApiService _winApiService;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly Config _config;
|
||||
private readonly GlobalContext _globalContext;
|
||||
private readonly ServiceManager _serviceManager;
|
||||
private readonly NotifyIconManager _notifyIconManager;
|
||||
private readonly WinApiService _winApiService;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly Config _config;
|
||||
|
||||
private string _downloadUrl;
|
||||
private string _downloadUrl;
|
||||
|
||||
public UpdateManager(GlobalContext globalContext, ServiceManager serviceManager, NotifyIconManager notifyIconManager, WinApiService winApiService, WinApiAdminService winApiAdminService)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_serviceManager = serviceManager;
|
||||
_notifyIconManager = notifyIconManager;
|
||||
_winApiService = winApiService;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
_config = globalContext.Config;
|
||||
}
|
||||
public UpdateManager(GlobalContext globalContext, ServiceManager serviceManager, NotifyIconManager notifyIconManager, WinApiService winApiService, WinApiAdminService winApiAdminService)
|
||||
{
|
||||
_globalContext = globalContext;
|
||||
_serviceManager = serviceManager;
|
||||
_notifyIconManager = notifyIconManager;
|
||||
_winApiService = winApiService;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
_config = globalContext.Config;
|
||||
}
|
||||
|
||||
private void trayIconBalloonTip_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (_globalContext.UpdateInfo?.HtmlUrl != null)
|
||||
{
|
||||
_winApiAdminService.StartProcess(_globalContext.UpdateInfo?.HtmlUrl);
|
||||
}
|
||||
}
|
||||
private void trayIconBalloonTip_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
if (_globalContext.UpdateInfo?.HtmlUrl != null)
|
||||
{
|
||||
_winApiAdminService.StartProcess(_globalContext.UpdateInfo?.HtmlUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CheckForUpdatesAndInstall()
|
||||
{
|
||||
var updateInfo = await CheckForUpdates();
|
||||
public async Task CheckForUpdatesAndInstall()
|
||||
{
|
||||
var updateInfo = await CheckForUpdates();
|
||||
|
||||
if (!updateInfo.UpdateAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!updateInfo.UpdateAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyIconManager.NotifyIcon.BalloonTipClicked += trayIconBalloonTip_Clicked;
|
||||
var newVersion = updateInfo.NewVersionNumber;
|
||||
_notifyIconManager.NotifyIcon.BalloonTipClicked += trayIconBalloonTip_Clicked;
|
||||
var newVersion = updateInfo.NewVersionNumber;
|
||||
|
||||
if (!string.IsNullOrEmpty(updateInfo.DownloadUrl) && _winApiService.IsServiceRunning())
|
||||
{
|
||||
if (_config.AutoInstallUpdates)
|
||||
{
|
||||
await InstallUpdate();
|
||||
if (!string.IsNullOrEmpty(updateInfo.DownloadUrl) && _winApiService.IsServiceRunning())
|
||||
{
|
||||
if (_config.AutoInstallUpdates)
|
||||
{
|
||||
await InstallUpdate();
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click on the Update-button to update", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Update-button to update", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click on the Update-button to update", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Update-button to update", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click to open the GitHub page", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Help-button to open the GitHub page.", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
}
|
||||
if (_notifyIconManager.NotifyIcon.Visible)
|
||||
{
|
||||
_notifyIconManager.NotifyIcon.ShowBalloonTip(5000, "Update available", $"Version {newVersion} is available. Click to open the GitHub page", ToolTipIcon.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageForms.InfoOk($"New version {newVersion} is available. Click on the Help-button to open the GitHub page.", "Update available", $"https://github.com/Maassoft/ColorControl/releases/tag/v{newVersion}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UpdateInfoDto> CheckForUpdates()
|
||||
{
|
||||
if (_globalContext.UpdateInfo != null)
|
||||
{
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
public bool UpdateAvailable() => _globalContext.UpdateInfo?.UpdateAvailable == true;
|
||||
|
||||
//if (Debugger.IsAttached)
|
||||
//{
|
||||
// return _globalContext.UpdateInfo = new UpdateInfoDto
|
||||
// {
|
||||
// UpdateAvailable = true,
|
||||
// NewVersionNumber = "10.0.0.2",
|
||||
// DownloadUrl = "http://testurl"
|
||||
// };
|
||||
//}
|
||||
public async Task<UpdateInfoDto> CheckForUpdates()
|
||||
{
|
||||
if (_globalContext.UpdateInfo != null)
|
||||
{
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
//if (Debugger.IsAttached)
|
||||
//{
|
||||
// return _globalContext.UpdateInfo = new UpdateInfoDto
|
||||
// {
|
||||
// UpdateAvailable = true,
|
||||
// NewVersionNumber = "10.0.0.2",
|
||||
// DownloadUrl = "http://testurl"
|
||||
// };
|
||||
//}
|
||||
|
||||
var json = await Utils.GetRestJsonAsync("https://api.github.com/repos/maassoft/colorcontrol/releases/latest");
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
var json = await Utils.GetRestJsonAsync("https://api.github.com/repos/maassoft/colorcontrol/releases/latest");
|
||||
|
||||
_globalContext.UpdateInfo = GetUpdateInfo(json);
|
||||
if (json == null)
|
||||
{
|
||||
return new UpdateInfoDto();
|
||||
}
|
||||
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
_globalContext.UpdateInfo = GetUpdateInfo(json);
|
||||
|
||||
private static UpdateInfoDto GetUpdateInfo(dynamic json)
|
||||
{
|
||||
var updateInfo = new UpdateInfoDto();
|
||||
return _globalContext.UpdateInfo;
|
||||
}
|
||||
|
||||
if (json?.tag_name == null)
|
||||
{
|
||||
return updateInfo;
|
||||
}
|
||||
private static UpdateInfoDto GetUpdateInfo(dynamic json)
|
||||
{
|
||||
var updateInfo = new UpdateInfoDto();
|
||||
|
||||
var currentVersion = Application.ProductVersion;
|
||||
var cvParts = currentVersion.Split(".");
|
||||
if (json?.tag_name == null)
|
||||
{
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
var newVersion = (string)json.tag_name.Value.Substring(1);
|
||||
var nvParts = newVersion.Split(".");
|
||||
var currentVersion = Application.ProductVersion;
|
||||
var cvParts = currentVersion.Split(".");
|
||||
|
||||
bool CompareVersions()
|
||||
{
|
||||
var result = true;
|
||||
var aNumberIsLarger = false;
|
||||
var newVersion = (string)json.tag_name.Value.Substring(1);
|
||||
var nvParts = newVersion.Split(".");
|
||||
|
||||
for (var i = 0; i < nvParts.Length; i++)
|
||||
{
|
||||
var part = Utils.ParseInt(nvParts[i]);
|
||||
var cvPart = Utils.ParseInt(cvParts[i]);
|
||||
bool CompareVersions()
|
||||
{
|
||||
var result = true;
|
||||
var aNumberIsLarger = false;
|
||||
|
||||
if (part > cvPart)
|
||||
{
|
||||
aNumberIsLarger = true;
|
||||
break;
|
||||
}
|
||||
for (var i = 0; i < nvParts.Length; i++)
|
||||
{
|
||||
var part = Utils.ParseInt(nvParts[i]);
|
||||
var cvPart = Utils.ParseInt(cvParts[i]);
|
||||
|
||||
if (part == cvPart)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (part > cvPart)
|
||||
{
|
||||
aNumberIsLarger = true;
|
||||
break;
|
||||
}
|
||||
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
if (part == cvPart)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return result && aNumberIsLarger;
|
||||
}
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
updateInfo.NewVersionNumber = newVersion;
|
||||
updateInfo.UpdateAvailable = nvParts.Length != cvParts.Length || CompareVersions();
|
||||
return result && aNumberIsLarger;
|
||||
}
|
||||
|
||||
updateInfo.HtmlUrl = json.html_url.Value;
|
||||
updateInfo.NewVersionNumber = newVersion;
|
||||
updateInfo.UpdateAvailable = nvParts.Length != cvParts.Length || CompareVersions();
|
||||
|
||||
if (json.assets != null)
|
||||
{
|
||||
var asset = json.assets[0];
|
||||
updateInfo.DownloadUrl = asset.browser_download_url.Value;
|
||||
}
|
||||
updateInfo.HtmlUrl = json.html_url.Value;
|
||||
|
||||
return updateInfo;
|
||||
}
|
||||
if (json.assets != null)
|
||||
{
|
||||
var asset = json.assets[0];
|
||||
updateInfo.DownloadUrl = asset.browser_download_url.Value;
|
||||
}
|
||||
|
||||
public async Task<bool> InstallUpdate()
|
||||
{
|
||||
if (_globalContext.UpdateInfo?.DownloadUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return updateInfo;
|
||||
}
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> InstallUpdate()
|
||||
{
|
||||
if (_globalContext.UpdateInfo?.DownloadUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var message = new SvcInstallUpdateMessage
|
||||
{
|
||||
DownloadUrl = _globalContext.UpdateInfo?.DownloadUrl,
|
||||
ClientPath = new FileInfo(Application.ExecutablePath).Directory.FullName
|
||||
};
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = await PipeUtils.SendMessageAsync(message, 30000);
|
||||
var message = new SvcInstallUpdateMessage
|
||||
{
|
||||
DownloadUrl = _globalContext.UpdateInfo?.DownloadUrl,
|
||||
ClientPath = new FileInfo(Application.ExecutablePath).Directory.FullName
|
||||
};
|
||||
|
||||
if (result != null && !result.Result)
|
||||
{
|
||||
if (Program.IsMainFormOpened())
|
||||
{
|
||||
MessageForms.ErrorOk($"Error while updating: {result.ErrorMessage}");
|
||||
}
|
||||
var result = await PipeUtils.SendMessageAsync(message, 30000);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (result != null && !result.Result)
|
||||
{
|
||||
if (Program.IsMainFormOpened())
|
||||
{
|
||||
MessageForms.ErrorOk($"Error while updating: {result.ErrorMessage}");
|
||||
}
|
||||
|
||||
await PipeUtils.SendMessageAsync(SvcMessageType.RestartAfterUpdate);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Program.IsMainFormOpened() && MessageForms.QuestionYesNo("Update installed successfully. Do you want to restart the application?") == DialogResult.Yes)
|
||||
{
|
||||
Program.Restart();
|
||||
}
|
||||
await PipeUtils.SendMessageAsync(SvcMessageType.RestartAfterUpdate);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (Program.IsMainFormOpened() && MessageForms.QuestionYesNo("Update installed successfully. Do you want to restart the application?") == DialogResult.Yes)
|
||||
{
|
||||
Program.Restart();
|
||||
}
|
||||
|
||||
public async Task<bool> RestartAfterUpdate()
|
||||
{
|
||||
Program.Restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public async Task<bool> RestartAfterUpdate()
|
||||
{
|
||||
Program.Restart();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using ColorControl.Shared.Common;
|
||||
using ColorControl.Shared.Contracts.Game;
|
||||
using ColorControl.Shared.EventDispatcher;
|
||||
using ColorControl.Shared.Services;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -330,5 +331,54 @@ namespace ColorControl.Services.GameLauncher
|
||||
|
||||
return fileInfo.FullName;
|
||||
}
|
||||
|
||||
public List<GameApp> GetMruApps(bool skipExistingPresets = false)
|
||||
{
|
||||
var key = Registry.ClassesRoot.OpenSubKey(@"Local Settings\Software\Microsoft\Windows\Shell\MuiCache");
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
return new List<GameApp>();
|
||||
}
|
||||
|
||||
var names = key.GetValueNames();
|
||||
var apps = new List<GameApp>();
|
||||
var winFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
var parts = name.Split('.');
|
||||
|
||||
if (parts.Last() != "FriendlyAppName")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var path = name.Replace("." + parts.Last(), "");
|
||||
|
||||
if (skipExistingPresets && _presets.Any(p => p.Path.Equals(path, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!path.StartsWith(winFolder) && Path.Exists(path))
|
||||
{
|
||||
var friendlyName = key.GetValue(name)?.ToString();
|
||||
|
||||
var app = new GameApp
|
||||
{
|
||||
Path = path,
|
||||
Filename = Path.GetFileName(path),
|
||||
FriendlyName = friendlyName
|
||||
};
|
||||
|
||||
apps.Add(app);
|
||||
}
|
||||
}
|
||||
|
||||
apps = apps.DistinctBy(a => a.Path).OrderBy(a => a.FriendlyName).ToList();
|
||||
|
||||
return apps;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,6 @@ using static NvAPIWrapper.Native.GPU.Structures.PrivateClockBoostLockV2;
|
||||
|
||||
namespace ColorControl.Services.NVIDIA
|
||||
{
|
||||
public enum NvThermalSensorType
|
||||
{
|
||||
Gpu = 0,
|
||||
HotSpot = 1,
|
||||
Memory = 3
|
||||
}
|
||||
|
||||
public class NvGpuInfo
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
@@ -42,6 +35,14 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
public string PCIIdentifier { get; private set; }
|
||||
|
||||
public uint GpuFrequencyInKhz { get; private set; }
|
||||
public uint MemoryFrequencyInKhz { get; private set; }
|
||||
public uint VoltageInMv { get; private set; }
|
||||
public double PowerInWatts { get; private set; }
|
||||
public double? GpuTemperature { get; private set; }
|
||||
public double? HotSpotTemperature { get; private set; }
|
||||
public double? MemoryTemperature { get; private set; }
|
||||
|
||||
|
||||
public static readonly PublicClockDomain[] Domains = new[] { PublicClockDomain.Graphics, PublicClockDomain.Memory };
|
||||
|
||||
@@ -56,17 +57,10 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
var gpu = gpus.FirstOrDefault(g => g.BusInformation.PCIIdentifiers.ToString() == pciId);
|
||||
|
||||
return GetGpuInfo(gpu);
|
||||
return new NvGpuInfo(gpu);
|
||||
}
|
||||
|
||||
public static NvGpuInfo GetGpuInfo(PhysicalGPU gpu)
|
||||
{
|
||||
var gpuInfo = new NvGpuInfo(gpu);
|
||||
|
||||
return gpuInfo;
|
||||
}
|
||||
|
||||
private NvGpuInfo(PhysicalGPU gpu)
|
||||
public NvGpuInfo(PhysicalGPU gpu)
|
||||
{
|
||||
GPU = gpu;
|
||||
PCIIdentifier = GPU.BusInformation.PCIIdentifiers.ToString();
|
||||
@@ -101,6 +95,32 @@ namespace ColorControl.Services.NVIDIA
|
||||
SetSupportedOcTypes();
|
||||
}
|
||||
|
||||
public NvGpuInfoDto ToDto()
|
||||
{
|
||||
Refresh();
|
||||
|
||||
return new NvGpuInfoDto
|
||||
{
|
||||
PCIIdentifier = PCIIdentifier,
|
||||
Name = GPU.FullName,
|
||||
ClockBoostLocks = ClockBoostLocks,
|
||||
CurveV1 = CurveV1,
|
||||
CurveV3 = CurveV3,
|
||||
DefaultPowerInMilliWatts = DefaultPowerInMilliWatts,
|
||||
VoltageInMv = VoltageInMv,
|
||||
PowerInWatts = Math.Round(PowerInWatts, 1),
|
||||
GpuFrequencyInKhz = GpuFrequencyInKhz,
|
||||
MemoryFrequencyInKhz = MemoryFrequencyInKhz,
|
||||
GpuTemperature = GpuTemperature,
|
||||
HotSpotTemperature = HotSpotTemperature,
|
||||
MemoryTemperature = MemoryTemperature,
|
||||
MaxPowerInMilliWatts = MaxPowerInMilliWatts,
|
||||
MinPowerInMilliWatts = MinPowerInMilliWatts,
|
||||
StockGraphicsClock = StockGraphicsClock,
|
||||
StockMemoryClock = StockMemoryClock
|
||||
};
|
||||
}
|
||||
|
||||
private void ReadMinMaxPower()
|
||||
{
|
||||
if (NvidiaML.IsAvailable || NvidiaML.Initialize())
|
||||
@@ -288,30 +308,42 @@ namespace ColorControl.Services.NVIDIA
|
||||
GPUApi.ClientPowerPoliciesSetStatus(GPU.Handle, status);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public void Refresh()
|
||||
{
|
||||
var gpuVoltage = GPUApi.GetCurrentVoltage(GPU.Handle).ValueInMicroVolt;
|
||||
GpuFrequencyInKhz = GPU.CurrentClockFrequencies.GraphicsClock.Frequency;
|
||||
MemoryFrequencyInKhz = GPU.CurrentClockFrequencies.MemoryClock.Frequency;
|
||||
|
||||
VoltageInMv = GPUApi.GetCurrentVoltage(GPU.Handle).ValueInMicroVolt / 1000;
|
||||
PowerInWatts = GetPowerUsageInMilliWatts() / 1000.0;
|
||||
|
||||
var temps = GetTemperatures();
|
||||
var power = GetPowerUsageInMilliWatts();
|
||||
|
||||
GpuTemperature = temps.TryGetValue(NvThermalSensorType.Gpu, out var gpuTemp) ? gpuTemp : null;
|
||||
HotSpotTemperature = temps.TryGetValue(NvThermalSensorType.HotSpot, out var hotSpotTemp) ? hotSpotTemp : null;
|
||||
MemoryTemperature = temps.TryGetValue(NvThermalSensorType.Memory, out var memoryTemp) ? memoryTemp : null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
Refresh();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"GPU: {GPU.CurrentClockFrequencies.GraphicsClock.Frequency.ToUnitString()} @ {gpuVoltage / 1000} mV, {temps[NvThermalSensorType.Gpu]}°");
|
||||
sb.Append($"GPU: {GpuFrequencyInKhz.ToUnitString()} @ {VoltageInMv} mV, {GpuTemperature}°");
|
||||
|
||||
if (temps.TryGetValue(NvThermalSensorType.HotSpot, out var hotSpotTemp))
|
||||
if (HotSpotTemperature.HasValue)
|
||||
{
|
||||
sb.Append($"/{hotSpotTemp}°");
|
||||
sb.Append($"/{HotSpotTemperature}°");
|
||||
}
|
||||
|
||||
sb.Append($", Mem: {GPU.CurrentClockFrequencies.MemoryClock.Frequency.ToUnitString()}");
|
||||
sb.Append($", Mem: {MemoryFrequencyInKhz.ToUnitString()}");
|
||||
|
||||
if (temps.TryGetValue(NvThermalSensorType.Memory, out var memoryTemp))
|
||||
if (MemoryTemperature.HasValue)
|
||||
{
|
||||
sb.Append($", {memoryTemp}°");
|
||||
sb.Append($", {MemoryTemperature}°");
|
||||
}
|
||||
|
||||
sb.Append($", Pwr: {Math.Round(power / 1000.0, 1)}W");
|
||||
sb.Append($", Pwr: {Math.Round(PowerInWatts, 1)}W");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
@@ -335,7 +367,10 @@ namespace ColorControl.Services.NVIDIA
|
||||
catch (Exception)
|
||||
{
|
||||
var gpuThermal = GPU.ThermalInformation.ThermalSensors.FirstOrDefault(s => s.Target == ThermalSettingsTarget.GPU);
|
||||
temperatures.Add(NvThermalSensorType.Gpu, gpuThermal.CurrentTemperature);
|
||||
if (gpuThermal != null)
|
||||
{
|
||||
temperatures.Add(NvThermalSensorType.Gpu, gpuThermal.CurrentTemperature);
|
||||
}
|
||||
}
|
||||
|
||||
return temperatures;
|
||||
|
||||
@@ -257,6 +257,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
}
|
||||
|
||||
var newPreset = preset.Clone();
|
||||
newPreset.IsDisplayPreset = false;
|
||||
AddOrUpdateItem(newPreset);
|
||||
}
|
||||
|
||||
@@ -1594,7 +1595,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
return;
|
||||
}
|
||||
|
||||
var gpuInfo = NvGpuInfo.GetGpuInfo(gpu);
|
||||
var gpuInfo = new NvGpuInfo(gpu);
|
||||
var currentSettings = gpuInfo.GetOverclockSettings();
|
||||
|
||||
var newSettings = AskNvOcSettings(gpuInfo, currentSettings);
|
||||
@@ -1798,7 +1799,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
return;
|
||||
}
|
||||
|
||||
var gpuInfo = NvGpuInfo.GetGpuInfo(gpu);
|
||||
var gpuInfo = new NvGpuInfo(gpu);
|
||||
var ocSettings = gpuInfo.GetOverclockSettings();
|
||||
|
||||
edtNvGpuInfo.Text = gpuInfo.ToString();
|
||||
|
||||
@@ -32,7 +32,6 @@ using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static ColorControl.Shared.Contracts.NVIDIA.NvHdrSettings;
|
||||
using static ColorControl.Shared.Native.CCD;
|
||||
|
||||
namespace ColorControl.Services.NVIDIA
|
||||
{
|
||||
@@ -158,6 +157,12 @@ namespace ColorControl.Services.NVIDIA
|
||||
LoadPresets();
|
||||
}
|
||||
|
||||
protected override void AfterPresetsLoaded()
|
||||
{
|
||||
// Force disable display preset on presets
|
||||
_presets.ForEach(p => p.IsDisplayPreset = false);
|
||||
}
|
||||
|
||||
public override void InstallEventHandlers()
|
||||
{
|
||||
SetShortcuts(SHORTCUTID_NVQA, Config.QuickAccessShortcut);
|
||||
@@ -419,7 +424,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
{
|
||||
if (preset.ColorProfileSettings.ProfileName != null)
|
||||
{
|
||||
CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName, _globalContext.Config.SetMinTmlAndMaxTml);
|
||||
CCD.SetDisplayDefaultColorProfile(display.Name, preset.ColorProfileSettings.ProfileName, _globalContext.Config.SetMinTmlAndMaxTml, newHdrEnabled);
|
||||
}
|
||||
|
||||
if (preset.scaling.HasValue)
|
||||
@@ -462,6 +467,9 @@ namespace ColorControl.Services.NVIDIA
|
||||
result = ApplyOverclocking(preset.ocSettings);
|
||||
}
|
||||
|
||||
//SetMonitorScaling();
|
||||
//CCD.SetScaling(100);
|
||||
|
||||
_lastAppliedPreset = preset;
|
||||
|
||||
preset.IsStartupPreset = false;
|
||||
@@ -627,13 +635,27 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
public void SetHDRState(Display display, bool enabled)
|
||||
{
|
||||
if (!enabled && Hdr10OutputModeForced.HasValue)
|
||||
{
|
||||
SetOutputMode(NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_SDR, display);
|
||||
Hdr10OutputModeForced = null;
|
||||
}
|
||||
|
||||
CCD.SetHDRState(enabled, display.Name);
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
if (Hdr10OutputModeForced.HasValue)
|
||||
{
|
||||
SetOutputMode(NV_DISPLAY_OUTPUT_MODE.NV_DISPLAY_OUTPUT_MODE_SDR, display);
|
||||
Hdr10OutputModeForced = null;
|
||||
}
|
||||
|
||||
// Sometimes HDR doesn't turn off properly via CCD. Force it using NvApi.
|
||||
Logger.Swallow(() =>
|
||||
{
|
||||
var hdrColorData = display.DisplayDevice.HDRColorData;
|
||||
if (hdrColorData != null && hdrColorData.HDRMode != ColorDataHDRMode.Off)
|
||||
{
|
||||
hdrColorData.HDRMode = ColorDataHDRMode.Off;
|
||||
display.DisplayDevice.SetHDRColorData(hdrColorData);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDigitalVibranceLevel(Display display, int level)
|
||||
@@ -1206,7 +1228,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
public void SetColorProfile(Display display, string name)
|
||||
{
|
||||
CCD.SetDisplayDefaultColorProfile(display.Name, name, _globalContext.Config.SetMinTmlAndMaxTml);
|
||||
CCD.SetDisplayDefaultColorProfile(display.Name, name, _globalContext.Config.SetMinTmlAndMaxTml, IsHDREnabled(display));
|
||||
}
|
||||
|
||||
public void TestResolution()
|
||||
@@ -1348,6 +1370,15 @@ namespace ColorControl.Services.NVIDIA
|
||||
return gpus;
|
||||
}
|
||||
|
||||
public List<NvGpuInfoDto> GetGpuInfos()
|
||||
{
|
||||
var gpus = GetGPUs();
|
||||
var infos = gpus.Select(g => new NvGpuInfo(g));
|
||||
var dtos = infos.Select(i => i.ToDto()).ToList();
|
||||
|
||||
return dtos;
|
||||
}
|
||||
|
||||
public List<NvDisplayInfo> GetSimpleDisplayInfos()
|
||||
{
|
||||
var displays = GetDisplays();
|
||||
@@ -1457,7 +1488,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
preset.HdmiInfoFrameSettings = GetHdmiSettings(display);
|
||||
preset.SDRBrightness = GetSDRBrightness(display.Name);
|
||||
preset.scaling = GetScaling(display);
|
||||
preset.ColorProfileSettings.ProfileName = CCD.GetDisplayDefaultColorProfile(display.Name, preset.HDREnabled ? COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE : COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE);
|
||||
preset.ColorProfileSettings.ProfileName = CCD.GetDisplayDefaultColorProfile(display.Name, preset.HDREnabled);
|
||||
|
||||
preset.DisplayConfig = CCD.GetDisplayConfig(display.Name);
|
||||
|
||||
@@ -1482,7 +1513,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
foreach (var gpu in gpus)
|
||||
{
|
||||
var gpuInfo = NvGpuInfo.GetGpuInfo(gpu);
|
||||
var gpuInfo = new NvGpuInfo(gpu);
|
||||
|
||||
var settings = gpuInfo.GetOverclockSettings();
|
||||
|
||||
@@ -1620,30 +1651,13 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
private List<string> GetProfileNamesOfMru()
|
||||
{
|
||||
var key = Registry.ClassesRoot.OpenSubKey(@"Local Settings\Software\Microsoft\Windows\Shell\MuiCache");
|
||||
|
||||
if (key == null)
|
||||
if (_serviceManager.GameService == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var names = key.GetValueNames();
|
||||
var appNames = new List<string>();
|
||||
var winFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
var parts = name.Split('.');
|
||||
|
||||
var path = name.Replace("." + parts.Last(), "");
|
||||
|
||||
if (!path.StartsWith(winFolder) && Path.Exists(path))
|
||||
{
|
||||
appNames.Add(Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
|
||||
appNames = appNames.Distinct().ToList();
|
||||
var apps = _serviceManager.GameService.GetMruApps();
|
||||
var appNames = apps.Select(a => a.Filename).ToList();
|
||||
|
||||
var profiles = DrsServiceLocator.SettingService.GetProfileNamesByApps(appNames);
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace ColorControl.XForms
|
||||
|
||||
var profileName = Path.GetFileName(tempFilename);
|
||||
|
||||
if (CCD.SetDisplayDefaultColorProfile(displayName, profileName, _viewModel.SetMinMaxTml))
|
||||
if (CCD.SetDisplayDefaultColorProfile(displayName, profileName, _viewModel.SetMinMaxTml, _viewModel.IsHDR))
|
||||
{
|
||||
if (!_viewModel.ExistingProfiles.Contains(profileName))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using NvAPIWrapper.Native.Display;
|
||||
using NvAPIWrapper.Native.Display;
|
||||
using NvAPIWrapper.Native.Display.Structures;
|
||||
using NvAPIWrapper.Native.Interfaces.Display;
|
||||
using System;
|
||||
|
||||
namespace NvAPIWrapper.Display
|
||||
{
|
||||
@@ -70,7 +70,7 @@ namespace NvAPIWrapper.Display
|
||||
public ColorDataDynamicRange? DynamicRange { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ColorDataHDRMode HDRMode { get; }
|
||||
public ColorDataHDRMode HDRMode { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public MasteringDisplayColorData MasteringDisplayData { get; }
|
||||
@@ -115,7 +115,7 @@ namespace NvAPIWrapper.Display
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals((HDRColorData) obj);
|
||||
return Equals((HDRColorData)obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -126,7 +126,7 @@ namespace NvAPIWrapper.Display
|
||||
var hashCode = ColorDepth.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ ColorFormat.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ DynamicRange.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ (int) HDRMode;
|
||||
hashCode = (hashCode * 397) ^ (int)HDRMode;
|
||||
hashCode = (hashCode * 397) ^ MasteringDisplayData.GetHashCode();
|
||||
|
||||
return hashCode;
|
||||
|
||||
@@ -755,5 +755,11 @@ The best and suggested method to provide this is via a Windows Service. Only whe
|
||||
|
||||
public static string ToUnitString(this uint value, int div = 1000, string units = "MHz") => $"{value / div}{units}";
|
||||
public static string ToUnitString(this int value, int div = 1000, string units = "MHz") => $"{value / div}{units}";
|
||||
public static string ToUnitString(string units = "°", params double?[] values)
|
||||
{
|
||||
var notNullValues = values.Where(v => v.HasValue).Select(v => $"{v}{units}");
|
||||
|
||||
return string.Join("/", notNullValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace ColorControl.Shared.Contracts.Game;
|
||||
|
||||
public class GameApp
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string Filename { get; set; }
|
||||
public string FriendlyName { get; set; }
|
||||
}
|
||||
@@ -4,42 +4,49 @@ namespace ColorControl.Shared.Contracts.NVIDIA;
|
||||
|
||||
public enum NvDitherState
|
||||
{
|
||||
[Description("Auto")]
|
||||
Auto = 0,
|
||||
[Description("Enabled")]
|
||||
Enabled = 1,
|
||||
[Description("Disabled")]
|
||||
Disabled = 2
|
||||
[Description("Auto")]
|
||||
Auto = 0,
|
||||
[Description("Enabled")]
|
||||
Enabled = 1,
|
||||
[Description("Disabled")]
|
||||
Disabled = 2
|
||||
}
|
||||
|
||||
public enum NvDitherBits
|
||||
{
|
||||
[Description("6-bit")]
|
||||
Bits6 = 0,
|
||||
[Description("8-bit")]
|
||||
Bits8 = 1,
|
||||
[Description("10-bit")]
|
||||
Bits10 = 2
|
||||
[Description("6-bit")]
|
||||
Bits6 = 0,
|
||||
[Description("8-bit")]
|
||||
Bits8 = 1,
|
||||
[Description("10-bit")]
|
||||
Bits10 = 2
|
||||
}
|
||||
|
||||
public enum NvDitherMode
|
||||
{
|
||||
[Description("Spatial Dynamic")]
|
||||
SpatialDynamic = 0,
|
||||
[Description("Spatial Static")]
|
||||
SpatialStatic = 1,
|
||||
[Description("Spatial Dynamic 2x2")]
|
||||
SpatialDynamic2x2 = 2,
|
||||
[Description("Spatial Static 2x2")]
|
||||
SpatialStatic2x2 = 3,
|
||||
[Description("Temporal")]
|
||||
Temporal = 4
|
||||
[Description("Spatial Dynamic")]
|
||||
SpatialDynamic = 0,
|
||||
[Description("Spatial Static")]
|
||||
SpatialStatic = 1,
|
||||
[Description("Spatial Dynamic 2x2")]
|
||||
SpatialDynamic2x2 = 2,
|
||||
[Description("Spatial Static 2x2")]
|
||||
SpatialStatic2x2 = 3,
|
||||
[Description("Temporal")]
|
||||
Temporal = 4
|
||||
}
|
||||
|
||||
public enum NovideoColorSpace
|
||||
{
|
||||
sRGB,
|
||||
DisplayP3,
|
||||
AdobeRGB,
|
||||
BT2020
|
||||
sRGB,
|
||||
DisplayP3,
|
||||
AdobeRGB,
|
||||
BT2020
|
||||
}
|
||||
|
||||
public enum NvThermalSensorType
|
||||
{
|
||||
Gpu = 0,
|
||||
HotSpot = 1,
|
||||
Memory = 3
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using NvAPIWrapper.Native.GPU.Structures;
|
||||
using static NvAPIWrapper.Native.GPU.Structures.PrivateClockBoostLockV2;
|
||||
|
||||
namespace ColorControl.Shared.Contracts.NVIDIA;
|
||||
|
||||
public class NvGpuInfoDto
|
||||
{
|
||||
public List<ClockBoostLock> ClockBoostLocks { get; set; } = new();
|
||||
public ClockDomainInfo StockGraphicsClock { get; set; }
|
||||
public ClockDomainInfo StockMemoryClock { get; set; }
|
||||
public PrivateVFPCurveV3 CurveV3 { get; set; }
|
||||
public PrivateVFPCurveV1 CurveV1 { get; set; }
|
||||
public int? DefaultPowerInMilliWatts { get; set; }
|
||||
public int MinPowerInMilliWatts { get; set; }
|
||||
public int MaxPowerInMilliWatts { get; set; }
|
||||
public int MinCoreStepInMHz { get; set; } = 15;
|
||||
public List<NvGpuOcType> SupportedOcTypes { get; set; } = new();
|
||||
|
||||
public string PCIIdentifier { get; set; }
|
||||
public string Name { get; set; }
|
||||
public uint GpuFrequencyInKhz { get; set; }
|
||||
public uint MemoryFrequencyInKhz { get; set; }
|
||||
public uint VoltageInMv { get; set; }
|
||||
public double PowerInWatts { get; set; }
|
||||
public double? GpuTemperature { get; set; }
|
||||
public double? HotSpotTemperature { get; set; }
|
||||
public double? MemoryTemperature { get; set; }
|
||||
}
|
||||
+524
-524
File diff suppressed because it is too large.
Load diff
+101
-16
@@ -325,12 +325,12 @@ namespace ColorControl.Shared.Native
|
||||
|
||||
private static bool MinRequiredVersion = true;
|
||||
|
||||
public static bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true)
|
||||
public static bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true, bool isHdrColorProfile = false)
|
||||
{
|
||||
var adapterId = GetAdapterId(displayName);
|
||||
var sourceId = GetSourceId(displayName);
|
||||
|
||||
var err = NativeMethods.ColorProfileAddDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, setAsDefault, true);
|
||||
var err = NativeMethods.ColorProfileAddDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, setAsDefault, isHdrColorProfile);
|
||||
|
||||
if (err != NativeConstants.ERROR_SUCCESS)
|
||||
{
|
||||
@@ -340,12 +340,12 @@ namespace ColorControl.Shared.Native
|
||||
return err == NativeConstants.ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
public static bool RemoveDisplayColorProfile(string displayName, string profileName)
|
||||
public static bool RemoveDisplayColorProfile(string displayName, string profileName, bool isHdrColorProfile = false)
|
||||
{
|
||||
var adapterId = GetAdapterId(displayName);
|
||||
var sourceId = GetSourceId(displayName);
|
||||
|
||||
var err = NativeMethods.ColorProfileRemoveDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, true);
|
||||
var err = NativeMethods.ColorProfileRemoveDisplayAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, adapterId, sourceId, isHdrColorProfile);
|
||||
|
||||
if (err != NativeConstants.ERROR_SUCCESS)
|
||||
{
|
||||
@@ -355,26 +355,26 @@ namespace ColorControl.Shared.Native
|
||||
return err == NativeConstants.ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
public static bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true)
|
||||
public static bool SetDisplayDefaultColorProfile(string displayName, string profileName, bool fixMaxTML = true, bool isHdrColorProfile = false)
|
||||
{
|
||||
var adapterId = GetAdapterId(displayName);
|
||||
var sourceId = GetSourceId(displayName);
|
||||
|
||||
var subType = COLORPROFILESUBTYPE.CPST_PERCEPTUAL;
|
||||
|
||||
if (profileName == null)
|
||||
{
|
||||
profileName = GetDisplayDefaultColorProfile(displayName, COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE);
|
||||
profileName = GetDisplayDefaultColorProfile(displayName, isHdrColorProfile);
|
||||
|
||||
if (profileName == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return RemoveDisplayColorProfile(displayName, profileName) && AddDisplayColorProfile(displayName, profileName, false);
|
||||
return RemoveDisplayColorProfile(displayName, profileName, isHdrColorProfile) && AddDisplayColorProfile(displayName, profileName, false, isHdrColorProfile);
|
||||
}
|
||||
|
||||
var err = NativeMethods.ColorProfileSetDisplayDefaultAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, COLORPROFILETYPE.CPT_ICC, subType, adapterId, sourceId);
|
||||
var profileSubType = GetColorProfileSubTypeByDynamicRange(isHdrColorProfile);
|
||||
|
||||
var err = NativeMethods.ColorProfileSetDisplayDefaultAssociation(WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER, profileName, COLORPROFILETYPE.CPT_ICC, profileSubType, adapterId, sourceId);
|
||||
|
||||
if (err != NativeConstants.ERROR_SUCCESS)
|
||||
{
|
||||
@@ -394,7 +394,7 @@ namespace ColorControl.Shared.Native
|
||||
return err == NativeConstants.ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
public static string GetDisplayDefaultColorProfile(string displayName, COLORPROFILESUBTYPE profileSubType = COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE)
|
||||
public static string GetDisplayDefaultColorProfile(string displayName, bool isHdrColorProfile)
|
||||
{
|
||||
if (!MinRequiredVersion)
|
||||
{
|
||||
@@ -404,6 +404,8 @@ namespace ColorControl.Shared.Native
|
||||
var adapterId = GetAdapterId(displayName);
|
||||
var sourceId = GetSourceId(displayName);
|
||||
|
||||
var profileSubType = GetColorProfileSubTypeByDynamicRange(isHdrColorProfile);
|
||||
|
||||
var scope = GetUsePerUserDisplayProfiles(displayName) ? WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE.WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
|
||||
try
|
||||
{
|
||||
@@ -431,6 +433,11 @@ namespace ColorControl.Shared.Native
|
||||
return null;
|
||||
}
|
||||
|
||||
public static COLORPROFILESUBTYPE GetColorProfileSubTypeByDynamicRange(bool isHdr)
|
||||
{
|
||||
return isHdr ? COLORPROFILESUBTYPE.CPST_EXTENDED_DISPLAY_COLOR_MODE : COLORPROFILESUBTYPE.CPST_STANDARD_DISPLAY_COLOR_MODE;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct UnmanagedStruct
|
||||
{
|
||||
@@ -652,6 +659,78 @@ namespace ColorControl.Shared.Native
|
||||
);
|
||||
}
|
||||
|
||||
public static DISPLAYCONFIG_SOURCE_DPI_SCALE_GET GetScaling(string displayName = null)
|
||||
{
|
||||
return ExecuteForModeConfig((modeInfo) =>
|
||||
{
|
||||
if (modeInfo.infoType == DisplayConfigModeInfoType.Source)
|
||||
{
|
||||
var requestPacket = new DISPLAYCONFIG_SOURCE_DPI_SCALE_GET();
|
||||
requestPacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
|
||||
requestPacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE;
|
||||
requestPacket.header.size = Marshal.SizeOf<DISPLAYCONFIG_SOURCE_DPI_SCALE_GET>();
|
||||
|
||||
requestPacket.header.adapterId = modeInfo.adapterId;
|
||||
requestPacket.header.id = modeInfo.id;
|
||||
|
||||
var err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestPacket);
|
||||
|
||||
if (err == NativeConstants.ERROR_SUCCESS)
|
||||
{
|
||||
return (requestPacket, false);
|
||||
}
|
||||
}
|
||||
|
||||
return (default, true);
|
||||
}, displayName
|
||||
);
|
||||
}
|
||||
|
||||
public static bool SetScaling(int percentage, string displayName = null)
|
||||
{
|
||||
var dpiInfo = GetScaling(displayName);
|
||||
|
||||
if (percentage == dpiInfo.curScaleRel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (percentage < dpiInfo.minScaleRel)
|
||||
{
|
||||
percentage = dpiInfo.minScaleRel;
|
||||
}
|
||||
else if (percentage > dpiInfo.maxScaleRel)
|
||||
{
|
||||
percentage = dpiInfo.maxScaleRel;
|
||||
}
|
||||
|
||||
return ExecuteForModeConfig((modeInfo) =>
|
||||
{
|
||||
if (modeInfo.infoType == DisplayConfigModeInfoType.Source)
|
||||
{
|
||||
var setPacket = new DISPLAYCONFIG_SOURCE_DPI_SCALE_SET();
|
||||
setPacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
|
||||
setPacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE;
|
||||
setPacket.header.size = Marshal.SizeOf<DISPLAYCONFIG_SOURCE_DPI_SCALE_SET>();
|
||||
|
||||
setPacket.header.adapterId = modeInfo.adapterId;
|
||||
setPacket.header.id = modeInfo.id;
|
||||
|
||||
setPacket.scaleRel = -7;
|
||||
|
||||
var err = NativeMethods.DisplayConfigSetDeviceInfo(ref setPacket);
|
||||
|
||||
if (err == NativeConstants.ERROR_SUCCESS)
|
||||
{
|
||||
return (true, false);
|
||||
}
|
||||
}
|
||||
|
||||
return (false, true);
|
||||
}, displayName
|
||||
);
|
||||
}
|
||||
|
||||
private static T ExecuteForModeConfig<T>(DisplayConfigModeInfoDelegate<T> infoDelegate, string displayName = null, QueryDisplayFlags queryDisplayFlags = QueryDisplayFlags.OnlyActivePaths)
|
||||
{
|
||||
uint pathCount, modeCount;
|
||||
@@ -921,14 +1000,14 @@ namespace ColorControl.Shared.Native
|
||||
return displayName1.Equals(displayName2, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private enum DISPLAYCONFIG_MODE_INFO_TYPE
|
||||
public enum DISPLAYCONFIG_MODE_INFO_TYPE
|
||||
{
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3,
|
||||
}
|
||||
|
||||
private enum DISPLAYCONFIG_DEVICE_INFO_TYPE
|
||||
public enum DISPLAYCONFIG_DEVICE_INFO_TYPE
|
||||
{
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE = -3,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE = -4,
|
||||
@@ -950,7 +1029,7 @@ namespace ColorControl.Shared.Native
|
||||
private const uint DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_PARAM = 0xFFFFFFF0;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DISPLAYCONFIG_DEVICE_INFO_HEADER
|
||||
public struct DISPLAYCONFIG_DEVICE_INFO_HEADER
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_TYPE type;
|
||||
public int size;
|
||||
@@ -1402,7 +1481,7 @@ namespace ColorControl.Shared.Native
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET
|
||||
public struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
/*
|
||||
@@ -1423,7 +1502,7 @@ namespace ColorControl.Shared.Native
|
||||
public int maxScaleRel;
|
||||
};
|
||||
|
||||
private struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET
|
||||
public struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
/*
|
||||
@@ -1520,6 +1599,12 @@ namespace ColorControl.Shared.Native
|
||||
[DllImport("user32")]
|
||||
public static extern int DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SET_ADVANCED_COLOR_PARAM setPacket);
|
||||
|
||||
[DllImport("user32")]
|
||||
public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DPI_SCALE_GET requestPacket);
|
||||
|
||||
[DllImport("user32")]
|
||||
public static extern int DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setPacket);
|
||||
|
||||
[DllImport("user32")]
|
||||
public static extern uint GetDpiForSystem();
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ public class RpcServerService
|
||||
{
|
||||
if (arg.GetType().Name == "JObject" || arg.GetType().Name == "JArray")
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject(arg.ToString(), type);
|
||||
var jsonString = arg.ToString();
|
||||
var obj = jsonString == "{}" ? Type.Missing : JsonConvert.DeserializeObject(jsonString, type);
|
||||
list.Add(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user