mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-18 23:07:15 -05:00
fix Nvidia crash, remove UWP dependencies
This commit is contained in:
1 parent
d31d2f321f
commit
6e206a6728
40 files changed
+8544
-8449
No files matched your search
@@ -1,14 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputType>Library</OutputType>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -251,6 +251,17 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="shadowDetailBoost">Shadow detail boost (%)</label>
|
||||
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,12 +33,12 @@ else
|
||||
{
|
||||
<span>No displays found</span>
|
||||
}
|
||||
@foreach (var display in Displays)
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="device">Selected display</label>
|
||||
<select class="form-select" id="device" @bind="SelectedDisplayId" @bind:after="SelectedDisplayChanged">
|
||||
@foreach (var device in Displays)
|
||||
@foreach (var display in Displays)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.DisplayName @@ @display.AdapterName</option>
|
||||
}
|
||||
|
||||
@@ -7,57 +7,133 @@
|
||||
|
||||
@if (Label != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">@Label</label>
|
||||
<input class="form-control" type="text" readonly value="@Shortcut"
|
||||
@onchange="(ChangeEventArgs e) => ShortcutOnChange(e)"
|
||||
@onkeydown="ShortcutOnKeyDown"
|
||||
@onkeyup="(KeyboardEventArgs e) => ShortcutOnKeyUp(e)"
|
||||
@onfocus="ShortcutOnFocus"
|
||||
@onblur="ShortcutOnBlur"/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">@Label</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" readonly value="@Shortcut"
|
||||
@onchange="(ChangeEventArgs e) => ShortcutOnChange(e)"
|
||||
@onkeydown="ShortcutOnKeyDown"
|
||||
@onkeyup="(KeyboardEventArgs e) => ShortcutOnKeyUp(e)"
|
||||
@onfocus="ShortcutOnFocus"
|
||||
@onblur="ShortcutOnBlur"/>
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var modifier in Modifiers) {
|
||||
<li>
|
||||
<a class="dropdown-item" @onclick="() => ModifierOnClick(modifier.Key)">
|
||||
<input class="form-check-input me-2" type="checkbox" id="flexCheckChecked" checked="@modifier.Value" @onchange="(e) => ModifierKeyChanged(e, modifier.Key)">@modifier.Key
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Shortcut { get; set; }
|
||||
[Parameter]
|
||||
public string? Shortcut { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string?> ShortcutChanged { get; set; }
|
||||
[Parameter]
|
||||
public EventCallback<string?> ShortcutChanged { get; set; }
|
||||
|
||||
private async Task ShortcutOnChange(ChangeEventArgs e)
|
||||
{
|
||||
Shortcut = e.Value?.ToString();
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
private Dictionary<string, bool> Modifiers = new() {
|
||||
{ "Shift", false },
|
||||
{ "Control", false },
|
||||
{ "Alt", false },
|
||||
{ "Win", false }
|
||||
};
|
||||
|
||||
private void ShortcutOnKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Tab")
|
||||
{
|
||||
return;
|
||||
}
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
LoadModifiers();
|
||||
}
|
||||
|
||||
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
|
||||
Shortcut = text;
|
||||
}
|
||||
private async Task ShortcutOnChange(ChangeEventArgs e)
|
||||
{
|
||||
Shortcut = e.Value?.ToString();
|
||||
LoadModifiers();
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
|
||||
private async Task ShortcutOnKeyUp(KeyboardEventArgs e)
|
||||
{
|
||||
//KeyboardShortcutDispatcher.HandleKeyboardShortcutUp()
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
private void ShortcutOnKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Tab")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void ShortcutOnFocus(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = false;
|
||||
}
|
||||
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
|
||||
Shortcut = text;
|
||||
}
|
||||
|
||||
private void ShortcutOnBlur(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = true;
|
||||
}
|
||||
private async Task ShortcutOnKeyUp(KeyboardEventArgs e)
|
||||
{
|
||||
//KeyboardShortcutDispatcher.HandleKeyboardShortcutUp()
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
|
||||
private void ShortcutOnFocus(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = false;
|
||||
}
|
||||
|
||||
private void ShortcutOnBlur(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = true;
|
||||
}
|
||||
|
||||
private void LoadModifiers()
|
||||
{
|
||||
foreach (var modifier in Modifiers.Keys)
|
||||
{
|
||||
Modifiers[modifier] = Shortcut?.Contains(modifier) == true;
|
||||
}
|
||||
}
|
||||
private async Task ModifierKeyChanged(ChangeEventArgs e, string modifier)
|
||||
{
|
||||
var value = e.Value is bool boolValue && boolValue;
|
||||
|
||||
Modifiers[modifier] = value;
|
||||
|
||||
await SetModifiers();
|
||||
}
|
||||
|
||||
private async Task SetModifiers()
|
||||
{
|
||||
var shortcut = Shortcut ?? "";
|
||||
foreach (var modifier in Modifiers.Keys)
|
||||
{
|
||||
shortcut = shortcut.Replace(modifier, "").Trim().TrimStart(',');
|
||||
}
|
||||
|
||||
shortcut = shortcut.Trim();
|
||||
|
||||
foreach (var modifier in Modifiers.Where(kv => kv.Value).Select(kv => kv.Key).Reverse())
|
||||
{
|
||||
if (shortcut.StartsWith("+"))
|
||||
{
|
||||
shortcut = $"{modifier} {shortcut}";
|
||||
}
|
||||
else
|
||||
{
|
||||
shortcut = $"{modifier}, {shortcut}";
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut = shortcut;
|
||||
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
|
||||
private async Task ModifierOnClick(string modifier)
|
||||
{
|
||||
Modifiers[modifier] = !Modifiers[modifier];
|
||||
|
||||
await SetModifiers();
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,8 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="me-auto">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="SetDitherRegistryKeyOnClick">Set dither registry key</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="RestartDriverOnClick">Restart driver</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="SetDitherRegistryKeyOnClick">Set dither registry key</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="RestartDriverOnClick">Restart driver</button>
|
||||
</span>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
@inject JSHelper jsHelper;
|
||||
|
||||
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
|
||||
@@ -70,10 +71,10 @@
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Display</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.DisplayId" disabled="@(Preset.primaryDisplay)">
|
||||
@foreach (var display in displayPresets)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.displayName</option>
|
||||
}
|
||||
@foreach (var display in displayPresets)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.displayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
@@ -356,7 +357,7 @@
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bits">Bit depth</label>
|
||||
<select class="form-select" id="bits" @bind="Preset.NvDitherBits">
|
||||
<select class="form-select" id="bits" @bind="Preset.NvDitherBits">
|
||||
@foreach (var bits in Enum.GetValues<NvDitherBits>())
|
||||
{
|
||||
<option value="@bits.ToString()">@bits.GetDescription()</option>
|
||||
@@ -544,7 +545,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
|
||||
<button type="submit" class="btn btn-primary" disabled="@(!IsValid())" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -667,12 +668,20 @@
|
||||
else
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdatePreset", Preset);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
|
||||
await jsHelper.CloseModal("presetModal");
|
||||
IsVisible = false;
|
||||
}
|
||||
|
||||
private async Task ApplyDisplayPreset()
|
||||
|
||||
@@ -90,6 +90,26 @@ else
|
||||
<br />
|
||||
<small class="text-body">Color profile: @preset.ColorProfileSettings.ProfileName</small>
|
||||
}
|
||||
@if (preset.driverSettings?.Any() == true)
|
||||
{
|
||||
<br />
|
||||
<div class="dropdown">
|
||||
<span role="button" class="text-decoration-underline dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside" @onclick="() => DriverSettingsOnClick(preset)">
|
||||
<small>@preset.driverSettings.Count() driver settings changed</small>
|
||||
</span>
|
||||
<form class="dropdown-menu">
|
||||
@if (DriverSettings != null)
|
||||
{
|
||||
<ul class="list-group p-0">
|
||||
@foreach (var setting in DriverSettings)
|
||||
{
|
||||
<li class="list-group-item py-0 px-1"><small>@setting.FriendlyName</small></li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,6 +278,7 @@ else
|
||||
private NvPreset? CurrentGpuSettingsPreset;
|
||||
private NvGpuInfoDto? CurrentGpuInfo;
|
||||
private bool DitheringVisible;
|
||||
private List<NvSettingItemDto>? DriverSettings;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
@@ -371,4 +392,14 @@ else
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
|
||||
private async Task DriverSettingsOnClick(NvPreset preset)
|
||||
{
|
||||
var driverSettings = await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", Type.Missing, true);
|
||||
|
||||
if (driverSettings != null)
|
||||
{
|
||||
DriverSettings = driverSettings.Where(s => s.Value != s.DefaultValue && s.State == 2).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,7 +301,7 @@ else
|
||||
|
||||
private async Task ElevationMethodInput(ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value != null ? (ElevationMethod)int.Parse((string)e.Value) : ElevationMethod.None;
|
||||
var value = e.Value != null ? Enum.Parse<ElevationMethod>((string)e.Value) : ElevationMethod.None;
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetElevationMethod", value);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
@@ -18,8 +18,8 @@
|
||||
<PublisherName>Maassoft</PublisherName>
|
||||
<Company>Maassoft</Company>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>10.1.0.0</ApplicationVersion>
|
||||
<Version>10.1.0.0</Version>
|
||||
<ApplicationVersion>10.2.0.0</ApplicationVersion>
|
||||
<Version>10.2.0.0</Version>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
@@ -28,8 +28,7 @@
|
||||
<UseWPF>false</UseWPF>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<ApplicationIcon>Resources\pngbarn.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||
<Copyright>2020-2024 Maassoft</Copyright>
|
||||
<Copyright>2020-2025 Maassoft</Copyright>
|
||||
<Authors>Maassoft</Authors>
|
||||
<ManifestCertificateThumbprint>CF0BC153EA664CD97AC6F234B91F45D34630958F</ManifestCertificateThumbprint>
|
||||
<ManifestKeyFile>ColorControl_1_TemporaryKey.pfx</ManifestKeyFile>
|
||||
@@ -39,6 +38,7 @@
|
||||
<SignManifests>true</SignManifests>
|
||||
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
@@ -106,17 +106,15 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2277.86" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="NStandard" Version="0.59.0" />
|
||||
<PackageReference Include="NWin32" Version="1.2.6" />
|
||||
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.10.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.17" />
|
||||
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NStandard" Version="0.83.1" />
|
||||
<PackageReference Include="NWin32" Version="1.2.8" />
|
||||
<PackageReference Include="System.Management" Version="9.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.11.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Include="XForms\ColorProfileWindow.xaml">
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Security;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LgTv
|
||||
{
|
||||
public class SendMessageExceptionCws : Exception
|
||||
{
|
||||
public SendMessageExceptionCws(string message, Exception e) : base(message, e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void IsConnectedDelegateCws(bool status);
|
||||
|
||||
public class LgTvApiCoreCws : IAsyncDisposable
|
||||
{
|
||||
public bool ConnectionClosed { get; private set; }
|
||||
|
||||
private ClientWebSocket _clientWebSocket;
|
||||
private int _commandCount;
|
||||
private Uri _uri;
|
||||
|
||||
public event IsConnectedDelegateCws IsConnected;
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<dynamic>> _tokens = new ConcurrentDictionary<string, TaskCompletionSource<dynamic>>();
|
||||
private readonly ConcurrentDictionary<string, Func<dynamic, bool>> _callbacks = new ConcurrentDictionary<string, Func<dynamic, bool>>();
|
||||
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public async Task<bool> Connect(Uri uri, bool ignoreReceiver = false)
|
||||
{
|
||||
_uri = uri;
|
||||
|
||||
try
|
||||
{
|
||||
ConnectionClosed = false;
|
||||
_commandCount = 0;
|
||||
CreateWebSocket();
|
||||
|
||||
try
|
||||
{
|
||||
await _clientWebSocket.ConnectAsync(uri, CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(5000));
|
||||
ConnectionClosed = false;
|
||||
|
||||
var _ = Receive(false);
|
||||
|
||||
IsConnected?.Invoke(true);
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Logger.Error($"Timeout while connecting to {uri}: {te.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Connect to {uri}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebSocket()
|
||||
{
|
||||
_clientWebSocket = new ClientWebSocket();
|
||||
_clientWebSocket.Options.RemoteCertificateValidationCallback += CertificationValidationCallback;
|
||||
}
|
||||
|
||||
private bool CertificationValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) => true;
|
||||
|
||||
public async Task SendMessageAsync(string message, bool reconnect = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_clientWebSocket?.State != WebSocketState.Open)
|
||||
{
|
||||
throw new InvalidOperationException("Socket not open");
|
||||
}
|
||||
var bytes = UTF8Encoding.UTF8.GetBytes(message);
|
||||
var data = bytes.AsMemory();
|
||||
|
||||
await _clientWebSocket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
if (reconnect && await Connect(_uri))
|
||||
{
|
||||
await SendMessageAsync(message, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("SendMessageAsync: " + e.Message);
|
||||
ConnectionClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<dynamic> SendCommandAsync(string message)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
return await SendCommandAsync((string)obj.id, message);
|
||||
}
|
||||
|
||||
private async Task<dynamic> SendCommandAsync(string id, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var taskSource = new TaskCompletionSource<dynamic>();
|
||||
_tokens.TryAdd(id, taskSource);
|
||||
await SendMessageAsync(message);
|
||||
if (ConnectionClosed)
|
||||
{
|
||||
throw new Exception("Connection closed");
|
||||
}
|
||||
|
||||
return await taskSource.Task;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SendMessageExceptionCws("Can't send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<dynamic> SendCommandAsync(RequestMessage message)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
return await SendCommandAsync(rawMessage.Id, serialized);
|
||||
}
|
||||
|
||||
public async Task<dynamic> SubscribeAsync(RequestMessage message, Func<dynamic, bool> callback)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
|
||||
_callbacks.TryAdd(rawMessage.Id, callback);
|
||||
|
||||
return await SendCommandAsync(rawMessage.Id, serialized);
|
||||
}
|
||||
|
||||
private async Task Receive(bool singleResponse)
|
||||
{
|
||||
var dataPerPacket = 8 * 1024; //8192
|
||||
var strBuilder = new StringBuilder();
|
||||
var buffer = new ArraySegment<byte>(new byte[dataPerPacket]);
|
||||
|
||||
try
|
||||
{
|
||||
while (_clientWebSocket?.State == WebSocketState.Open)
|
||||
{
|
||||
var receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
|
||||
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
|
||||
//keep doing until end of message
|
||||
while (!receiveResult.EndOfMessage)
|
||||
{
|
||||
receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
|
||||
}
|
||||
|
||||
var message = strBuilder.ToString();
|
||||
strBuilder.Clear();
|
||||
|
||||
HandleMessage(message);
|
||||
|
||||
if (singleResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ConnectionClosed = true;
|
||||
Logger.Debug("Connection closed");
|
||||
IsConnected?.Invoke(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleMessage(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
var id = (string)obj.id;
|
||||
var type = (string)obj.type;
|
||||
|
||||
TaskCompletionSource<dynamic> taskCompletion;
|
||||
if (type == "registered")
|
||||
{
|
||||
if (_tokens.TryRemove(id, out taskCompletion))
|
||||
{
|
||||
var key = (string)JObject.Parse(message)["payload"]["client-key"];
|
||||
taskCompletion.TrySetResult(new { clientKey = key });
|
||||
}
|
||||
|
||||
}
|
||||
else if (_tokens.TryGetValue(id, out taskCompletion))
|
||||
{
|
||||
if (id == "register_0")
|
||||
{
|
||||
//taskCompletion.TrySetResult(obj.payload);
|
||||
return;
|
||||
}
|
||||
if (obj.type == "error")
|
||||
{
|
||||
taskCompletion.SetException(new Exception(obj.error?.ToString()));
|
||||
}
|
||||
//else if (args.Cancelled)
|
||||
//{
|
||||
// taskSource.SetCanceled();
|
||||
//}
|
||||
taskCompletion.TrySetResult(obj.payload);
|
||||
|
||||
if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(obj.payload);
|
||||
}
|
||||
catch (Exception callbackException)
|
||||
{
|
||||
Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"HandleMessage: status: {_clientWebSocket.State}, exception: {ex.ToLogString()}");
|
||||
|
||||
SetExceptionOnAllTokens(ex);
|
||||
|
||||
ConnectionClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_clientWebSocket != null)
|
||||
{
|
||||
if (_clientWebSocket.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
_clientWebSocket?.Dispose();
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void SetExceptionOnAllTokens(Exception ex)
|
||||
{
|
||||
//Logger.Debug($"Tokens: {_tokens.Count}");
|
||||
|
||||
foreach (var taskCompletion in _tokens.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
|
||||
taskCompletion.SetException(new Exception(ex.Message));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.Error("SetExceptionOnAllTokens: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_tokens.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -38,6 +38,7 @@ namespace ColorControl.Services.Common
|
||||
protected int _quickAccessShortcutId;
|
||||
|
||||
protected GlobalContext _globalContext;
|
||||
private KeyboardShortcutDispatcher _keyboardShortcutDispatcher;
|
||||
|
||||
private List<JsonConverter> _jsonConverters;
|
||||
|
||||
@@ -147,28 +148,33 @@ namespace ColorControl.Services.Common
|
||||
return await ApplyPresetUi(preset);
|
||||
}
|
||||
|
||||
private void LoadServices()
|
||||
{
|
||||
_keyboardShortcutDispatcher ??= _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
|
||||
}
|
||||
|
||||
protected void SetShortcuts(int quickAccessShortcutId = 0, string shortcut = null)
|
||||
{
|
||||
_quickAccessShortcutId = quickAccessShortcutId;
|
||||
|
||||
var keyboardShortcutDispatcher = _globalContext.ServiceProvider.GetService<KeyboardShortcutDispatcher>();
|
||||
LoadServices();
|
||||
|
||||
if (keyboardShortcutDispatcher == null)
|
||||
if (_keyboardShortcutDispatcher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (quickAccessShortcutId != 0)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(quickAccessShortcutId, shortcut);
|
||||
_keyboardShortcutDispatcher.RegisterShortcut(quickAccessShortcutId, shortcut);
|
||||
}
|
||||
|
||||
foreach (var preset in _presets)
|
||||
{
|
||||
keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut);
|
||||
_keyboardShortcutDispatcher.RegisterShortcut(preset.id, preset.shortcut);
|
||||
}
|
||||
|
||||
keyboardShortcutDispatcher.RegisterAsyncEventHandler(KeyboardShortcutDispatcher.Event_HotKey, HotKeyPressed);
|
||||
_keyboardShortcutDispatcher.RegisterAsyncEventHandler(KeyboardShortcutDispatcher.Event_HotKey, HotKeyPressed);
|
||||
}
|
||||
|
||||
public virtual void InstallEventHandlers()
|
||||
@@ -288,8 +294,23 @@ namespace ColorControl.Services.Common
|
||||
|
||||
}
|
||||
|
||||
protected void SavePresets()
|
||||
protected void ValidatePreset(PresetBase preset)
|
||||
{
|
||||
if (!KeyboardShortcutDispatcher.ValidateShortcut(preset.shortcut, false))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid shortcut");
|
||||
}
|
||||
}
|
||||
|
||||
protected void SavePresets(PresetBase updatedPreset = null)
|
||||
{
|
||||
if (updatedPreset != null)
|
||||
{
|
||||
LoadServices();
|
||||
|
||||
_keyboardShortcutDispatcher?.RegisterShortcut(updatedPreset.id, updatedPreset.shortcut);
|
||||
}
|
||||
|
||||
Utils.WriteObject(_presetsFilename, _presets);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,370 +15,372 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace ColorControl.Services.GameLauncher
|
||||
{
|
||||
class GameService : ServiceBase<GamePreset>
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public override string ServiceName => "Game";
|
||||
|
||||
protected override string PresetsBaseFilename => "GamePresets.json";
|
||||
|
||||
private ProcessEventDispatcher _processEventDispatcher;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly WinApiService _winApiService;
|
||||
private ServiceManager _serviceManager;
|
||||
private int _lastStartedProcessId;
|
||||
private System.DateTime _lastStartedPresetDateTime = System.DateTime.MinValue;
|
||||
private string _lastStartedProcessPath;
|
||||
private string _configFilename;
|
||||
|
||||
public GameServiceConfig Config { get; private set; }
|
||||
|
||||
public static readonly int SHORTCUTID_GAMEQA = -203;
|
||||
|
||||
public GameService(GlobalContext globalContext, ServiceManager serviceManager, ProcessEventDispatcher processEventDispatcher, WinApiAdminService winApiAdminService, WinApiService winApiService) : base(globalContext)
|
||||
{
|
||||
LoadConfig();
|
||||
LoadPresets();
|
||||
_serviceManager = serviceManager;
|
||||
_processEventDispatcher = processEventDispatcher;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
_winApiService = winApiService;
|
||||
_processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged);
|
||||
}
|
||||
|
||||
public override void InstallEventHandlers()
|
||||
{
|
||||
base.InstallEventHandlers();
|
||||
|
||||
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
|
||||
}
|
||||
|
||||
public override List<string> GetInfo()
|
||||
{
|
||||
return [$"{_presets.Count} presets"];
|
||||
}
|
||||
|
||||
private async Task ProcessChanged(object sender, ProcessChangedEventArgs e, CancellationToken token)
|
||||
{
|
||||
if (e.StartedProcesses?.Any() != true || !Config.ApplyExternallyLaunched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastStartedPresetDateTime > System.DateTime.Now.AddSeconds(-5))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastStartedProcessId = 0;
|
||||
|
||||
var startedProcesses = e.StartedProcesses;
|
||||
|
||||
foreach (var startedProcess in startedProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await HandleStartedProcess(startedProcess))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (System.Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
private Task<bool> HandleStartedProcess(Process process)
|
||||
{
|
||||
GamePreset preset;
|
||||
|
||||
if (_winApiService.IsAdministrator())
|
||||
{
|
||||
var mainModule = process.MainModule;
|
||||
|
||||
if (mainModule == null)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var fileName = mainModule.FileName;
|
||||
|
||||
preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = process.ProcessName.Replace("-Win64-Shipping", "");
|
||||
preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (preset == null || !preset.AutoSettings.AllowAutoApply)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
if (_lastStartedProcessId != process.Id)
|
||||
{
|
||||
Logger.Debug($"Auto-starting preset {preset.name} for process {process.ProcessName}");
|
||||
|
||||
var mainThreadId = 0;
|
||||
var suspended = false;
|
||||
if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Suspend)
|
||||
{
|
||||
mainThreadId = _winApiAdminService.SuspendMainThread(process.Id);
|
||||
suspended = true;
|
||||
}
|
||||
else if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Restart)
|
||||
{
|
||||
_winApiAdminService.StopProcess(process.Id);
|
||||
process = null;
|
||||
}
|
||||
|
||||
var _ = ExecutePreset(preset, process, suspended ? mainThreadId : 0);
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
protected override List<GamePreset> GetDefaultPresets()
|
||||
{
|
||||
return new List<GamePreset>();
|
||||
}
|
||||
|
||||
public void GlobalSave()
|
||||
{
|
||||
SavePresets();
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
public override async Task<bool> ApplyPreset(GamePreset preset)
|
||||
{
|
||||
var result = await ExecutePreset(preset);
|
||||
|
||||
_lastAppliedPreset = preset;
|
||||
|
||||
PresetApplied();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> ExecutePreset(GamePreset preset, Process process = null, int resumeThreadId = default)
|
||||
{
|
||||
var result = true;
|
||||
|
||||
await ExecuteSteps(preset.PreLaunchSteps);
|
||||
|
||||
if (process == null && !string.IsNullOrEmpty(preset.Path))
|
||||
{
|
||||
process = _winApiAdminService.StartProcess(preset.Path, preset.Parameters, setWorkingDir: true, elevate: preset.RunAsAdministrator, affinityMask: preset.ProcessAffinityMask, priorityClass: preset.ProcessPriorityClass);
|
||||
|
||||
_lastStartedPresetDateTime = System.DateTime.Now;
|
||||
_lastStartedProcessId = process.Id;
|
||||
_lastStartedProcessPath = Path.GetDirectoryName(preset.Path);
|
||||
}
|
||||
else if (resumeThreadId != default)
|
||||
{
|
||||
_winApiAdminService.ResumeThread((uint)resumeThreadId);
|
||||
}
|
||||
|
||||
await ExecuteSteps(preset.PostLaunchSteps);
|
||||
|
||||
if (process != null && preset.FinalizeSteps?.Any() == true)
|
||||
{
|
||||
await ExecuteFinalizationStepsAsync(process, preset.FinalizeSteps);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task ExecuteFinalizationStepsAsync(Process process, List<string> finalizeSteps)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
|
||||
try
|
||||
{
|
||||
if (process.HasExited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteSteps(finalizeSteps);
|
||||
}
|
||||
|
||||
private async Task ExecuteSteps(List<string> steps)
|
||||
{
|
||||
if (steps?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var step in steps)
|
||||
{
|
||||
var keySpec = step.Split(':');
|
||||
|
||||
var delay = 0;
|
||||
var key = step;
|
||||
if (keySpec.Length == 2)
|
||||
{
|
||||
delay = Utils.ParseInt(keySpec[1]);
|
||||
if (delay > 0)
|
||||
{
|
||||
key = keySpec[0];
|
||||
}
|
||||
}
|
||||
|
||||
var index = key.IndexOf("(");
|
||||
string[] parameters = null;
|
||||
if (index > -1)
|
||||
{
|
||||
var keyValue = key.Split('(');
|
||||
key = keyValue[0];
|
||||
parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Trim().Split(';');
|
||||
}
|
||||
|
||||
var handled = false;
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
handled = await _serviceManager.HandleExternalServiceAsync(key, parameters);
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_configFilename = Path.Combine(_dataPath, "GameConfig.json");
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configFilename))
|
||||
{
|
||||
Config = JsonConvert.DeserializeObject<GameServiceConfig>(File.ReadAllText(_configFilename));
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Logger.Error($"LoadConfig: {ex.Message}");
|
||||
}
|
||||
Config ??= new GameServiceConfig();
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
Utils.WriteObject(_configFilename, Config);
|
||||
}
|
||||
|
||||
public GameServiceConfig GetConfig()
|
||||
{
|
||||
return Config;
|
||||
}
|
||||
|
||||
public bool UpdateConfig(GameServiceConfig config)
|
||||
{
|
||||
Config.Update(config);
|
||||
|
||||
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
|
||||
|
||||
SaveConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdatePreset(GamePreset presetSpec)
|
||||
{
|
||||
var preset = _presets.FirstOrDefault(x => x.id == presetSpec.id);
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
preset.Update(presetSpec);
|
||||
}
|
||||
else
|
||||
{
|
||||
preset = new GamePreset(presetSpec);
|
||||
_presets.Add(preset);
|
||||
}
|
||||
|
||||
SavePresets();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string SelectPath()
|
||||
{
|
||||
var fileInfo = Utils.SelectFile();
|
||||
|
||||
if (fileInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
class GameService : ServiceBase<GamePreset>
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public override string ServiceName => "Game";
|
||||
|
||||
protected override string PresetsBaseFilename => "GamePresets.json";
|
||||
|
||||
private ProcessEventDispatcher _processEventDispatcher;
|
||||
private readonly WinApiAdminService _winApiAdminService;
|
||||
private readonly WinApiService _winApiService;
|
||||
private ServiceManager _serviceManager;
|
||||
private int _lastStartedProcessId;
|
||||
private System.DateTime _lastStartedPresetDateTime = System.DateTime.MinValue;
|
||||
private string _lastStartedProcessPath;
|
||||
private string _configFilename;
|
||||
|
||||
public GameServiceConfig Config { get; private set; }
|
||||
|
||||
public static readonly int SHORTCUTID_GAMEQA = -203;
|
||||
|
||||
public GameService(GlobalContext globalContext, ServiceManager serviceManager, ProcessEventDispatcher processEventDispatcher, WinApiAdminService winApiAdminService, WinApiService winApiService) : base(globalContext)
|
||||
{
|
||||
LoadConfig();
|
||||
LoadPresets();
|
||||
_serviceManager = serviceManager;
|
||||
_processEventDispatcher = processEventDispatcher;
|
||||
_winApiAdminService = winApiAdminService;
|
||||
_winApiService = winApiService;
|
||||
_processEventDispatcher.RegisterAsyncEventHandler(ProcessEventDispatcher.Event_ProcessChanged, ProcessChanged);
|
||||
}
|
||||
|
||||
public override void InstallEventHandlers()
|
||||
{
|
||||
base.InstallEventHandlers();
|
||||
|
||||
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
|
||||
}
|
||||
|
||||
public override List<string> GetInfo()
|
||||
{
|
||||
return [$"{_presets.Count} presets"];
|
||||
}
|
||||
|
||||
private async Task ProcessChanged(object sender, ProcessChangedEventArgs e, CancellationToken token)
|
||||
{
|
||||
if (e.StartedProcesses?.Any() != true || !Config.ApplyExternallyLaunched)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastStartedPresetDateTime > System.DateTime.Now.AddSeconds(-5))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_lastStartedProcessId = 0;
|
||||
|
||||
var startedProcesses = e.StartedProcesses;
|
||||
|
||||
foreach (var startedProcess in startedProcesses)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await HandleStartedProcess(startedProcess))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (System.Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
private Task<bool> HandleStartedProcess(Process process)
|
||||
{
|
||||
GamePreset preset;
|
||||
|
||||
if (_winApiService.IsAdministrator())
|
||||
{
|
||||
var mainModule = process.MainModule;
|
||||
|
||||
if (mainModule == null)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
var fileName = mainModule.FileName;
|
||||
|
||||
preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = process.ProcessName.Replace("-Win64-Shipping", "");
|
||||
preset = _presets.FirstOrDefault(p => Path.GetFileName(p.Path).Contains(name, System.StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (preset == null || !preset.AutoSettings.AllowAutoApply)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
if (_lastStartedProcessId != process.Id)
|
||||
{
|
||||
Logger.Debug($"Auto-starting preset {preset.name} for process {process.ProcessName}");
|
||||
|
||||
var mainThreadId = 0;
|
||||
var suspended = false;
|
||||
if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Suspend)
|
||||
{
|
||||
mainThreadId = _winApiAdminService.SuspendMainThread(process.Id);
|
||||
suspended = true;
|
||||
}
|
||||
else if (preset.AutoSettings.ProcessAutoAction == ProcessAutoAction.Restart)
|
||||
{
|
||||
_winApiAdminService.StopProcess(process.Id);
|
||||
process = null;
|
||||
}
|
||||
|
||||
var _ = ExecutePreset(preset, process, suspended ? mainThreadId : 0);
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
protected override List<GamePreset> GetDefaultPresets()
|
||||
{
|
||||
return new List<GamePreset>();
|
||||
}
|
||||
|
||||
public void GlobalSave()
|
||||
{
|
||||
SavePresets();
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
public override async Task<bool> ApplyPreset(GamePreset preset)
|
||||
{
|
||||
var result = await ExecutePreset(preset);
|
||||
|
||||
_lastAppliedPreset = preset;
|
||||
|
||||
PresetApplied();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> ExecutePreset(GamePreset preset, Process process = null, int resumeThreadId = default)
|
||||
{
|
||||
var result = true;
|
||||
|
||||
await ExecuteSteps(preset.PreLaunchSteps);
|
||||
|
||||
if (process == null && !string.IsNullOrEmpty(preset.Path))
|
||||
{
|
||||
process = _winApiAdminService.StartProcess(preset.Path, preset.Parameters, setWorkingDir: true, elevate: preset.RunAsAdministrator, affinityMask: preset.ProcessAffinityMask, priorityClass: preset.ProcessPriorityClass);
|
||||
|
||||
_lastStartedPresetDateTime = System.DateTime.Now;
|
||||
_lastStartedProcessId = process.Id;
|
||||
_lastStartedProcessPath = Path.GetDirectoryName(preset.Path);
|
||||
}
|
||||
else if (resumeThreadId != default)
|
||||
{
|
||||
_winApiAdminService.ResumeThread((uint)resumeThreadId);
|
||||
}
|
||||
|
||||
await ExecuteSteps(preset.PostLaunchSteps);
|
||||
|
||||
if (process != null && preset.FinalizeSteps?.Any() == true)
|
||||
{
|
||||
await ExecuteFinalizationStepsAsync(process, preset.FinalizeSteps);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task ExecuteFinalizationStepsAsync(Process process, List<string> finalizeSteps)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
|
||||
try
|
||||
{
|
||||
if (process.HasExited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteSteps(finalizeSteps);
|
||||
}
|
||||
|
||||
private async Task ExecuteSteps(List<string> steps)
|
||||
{
|
||||
if (steps?.Any() != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var step in steps)
|
||||
{
|
||||
var keySpec = step.Split(':');
|
||||
|
||||
var delay = 0;
|
||||
var key = step;
|
||||
if (keySpec.Length == 2)
|
||||
{
|
||||
delay = Utils.ParseInt(keySpec[1]);
|
||||
if (delay > 0)
|
||||
{
|
||||
key = keySpec[0];
|
||||
}
|
||||
}
|
||||
|
||||
var index = key.IndexOf("(");
|
||||
string[] parameters = null;
|
||||
if (index > -1)
|
||||
{
|
||||
var keyValue = key.Split('(');
|
||||
key = keyValue[0];
|
||||
parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Trim().Split(';');
|
||||
}
|
||||
|
||||
var handled = false;
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
handled = await _serviceManager.HandleExternalServiceAsync(key, parameters);
|
||||
}
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_configFilename = Path.Combine(_dataPath, "GameConfig.json");
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configFilename))
|
||||
{
|
||||
Config = JsonConvert.DeserializeObject<GameServiceConfig>(File.ReadAllText(_configFilename));
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Logger.Error($"LoadConfig: {ex.Message}");
|
||||
}
|
||||
Config ??= new GameServiceConfig();
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
Utils.WriteObject(_configFilename, Config);
|
||||
}
|
||||
|
||||
public GameServiceConfig GetConfig()
|
||||
{
|
||||
return Config;
|
||||
}
|
||||
|
||||
public bool UpdateConfig(GameServiceConfig config)
|
||||
{
|
||||
Config.Update(config);
|
||||
|
||||
SetShortcuts(SHORTCUTID_GAMEQA, Config.QuickAccessShortcut);
|
||||
|
||||
SaveConfig();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdatePreset(GamePreset specPreset)
|
||||
{
|
||||
ValidatePreset(specPreset);
|
||||
|
||||
var preset = _presets.FirstOrDefault(x => x.id == specPreset.id);
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
preset.Update(specPreset);
|
||||
}
|
||||
else
|
||||
{
|
||||
preset = new GamePreset(specPreset);
|
||||
_presets.Add(preset);
|
||||
}
|
||||
|
||||
SavePresets(preset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string SelectPath()
|
||||
{
|
||||
var fileInfo = Utils.SelectFile();
|
||||
|
||||
if (fileInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1315
-1315
File diff suppressed because it is too large.
Load diff
+1250
-1251
File diff suppressed because it is too large.
Load diff
@@ -300,6 +300,8 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
public bool UpdatePreset(NvPreset specPreset)
|
||||
{
|
||||
ValidatePreset(specPreset);
|
||||
|
||||
var currentPreset = _presets.FirstOrDefault(p => p.id == specPreset.id);
|
||||
|
||||
if (currentPreset != null)
|
||||
@@ -308,7 +310,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
currentPreset.shortcut = specPreset.shortcut;
|
||||
currentPreset.Update(specPreset);
|
||||
|
||||
SavePresets();
|
||||
SavePresets(currentPreset);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -319,7 +321,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
_presets.Add(newPreset);
|
||||
|
||||
SavePresets();
|
||||
SavePresets(newPreset);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -858,13 +860,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
var gpuHandle = displayDevice.PhysicalGPU.Handle;
|
||||
var displayId = displayDevice.DisplayId;
|
||||
|
||||
if (state == NvDitherState.Auto)
|
||||
{
|
||||
// These seem to be ignored in Auto-state
|
||||
bits = 0;
|
||||
mode = 0;
|
||||
}
|
||||
else if (preset != null)
|
||||
if (preset != null)
|
||||
{
|
||||
bits = preset.ditheringBits;
|
||||
mode = preset.ditheringMode;
|
||||
@@ -873,6 +869,13 @@ namespace ColorControl.Services.NVIDIA
|
||||
restartDriver |= preset.RestartDriver;
|
||||
}
|
||||
|
||||
if (state == NvDitherState.Auto)
|
||||
{
|
||||
// These seem to be ignored in Auto-state
|
||||
bits = 0;
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
if (setRegistryKey)
|
||||
{
|
||||
var info = CCD.GetDisplayInfo(display.Name);
|
||||
@@ -1566,7 +1569,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
return _meta.GetSettingMeta(settingId);
|
||||
}
|
||||
|
||||
public List<NvSettingItemDto> GetDriverSettings(string profileName = null)
|
||||
public List<NvSettingItemDto> GetDriverSettings(string profileName = null, bool addFriendlyName = false)
|
||||
{
|
||||
if (profileName == null)
|
||||
{
|
||||
@@ -1599,7 +1602,8 @@ namespace ColorControl.Services.NVIDIA
|
||||
Value = intValue.intValue,
|
||||
DefaultValue = intValue.defaultValue,
|
||||
SettingType = type,
|
||||
Values = values?.ToList()
|
||||
Values = values?.ToList(),
|
||||
FriendlyName = addFriendlyName ? $"{settingMeta.SettingName}: {settingMeta.ToFriendlyName(intValue: intValue.intValue, displayDefault: true)}" : null
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Security;
|
||||
using System.Net.WebSockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Sockets;
|
||||
using Windows.Security.Cryptography.Certificates;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace ColorControl.Services.Samsung
|
||||
{
|
||||
@@ -21,72 +19,53 @@ namespace ColorControl.Services.Samsung
|
||||
|
||||
public delegate void ConnectedDelegate(string token, bool disconnect);
|
||||
|
||||
public class SamTvConnection : IDisposable
|
||||
public class SamTvConnection : IAsyncDisposable
|
||||
{
|
||||
public static SynchronizationContext SyncContext { get; set; }
|
||||
|
||||
public bool? ConnectionClosed { get; private set; }
|
||||
public bool ClosedByDispose { get; private set; }
|
||||
|
||||
private MessageWebSocket _connection;
|
||||
private DataWriter _messageWriter;
|
||||
private ClientWebSocket _clientWebSocket;
|
||||
private int _commandCount;
|
||||
private int _readTimeout;
|
||||
private Uri _uri;
|
||||
|
||||
public event ConnectedDelegate Connected;
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<dynamic>> _tokens = new ConcurrentDictionary<string, TaskCompletionSource<dynamic>>();
|
||||
private readonly ConcurrentDictionary<string, Func<dynamic, bool>> _callbacks = new ConcurrentDictionary<string, Func<dynamic, bool>>();
|
||||
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public async Task<bool> Connect(Uri uri, int readTimeout = 5000)
|
||||
public async Task<bool> Connect(Uri uri, int connectTimeout = 5000)
|
||||
{
|
||||
_readTimeout = readTimeout;
|
||||
_uri = uri;
|
||||
|
||||
try
|
||||
{
|
||||
_connection?.Dispose();
|
||||
|
||||
_clientWebSocket?.Dispose();
|
||||
_commandCount = 0;
|
||||
|
||||
// Not creating the web socket from main thread can cause a stackoverflow in combase32.dll
|
||||
if (Environment.CurrentManagedThreadId != 1)
|
||||
{
|
||||
SyncContext.Send((_) => CreateWebSocket(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateWebSocket();
|
||||
}
|
||||
CreateWebSocket();
|
||||
|
||||
try
|
||||
{
|
||||
await _connection.ConnectAsync(uri).AsTask().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
Logger.Debug($"Connecting to {uri}");
|
||||
|
||||
await _clientWebSocket.ConnectAsync(uri, CancellationToken.None).WaitAsync(TimeSpan.FromMilliseconds(connectTimeout));
|
||||
|
||||
Logger.Debug($"Connected to {uri}");
|
||||
ConnectionClosed = false;
|
||||
|
||||
_messageWriter = new DataWriter(_connection.OutputStream);
|
||||
await Receive(true);
|
||||
|
||||
Logger.Debug("Received");
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Logger.Error($"Connect to {uri}: {te.Message}");
|
||||
Logger.Error($"Timeout while connecting to {uri}: {te.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
switch (SocketError.GetStatus(e.HResult))
|
||||
{
|
||||
case SocketErrorStatus.HostNotFound:
|
||||
// Handle HostNotFound Error
|
||||
break;
|
||||
default:
|
||||
// Handle Unknown Error
|
||||
break;
|
||||
}
|
||||
|
||||
Logger.Error($"Connect to {uri}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
@@ -94,63 +73,50 @@ namespace ColorControl.Services.Samsung
|
||||
|
||||
private void CreateWebSocket()
|
||||
{
|
||||
_connection = new MessageWebSocket();
|
||||
_connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
|
||||
_connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
|
||||
_connection.Control.MessageType = SocketMessageType.Utf8;
|
||||
_connection.MessageReceived += Connection_MessageReceived;
|
||||
_connection.Closed += Connection_Closed;
|
||||
_clientWebSocket = new ClientWebSocket();
|
||||
_clientWebSocket.Options.RemoteCertificateValidationCallback += CertificationValidationCallback;
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(string message)
|
||||
private bool CertificationValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) => true;
|
||||
|
||||
public async Task SendMessageAsync(string message, bool reconnect = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_messageWriter == null)
|
||||
if (_clientWebSocket?.State != WebSocketState.Open)
|
||||
{
|
||||
throw new Exception("SendMessageAsync: no writer");
|
||||
throw new InvalidOperationException("Socket not open");
|
||||
}
|
||||
var bytes = UTF8Encoding.UTF8.GetBytes(message);
|
||||
var data = bytes.AsMemory();
|
||||
|
||||
_messageWriter.WriteString(message);
|
||||
|
||||
await _messageWriter.StoreAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
await _clientWebSocket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
}
|
||||
catch (WebSocketException)
|
||||
{
|
||||
if (reconnect && await Connect(_uri))
|
||||
{
|
||||
await SendMessageAsync(message, false);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("SendMessageAsync: " + e.Message);
|
||||
ConnectionClosed = true;
|
||||
_messageWriter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<dynamic> SendCommandAsync(string id, string message, bool waitForAnswer = true)
|
||||
private async Task<dynamic> SendCommandAsync(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (waitForAnswer)
|
||||
await SendMessageAsync(message);
|
||||
if (ConnectionClosed == true)
|
||||
{
|
||||
var taskSource = new TaskCompletionSource<dynamic>();
|
||||
_tokens.TryAdd(id, taskSource);
|
||||
|
||||
await SendMessageAsync(message);
|
||||
if (ConnectionClosed == true)
|
||||
{
|
||||
throw new Exception("Connection closed");
|
||||
}
|
||||
|
||||
return await taskSource.Task;
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendMessageAsync(message);
|
||||
if (ConnectionClosed == true)
|
||||
{
|
||||
throw new Exception("Connection closed");
|
||||
}
|
||||
|
||||
return null;
|
||||
throw new Exception("Connection closed");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -158,7 +124,7 @@ namespace ColorControl.Services.Samsung
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<dynamic> SendCommandAsync(RequestMessage message, bool waitForAnswer = true)
|
||||
public async Task<dynamic> SendCommandAsync(RequestMessage message)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
@@ -166,32 +132,7 @@ namespace ColorControl.Services.Samsung
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
//ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
return await SendCommandAsync(rawMessage.Id, serialized, waitForAnswer);
|
||||
}
|
||||
|
||||
public async Task<dynamic> SubscribeAsync(RequestMessage message, Func<dynamic, bool> callback)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
|
||||
_callbacks.TryAdd(rawMessage.Id, callback);
|
||||
|
||||
return await SendCommandAsync(rawMessage.Id, serialized);
|
||||
}
|
||||
|
||||
private void Connection_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
|
||||
{
|
||||
var webSocket = Interlocked.Exchange(ref _connection, null);
|
||||
webSocket?.Dispose();
|
||||
ConnectionClosed = true;
|
||||
|
||||
Logger.Debug("Connection closed");
|
||||
|
||||
Connected?.Invoke(null, true);
|
||||
return await SendCommandAsync(serialized);
|
||||
}
|
||||
|
||||
private const string MS_CHANNEL_CONNECT_EVENT = "ms.channel.connect";
|
||||
@@ -201,128 +142,76 @@ namespace ColorControl.Services.Samsung
|
||||
private const string MS_CHANNEL_UNAUTHORIZED = "ms.channel.unauthorized";
|
||||
private const string MS_ERROR_EVENT = "ms.error";
|
||||
|
||||
private async void Connection_MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
|
||||
private async Task Receive(bool singleResponse)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = new Task<DataReader>(args.GetDataReader);
|
||||
task.Start();
|
||||
var dataReader = await task.WaitAsync(TimeSpan.FromMilliseconds(_readTimeout));
|
||||
var dataPerPacket = 8 * 1024; //8192
|
||||
var strBuilder = new StringBuilder();
|
||||
var buffer = new ArraySegment<byte>(new byte[dataPerPacket]);
|
||||
|
||||
using (dataReader)
|
||||
while (_clientWebSocket?.State == WebSocketState.Open)
|
||||
{
|
||||
var receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
|
||||
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
|
||||
//keep doing until end of message
|
||||
while (!receiveResult.EndOfMessage)
|
||||
{
|
||||
dataReader.UnicodeEncoding = UnicodeEncoding.Utf8;
|
||||
var message = dataReader.ReadString(dataReader.UnconsumedBufferLength);
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
var id = (string)obj.id;
|
||||
var type = (string)obj.@event;
|
||||
|
||||
Logger.Debug($"Received message: {message}");
|
||||
|
||||
// Response:
|
||||
// {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA=="},"connectTime":1685302606520,"deviceName":"c2Ftc3VuZ2N0bA==","id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","isHost":false}],"id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","token":"16224598"},"event":"ms.channel.connect"}
|
||||
// Second response:
|
||||
// {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA==","token":"16224598"},"connectTime":1685303321571,"deviceName":"c2Ftc3VuZ2N0bA==","id":"58589451-2ecf-44f0-9267-31983a5466","isHost":false}],"id":"58589451-2ecf-44f0-9267-31983a5466"},"event":"ms.channel.connect"}
|
||||
|
||||
//TaskCompletionSource<dynamic> taskCompletion;
|
||||
if (type == MS_CHANNEL_CONNECT_EVENT)
|
||||
{
|
||||
var token = (string)obj.data?.token;
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
token = (string)obj.data?.clients[0]?.attributes?.token;
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
Connected?.Invoke(token, false);
|
||||
}
|
||||
|
||||
//if (_tokens.TryRemove(id, out taskCompletion))
|
||||
//{
|
||||
// var key = (string)JObject.Parse(message)["payload"]["client-key"];
|
||||
// taskCompletion.TrySetResult(new { clientKey = key });
|
||||
//}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
//else if (_tokens.TryGetValue(id, out taskCompletion))
|
||||
//{
|
||||
// if (id == "register_0") return;
|
||||
// if (obj.type == "error")
|
||||
// {
|
||||
// taskCompletion.SetException(new Exception(obj.error?.ToString()));
|
||||
// }
|
||||
// //else if (args.Cancelled)
|
||||
// //{
|
||||
// // taskSource.SetCanceled();
|
||||
// //}
|
||||
// taskCompletion.TrySetResult(obj.payload);
|
||||
|
||||
// if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// callback(obj.payload);
|
||||
// }
|
||||
// catch (Exception callbackException)
|
||||
// {
|
||||
// Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
receiveResult = await _clientWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
strBuilder.Append(Encoding.UTF8.GetString(buffer.Array, buffer.Offset, receiveResult.Count));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
|
||||
Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.ToLogString()}");
|
||||
|
||||
SetExceptionOnAllTokens(ex);
|
||||
var message = strBuilder.ToString();
|
||||
strBuilder.Clear();
|
||||
|
||||
ConnectionClosed = true;
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
var id = (string)obj.id;
|
||||
var type = (string)obj.@event;
|
||||
|
||||
_messageWriter?.Dispose();
|
||||
// Response:
|
||||
// {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA=="},"connectTime":1685302606520,"deviceName":"c2Ftc3VuZ2N0bA==","id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","isHost":false}],"id":"92e0cbc1-6f86-4013-9b9b-65a47afb2236","token":"16224598"},"event":"ms.channel.connect"}
|
||||
// Second response:
|
||||
// {"data":{"clients":[{"attributes":{"name":"c2Ftc3VuZ2N0bA==","token":"16224598"},"connectTime":1685303321571,"deviceName":"c2Ftc3VuZ2N0bA==","id":"58589451-2ecf-44f0-9267-31983a5466","isHost":false}],"id":"58589451-2ecf-44f0-9267-31983a5466"},"event":"ms.channel.connect"}
|
||||
|
||||
if (type == MS_CHANNEL_CONNECT_EVENT)
|
||||
{
|
||||
var token = (string)obj.data?.token;
|
||||
|
||||
if (token == null)
|
||||
{
|
||||
token = (string)obj.data?.clients[0]?.attributes?.token;
|
||||
}
|
||||
|
||||
if (token != null)
|
||||
{
|
||||
Connected?.Invoke(token, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (singleResponse)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetExceptionOnAllTokens(Exception ex)
|
||||
{
|
||||
//Logger.Debug($"Tokens: {_tokens.Count}");
|
||||
|
||||
foreach (var taskCompletion in _tokens.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
|
||||
taskCompletion.SetException(new Exception(ex.Message));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.Error("SetExceptionOnAllTokens: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_tokens.Clear();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_connection?.Close(1000, "");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
ClosedByDispose = true;
|
||||
Close();
|
||||
_connection?.Dispose();
|
||||
|
||||
if (_clientWebSocket != null)
|
||||
{
|
||||
if (_clientWebSocket.State == WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
_clientWebSocket?.Dispose();
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+599
-598
File diff suppressed because it is too large.
Load diff
@@ -1,281 +0,0 @@
|
||||
using ColorControl.Shared.Common;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Networking.Sockets;
|
||||
using Windows.Security.Cryptography.Certificates;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace LgTv
|
||||
{
|
||||
public class SendMessageException : Exception
|
||||
{
|
||||
public SendMessageException(string message, Exception e) : base(message, e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void IsConnectedDelegate(bool status);
|
||||
|
||||
public class LgTvApiCore : IDisposable
|
||||
{
|
||||
public static SynchronizationContext SyncContext { get; set; }
|
||||
|
||||
public bool ConnectionClosed { get; private set; }
|
||||
|
||||
private MessageWebSocket _connection;
|
||||
private DataWriter _messageWriter;
|
||||
private int _commandCount;
|
||||
|
||||
public event IsConnectedDelegate IsConnected;
|
||||
private readonly ConcurrentDictionary<string, TaskCompletionSource<dynamic>> _tokens = new ConcurrentDictionary<string, TaskCompletionSource<dynamic>>();
|
||||
private readonly ConcurrentDictionary<string, Func<dynamic, bool>> _callbacks = new ConcurrentDictionary<string, Func<dynamic, bool>>();
|
||||
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public async Task<bool> Connect(Uri uri, bool ignoreReceiver = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Logger.Debug($"Connecting from thread: {Environment.CurrentManagedThreadId}");
|
||||
|
||||
_connection?.Dispose();
|
||||
|
||||
ConnectionClosed = false;
|
||||
_commandCount = 0;
|
||||
|
||||
// Not creating the web socket from main thread can cause a stackoverflow in combase32.dll
|
||||
if (Environment.CurrentManagedThreadId != 1)
|
||||
{
|
||||
SyncContext.Send((_) => CreateWebSocket(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateWebSocket();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _connection.ConnectAsync(uri).AsTask().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
_messageWriter = new DataWriter(_connection.OutputStream);
|
||||
|
||||
IsConnected?.Invoke(true);
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException te)
|
||||
{
|
||||
Logger.Error($"Connect to {uri}: {te.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
switch (SocketError.GetStatus(e.HResult))
|
||||
{
|
||||
case SocketErrorStatus.HostNotFound:
|
||||
// Handle HostNotFound Error
|
||||
break;
|
||||
default:
|
||||
// Handle Unknown Error
|
||||
break;
|
||||
}
|
||||
|
||||
Logger.Error($"Connect to {uri}: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateWebSocket()
|
||||
{
|
||||
_connection = new MessageWebSocket();
|
||||
_connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
|
||||
_connection.Control.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
|
||||
_connection.Control.MessageType = SocketMessageType.Utf8;
|
||||
_connection.MessageReceived += Connection_MessageReceived;
|
||||
_connection.Closed += Connection_Closed;
|
||||
}
|
||||
|
||||
public async Task SendMessageAsync(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_messageWriter == null)
|
||||
{
|
||||
throw new Exception("SendMessageAsync: no writer");
|
||||
}
|
||||
|
||||
_messageWriter.WriteString(message);
|
||||
|
||||
await _messageWriter.StoreAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("SendMessageAsync: " + e.Message);
|
||||
ConnectionClosed = true;
|
||||
_messageWriter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<dynamic> SendCommandAsync(string message)
|
||||
{
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
return await SendCommandAsync((string)obj.id, message);
|
||||
}
|
||||
|
||||
private async Task<dynamic> SendCommandAsync(string id, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var taskSource = new TaskCompletionSource<dynamic>();
|
||||
_tokens.TryAdd(id, taskSource);
|
||||
await SendMessageAsync(message);
|
||||
if (ConnectionClosed)
|
||||
{
|
||||
throw new Exception("Connection closed");
|
||||
}
|
||||
|
||||
return await taskSource.Task;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SendMessageException("Can't send message", e);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<dynamic> SendCommandAsync(RequestMessage message)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
return await SendCommandAsync(rawMessage.Id, serialized);
|
||||
}
|
||||
|
||||
public async Task<dynamic> SubscribeAsync(RequestMessage message, Func<dynamic, bool> callback)
|
||||
{
|
||||
var rawMessage = new RawRequestMessage(message, ++_commandCount);
|
||||
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
|
||||
_callbacks.TryAdd(rawMessage.Id, callback);
|
||||
|
||||
return await SendCommandAsync(rawMessage.Id, serialized);
|
||||
}
|
||||
|
||||
private void Connection_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
|
||||
{
|
||||
MessageWebSocket webSocket = Interlocked.Exchange(ref _connection, null);
|
||||
webSocket?.Dispose();
|
||||
ConnectionClosed = true;
|
||||
}
|
||||
|
||||
private async void Connection_MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = new Task<DataReader>(args.GetDataReader);
|
||||
task.Start();
|
||||
var dataReader = await task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
using (dataReader)
|
||||
{
|
||||
dataReader.UnicodeEncoding = UnicodeEncoding.Utf8;
|
||||
var message = dataReader.ReadString(dataReader.UnconsumedBufferLength);
|
||||
var obj = JsonConvert.DeserializeObject<dynamic>(message);
|
||||
var id = (string)obj.id;
|
||||
var type = (string)obj.type;
|
||||
|
||||
TaskCompletionSource<dynamic> taskCompletion;
|
||||
if (type == "registered")
|
||||
{
|
||||
if (_tokens.TryRemove(id, out taskCompletion))
|
||||
{
|
||||
var key = (string)JObject.Parse(message)["payload"]["client-key"];
|
||||
taskCompletion.TrySetResult(new { clientKey = key });
|
||||
}
|
||||
|
||||
}
|
||||
else if (_tokens.TryGetValue(id, out taskCompletion))
|
||||
{
|
||||
if (id == "register_0") return;
|
||||
if (obj.type == "error")
|
||||
{
|
||||
taskCompletion.SetException(new Exception(obj.error?.ToString()));
|
||||
}
|
||||
//else if (args.Cancelled)
|
||||
//{
|
||||
// taskSource.SetCanceled();
|
||||
//}
|
||||
taskCompletion.TrySetResult(obj.payload);
|
||||
|
||||
if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
|
||||
{
|
||||
try
|
||||
{
|
||||
callback(obj.payload);
|
||||
}
|
||||
catch (Exception callbackException)
|
||||
{
|
||||
Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
|
||||
Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.ToLogString()}");
|
||||
|
||||
SetExceptionOnAllTokens(ex);
|
||||
|
||||
ConnectionClosed = true;
|
||||
_messageWriter?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetExceptionOnAllTokens(Exception ex)
|
||||
{
|
||||
//Logger.Debug($"Tokens: {_tokens.Count}");
|
||||
|
||||
foreach (var taskCompletion in _tokens.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
|
||||
taskCompletion.SetException(new Exception(ex.Message));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.Error("SetExceptionOnAllTokens: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_tokens.Clear();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_connection?.Close(1, "");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,49 +4,54 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace LgTv
|
||||
{
|
||||
public class LgWebOsMouseService : IDisposable
|
||||
{
|
||||
private readonly LgTvApiCore _connection;
|
||||
public class LgWebOsMouseService : IAsyncDisposable
|
||||
{
|
||||
private readonly LgTvApiCoreCws _connection;
|
||||
|
||||
public LgWebOsMouseService(LgTvApiCore connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
public LgWebOsMouseService(LgTvApiCoreCws connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public async Task<bool> Connect(string uri)
|
||||
{
|
||||
var ctx = _connection.Connect(new Uri(uri), ignoreReceiver: true);
|
||||
return (await ctx);
|
||||
}
|
||||
public async Task<bool> Connect(string uri)
|
||||
{
|
||||
var ctx = _connection.Connect(new Uri(uri), ignoreReceiver: true);
|
||||
return (await ctx);
|
||||
}
|
||||
|
||||
public async Task SendButton(ButtonType bt)
|
||||
{
|
||||
var text = bt.ToString();
|
||||
if (text[0] == '_')
|
||||
{
|
||||
text = text.Substring(1);
|
||||
}
|
||||
await _connection.SendMessageAsync($"type:button\nname:{text}\n\n");
|
||||
}
|
||||
public async Task SendButton(ButtonType bt)
|
||||
{
|
||||
var text = bt.ToString();
|
||||
if (text[0] == '_')
|
||||
{
|
||||
text = text.Substring(1);
|
||||
}
|
||||
await _connection.SendMessageAsync($"type:button\nname:{text}\n\n");
|
||||
}
|
||||
|
||||
public void Move(double dx, double dy, bool drag = false)
|
||||
{
|
||||
_connection.SendMessageAsync($"type:move\ndx:{dx}\ndy:{dy}\ndown:{(drag ? 1 : 0)}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
public void Move(double dx, double dy, bool drag = false)
|
||||
{
|
||||
_connection.SendMessageAsync($"type:move\ndx:{dx}\ndy:{dy}\ndown:{(drag ? 1 : 0)}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Scroll(double dx, double dy)
|
||||
{
|
||||
_connection.SendMessageAsync($"type:scroll\ndx:{dx}\ndy:{dy}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
public void Scroll(double dx, double dy)
|
||||
{
|
||||
_connection.SendMessageAsync($"type:scroll\ndx:{dx}\ndy:{dy}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Click()
|
||||
{
|
||||
_connection.SendMessageAsync("type:click\n\n").ConfigureAwait(false);
|
||||
}
|
||||
public void Click()
|
||||
{
|
||||
_connection.SendMessageAsync("type:click\n\n").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection?.Dispose();
|
||||
}
|
||||
}
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_connection != null)
|
||||
{
|
||||
await _connection.DisposeAsync();
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
+1203
-1183
File diff suppressed because it is too large.
Load diff
@@ -1,21 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Platforms>x64</Platforms>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(NETCoreSdkVersion)', '8.0'))">
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<UseNativeAot Condition="$(PublishDir) != ''">true</UseNativeAot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Optimize for size -->
|
||||
<PropertyGroup Condition="'$(UseNativeAot)' == 'true'">
|
||||
<PublishAot>true</PublishAot>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RootNamespace>DJ</RootNamespace>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
@@ -18,8 +18,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -27,8 +27,11 @@ namespace nspector.Common.Meta
|
||||
return settingIds;
|
||||
}
|
||||
|
||||
private SettingMeta GetDriverSettingMetaInternal(uint settingId)
|
||||
private SettingMeta? GetDriverSettingMetaInternal(uint settingId)
|
||||
{
|
||||
if ((settingId & 0xFFFFF000) == 0x10c7d000)
|
||||
return null;
|
||||
|
||||
var values = new NVDRS_SETTING_VALUES();
|
||||
values.version = nvw.NVDRS_SETTING_VALUES_VER;
|
||||
uint valueCount = 255;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7913,6 +7913,27 @@
|
||||
</CustomSettingValue>
|
||||
</SettingValues>
|
||||
</CustomSetting>
|
||||
<CustomSetting>
|
||||
<UserfriendlyName>DLSS 3.1.11+ - Forced DLSS3.1 preset letter (base profile only)</UserfriendlyName>
|
||||
<HexSettingID>0x10E41DF3</HexSettingID>
|
||||
<GroupName>8 - Extra</GroupName>
|
||||
<MinRequiredDriverVersion>0</MinRequiredDriverVersion>
|
||||
<SettingValues>
|
||||
<CustomSettingValue>
|
||||
<UserfriendlyName>Preset E</UserfriendlyName>
|
||||
<HexValue>0x00000005</HexValue>
|
||||
</CustomSettingValue>
|
||||
<CustomSettingValue>
|
||||
<UserfriendlyName>Preset F</UserfriendlyName>
|
||||
<HexValue>0x00000006</HexValue>
|
||||
</CustomSettingValue>
|
||||
<CustomSettingValue>
|
||||
<UserfriendlyName>Preset J</UserfriendlyName>
|
||||
<HexValue>0x0000000A</HexValue>
|
||||
</CustomSettingValue>
|
||||
</SettingValues>
|
||||
<SettingMasks />
|
||||
</CustomSetting>
|
||||
|
||||
</Settings>
|
||||
</CustomSettingNames>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Version>0.8.1.106</Version>
|
||||
<Company>falahati.net</Company>
|
||||
<Description>NvAPIWrapper is a .Net wrapper for NVIDIA public API</Description>
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace ColorControl.Shared.Common
|
||||
var len = BitConverter.ToInt32(lenBytes);
|
||||
|
||||
var inBuffer = new byte[len];
|
||||
ioStream.Read(inBuffer, 0, len);
|
||||
ioStream.ReadExactly(inBuffer, 0, len);
|
||||
|
||||
return streamEncoding.GetString(inBuffer);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace ColorControl.Shared.Common
|
||||
var len = BitConverter.ToInt32(lenBytes);
|
||||
|
||||
var inBuffer = new byte[len];
|
||||
ioStream.Read(inBuffer, 0, len);
|
||||
ioStream.ReadExactly(inBuffer, 0, len);
|
||||
|
||||
return streamEncoding.GetString(inBuffer);
|
||||
}
|
||||
|
||||
+727
-734
File diff suppressed because it is too large.
Load diff
@@ -4,64 +4,65 @@ namespace ColorControl.Shared.Contracts.NVIDIA;
|
||||
|
||||
public enum NvSettingType
|
||||
{
|
||||
DwordType,
|
||||
BinaryType
|
||||
DwordType,
|
||||
BinaryType
|
||||
}
|
||||
|
||||
public class NvSettingItemDto
|
||||
{
|
||||
public uint SettingId { get; set; }
|
||||
public uint SettingId { get; set; }
|
||||
|
||||
public string SettingText { get; set; }
|
||||
public string SettingText { get; set; }
|
||||
|
||||
public string ValueText { get; set; }
|
||||
public string ValueText { get; set; }
|
||||
|
||||
public string ValueRaw { get; set; }
|
||||
public string ValueRaw { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool ValueChanged { get; set; }
|
||||
[JsonIgnore]
|
||||
public bool ValueChanged { get; set; }
|
||||
|
||||
private uint _value;
|
||||
private uint _value;
|
||||
|
||||
public uint Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
public uint Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
ValueChanged = true;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
ValueChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public string GroupName { get; set; }
|
||||
public string GroupName { get; set; }
|
||||
|
||||
public int State { get; set; }
|
||||
public int State { get; set; }
|
||||
|
||||
public bool IsStringValue { get; set; }
|
||||
public bool IsStringValue { get; set; }
|
||||
|
||||
public bool IsApiExposed { get; set; }
|
||||
public bool IsApiExposed { get; set; }
|
||||
|
||||
public bool IsSettingHidden { get; set; }
|
||||
public bool IsSettingHidden { get; set; }
|
||||
|
||||
public NvSettingType SettingType { get; set; }
|
||||
public NvSettingType SettingType { get; set; }
|
||||
|
||||
public uint DefaultValue { get; set; }
|
||||
public uint DefaultValue { get; set; }
|
||||
|
||||
public List<NvSettingItemValue> Values { get; set; }
|
||||
public List<NvSettingItemValue> Values { get; set; }
|
||||
|
||||
public string FriendlyName { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}; 0x{1:X8}; {2}; {3}; {4};", State, SettingId, SettingText, ValueText, ValueRaw);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}; 0x{1:X8}; {2}; {3}; {4};", State, SettingId, SettingText, ValueText, ValueRaw);
|
||||
}
|
||||
}
|
||||
|
||||
public class NvSettingItemValue
|
||||
{
|
||||
public uint Value { get; set; }
|
||||
public string ValueName { get; set; }
|
||||
public uint Value { get; set; }
|
||||
public string ValueName { get; set; }
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static NStandard.Measures.Length;
|
||||
|
||||
namespace ColorControl.Shared.EventDispatcher;
|
||||
namespace ColorControl.Shared.EventDispatcher;
|
||||
|
||||
public class DisplayChangedEventArgs : EventArgs
|
||||
{
|
||||
public uint Width { get; set; }
|
||||
public uint Height { get; set; }
|
||||
public uint BitsPerPixel{ get; set; }
|
||||
public uint BitsPerPixel { get; set; }
|
||||
}
|
||||
|
||||
public class DisplayChangeEventDispatcher : EventDispatcher<DisplayChangedEventArgs>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using ColorControl.Shared.Forms;
|
||||
using Linearstar.Windows.RawInput;
|
||||
using Linearstar.Windows.RawInput.Native;
|
||||
using NStandard;
|
||||
|
||||
namespace ColorControl.Shared.EventDispatcher;
|
||||
|
||||
@@ -423,11 +424,11 @@ public class KeyboardShortcutDispatcher : EventDispatcher<KeyboardShortcutEventA
|
||||
return shortcutString;
|
||||
}
|
||||
|
||||
public static bool ValidateShortcut(string shortcut)
|
||||
public static bool ValidateShortcut(string shortcut, bool showMessage = true)
|
||||
{
|
||||
var valid = string.IsNullOrWhiteSpace(shortcut) || shortcut.Contains("+") || ShortcutIsAllowedWithoutModifier(shortcut);
|
||||
var valid = string.IsNullOrWhiteSpace(shortcut) || shortcut.Split("+").Where(s => !s.IsNullOrWhiteSpace()).Count() > 1 || ShortcutIsAllowedWithoutModifier(shortcut);
|
||||
|
||||
if (!valid)
|
||||
if (!valid && showMessage)
|
||||
{
|
||||
MessageForms.WarningOk("Invalid shortcut. The shortcut should have modifiers and a normal key or be F13-F24.");
|
||||
}
|
||||
|
||||
@@ -124,12 +124,6 @@ namespace ColorControl.Shared.Forms
|
||||
|
||||
try
|
||||
{
|
||||
//var settings = new UISettings();
|
||||
|
||||
//var foregroundColorValue = settings.GetColorValue(UIColorType.Foreground);
|
||||
//var backroundColorValue = settings.GetColorValue(UIColorType.Background);
|
||||
//var isDarkMode = FormUtils.IsColorLight(foregroundColorValue);
|
||||
|
||||
SetControlTheme(form, FormUtils.CurrentBackColor, FormUtils.CurrentForeColor);
|
||||
|
||||
var value = toDark ? 1 : 0;
|
||||
@@ -240,10 +234,5 @@ namespace ColorControl.Shared.Forms
|
||||
SetMenuForeColor(item, color);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsColorLight(Windows.UI.Color color)
|
||||
{
|
||||
return ((5 * color.G) + (2 * color.R) + color.B) > (8 * 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-15
@@ -1,12 +1,8 @@
|
||||
using Windows.Devices.Enumeration;
|
||||
using Windows.Devices.Enumeration.Pnp;
|
||||
|
||||
namespace ColorControl.Shared.Native
|
||||
namespace ColorControl.Shared.Native
|
||||
{
|
||||
public class PnpDev
|
||||
{
|
||||
public DeviceInformation DevInfo { get; private set; }
|
||||
public PnpObject PnpObject { get; private set; }
|
||||
public bool IsCustom { get; private set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string IpAddress { get; private set; }
|
||||
@@ -15,24 +11,20 @@ namespace ColorControl.Shared.Native
|
||||
|
||||
public PnpDev(string name, string ipAddress, string macAddress)
|
||||
{
|
||||
IsCustom = true;
|
||||
Name = name;
|
||||
IpAddress = ipAddress;
|
||||
MacAddress = macAddress;
|
||||
}
|
||||
public PnpDev(DeviceInformation devInfo, PnpObject pnpObject, string name, string ipAddress, string macAddress) : this(name, ipAddress, macAddress)
|
||||
|
||||
public PnpDev(bool isCustom, string name, string ipAddress, string macAddress) : this(name, ipAddress, macAddress)
|
||||
{
|
||||
DevInfo = devInfo;
|
||||
PnpObject = pnpObject;
|
||||
IsCustom = isCustom;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return (IsCustom() ? "Custom: " : "Auto detect: ") + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $"({IpAddress})" : string.Empty);
|
||||
}
|
||||
|
||||
public bool IsCustom()
|
||||
{
|
||||
return DevInfo == null;
|
||||
return (IsCustom ? "Custom: " : "Auto detect: ") + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $"({IpAddress})" : string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +317,7 @@ namespace ColorControl.Shared.Services
|
||||
|
||||
try
|
||||
{
|
||||
using (TaskService ts = new TaskService())
|
||||
using (var ts = new TaskService())
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
@@ -329,6 +329,9 @@ namespace ColorControl.Shared.Services
|
||||
td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name });
|
||||
|
||||
td.Actions.Add(new ExecAction(file, StartUpParams.RunningFromScheduledTaskParam, directory));
|
||||
td.Settings.DisallowStartIfOnBatteries = false;
|
||||
td.Settings.StopIfGoingOnBatteries = false;
|
||||
td.Settings.ExecutionTimeLimit = TimeSpan.Zero;
|
||||
|
||||
ts.RootFolder.RegisterTaskDefinition(taskName, td);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PathMap Condition="'$(Configuration)' == 'Release'">$(MSBuildThisFileDirectory)=.\</PathMap>
|
||||
@@ -25,14 +26,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="NStandard" Version="0.59.0" />
|
||||
<PackageReference Include="NWin32" Version="1.2.6" />
|
||||
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.10.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.17" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="NStandard" Version="0.83.1" />
|
||||
<PackageReference Include="NWin32" Version="1.2.8" />
|
||||
<PackageReference Include="System.Management" Version="9.0.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.11.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="4.0.4" />
|
||||
<PackageReference Include="RawInput.Sharp" Version="0.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.20348.0</TargetFramework>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -16,8 +16,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.2.8" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="8.0.0" />
|
||||
<PackageReference Include="NLog" Version="5.3.4" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in new issue
Block a user