mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-21 00:01:48 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1ace0bdd5 | ||
|
|
66e682ba3d | ||
|
|
0de3deebf7 | ||
|
|
0c1859f848 | ||
|
|
b8910a3b64 | ||
|
|
b2e7c529a0 | ||
|
|
977060e927 | ||
|
|
f609471fd2 | ||
|
|
a1a94d27db | ||
|
|
e12faaba27 | ||
|
|
6f5f7c4dd3 | ||
|
|
e1800b8d50 | ||
|
|
7779533c1e | ||
|
|
74cefc00f7 | ||
|
|
a8d33b6a30 | ||
|
|
f83869da04 | ||
|
|
815710c081 | ||
|
|
805c516371 | ||
|
|
b85d929890 | ||
|
|
dd9ce932fc | ||
|
|
3667243fc7 |
No files matched your search
+2
-1
@@ -337,4 +337,5 @@ ASALocalRun/
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
healthchecksdb
|
||||
*.clef
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30128.74
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorControl", "ColorControl\ColorControl.csproj", "{A6005D08-70F0-44CB-A384-9219EBA84F21}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColorControl", "ColorControl\ColorControl.csproj", "{A6005D08-70F0-44CB-A384-9219EBA84F21}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -15,8 +15,8 @@ Global
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.Build.0 = Debug|x64
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|x64.ActiveCfg = Release|x64
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"Build":"0.3.261602\u002Befb3cc30a24e699e6e4337f263a2b9478f04f618","CurrentProject":"ColorControl.csproj","EntryPoints":["ColorControl.csproj"],"Properties":{}}
|
||||
@@ -1,4 +1,5 @@
|
||||
using ATI.ADL;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -13,16 +14,11 @@ namespace ColorControl
|
||||
|
||||
private ADLDisplayInfo _currentDisplay;
|
||||
|
||||
public AmdService(string dataPath) : base(dataPath)
|
||||
public AmdService(string dataPath) : base(dataPath, "AmdPresets.json")
|
||||
{
|
||||
LoadPresets();
|
||||
}
|
||||
|
||||
public AmdPreset GetLastAppliedPreset()
|
||||
{
|
||||
return _lastAppliedPreset;
|
||||
}
|
||||
|
||||
public static bool ExecutePresetAsync(string idOrName)
|
||||
{
|
||||
try
|
||||
@@ -45,27 +41,16 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPresets()
|
||||
protected override List<AmdPreset> GetDefaultPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, "AmdPresets.json");
|
||||
|
||||
if (File.Exists(_presetsFilename))
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
_presets = _JsonDeserializer.Deserialize<List<AmdPreset>>(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presets = AmdPreset.GetDefaultPresets();
|
||||
}
|
||||
return AmdPreset.GetDefaultPresets();
|
||||
}
|
||||
|
||||
private void SavePresets()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = _JsonSerializer.Serialize(_presets);
|
||||
var json = JsonConvert.SerializeObject(_presets);
|
||||
File.WriteAllText(_presetsFilename, json);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -79,7 +64,7 @@ namespace ColorControl
|
||||
SavePresets();
|
||||
}
|
||||
|
||||
public override bool HasDisplaysAttached()
|
||||
public override bool HasDisplaysAttached(bool reinitialize = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
{
|
||||
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "Dependency Analysis",
|
||||
"semanticVersion": "0.3.261602",
|
||||
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
|
||||
"rules": [
|
||||
{
|
||||
"id": "UA103",
|
||||
"name": "ReferenceToBeDeleted",
|
||||
"fullDescription": {
|
||||
"text": "References that need to be deleted in order to upgrade the project to chosen TFM"
|
||||
},
|
||||
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
|
||||
},
|
||||
{
|
||||
"id": "UA106",
|
||||
"name": "PackageToBeAdded",
|
||||
"fullDescription": {
|
||||
"text": "Packages that need to be added in order to upgrade the project to chosen TFM"
|
||||
},
|
||||
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
|
||||
},
|
||||
{
|
||||
"id": "UA108",
|
||||
"name": "FrameworkReferenceToBeAdded",
|
||||
"fullDescription": {
|
||||
"text": "Framework Reference that need to be added in order to upgrade the project to chosen TFM"
|
||||
},
|
||||
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "UA103",
|
||||
"message": {
|
||||
"text": "Reference to System.Configuration needs to be deleted."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA103",
|
||||
"message": {
|
||||
"text": "Reference to System.ServiceModel needs to be deleted."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA103",
|
||||
"message": {
|
||||
"text": "Reference to System.Web needs to be deleted."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA103",
|
||||
"message": {
|
||||
"text": "Reference to System.Web.Extensions needs to be deleted."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.Configuration.ConfigurationManager, Version=5.0.0 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.Primitives, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.Http, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.Duplex, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.NetTcp, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.Security, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package System.ServiceModel.Federation, Version=4.8.1 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers, Version=0.3.261602 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA106",
|
||||
"message": {
|
||||
"text": "Package Microsoft.Windows.Compatibility, Version=6.0.0 needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ruleId": "UA108",
|
||||
"message": {
|
||||
"text": "Framework Reference to Microsoft.AspNetCore.App needs to be added."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"columnKind": "utf16CodeUnits"
|
||||
},
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "API Upgradability",
|
||||
"semanticVersion": "0.3.261602",
|
||||
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
|
||||
}
|
||||
},
|
||||
"results": [],
|
||||
"columnKind": "utf16CodeUnits"
|
||||
},
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "Component Analysis",
|
||||
"semanticVersion": "0.3.261602",
|
||||
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
|
||||
"rules": [
|
||||
{
|
||||
"id": "UA202",
|
||||
"name": "Microsoft.DotNet.UpgradeAssistant.Extensions.Windows.WinformsDpiSettingUpdater",
|
||||
"fullDescription": {
|
||||
"text": "Winforms Source Updater"
|
||||
},
|
||||
"helpUri": "about:blank"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"results": [
|
||||
{
|
||||
"ruleId": "UA202",
|
||||
"message": {
|
||||
"text": "HighDpiMode needs to set in Main() instead of app.config or app.manifest - Application.SetHighDpiMode(HighDpiMode.<setting>). It is recommended to use SystemAware as the HighDpiMode option for better results."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/Program.cs"
|
||||
},
|
||||
"region": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"columnKind": "utf16CodeUnits"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.4.0" newVersion="4.1.4.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="PacketDotNet" publicKeyToken="451414c7667b2a58" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<System.Windows.Forms.ApplicationConfigurationSection>
|
||||
<add key="DpiAwareness" value="PerMonitorV2" />
|
||||
</System.Windows.Forms.ApplicationConfigurationSection>
|
||||
</configuration>
|
||||
@@ -1,17 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A6005D08-70F0-44CB-A384-9219EBA84F21}</ProjectGuid>
|
||||
<TargetFramework>net6.0-windows10.0.20348.0</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>ColorControl</RootNamespace>
|
||||
<AssemblyName>ColorControl</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
@@ -26,267 +16,52 @@
|
||||
<UpdateUrl>https://www.maassoft.com/downloads/ColorControl/</UpdateUrl>
|
||||
<ProductName>ColorControl</ProductName>
|
||||
<PublisherName>Maassoft</PublisherName>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>4.0.0.0</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<Company>Maassoft</Company>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>5.7.1.0</ApplicationVersion>
|
||||
<Version>5.7.1.0</Version>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWPF>false</UseWPF>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
<ApplicationIcon>pngbarn.ico</ApplicationIcon>
|
||||
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<Copyright>2020-2022 Maassoft</Copyright>
|
||||
<Authors>Maassoft</Authors>
|
||||
<ManifestCertificateThumbprint>CF0BC153EA664CD97AC6F234B91F45D34630958F</ManifestCertificateThumbprint>
|
||||
<ManifestKeyFile>ColorControl_1_TemporaryKey.pfx</ManifestKeyFile>
|
||||
<GenerateManifests>true</GenerateManifests>
|
||||
<TargetZone>LocalIntranet</TargetZone>
|
||||
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
|
||||
<SignManifests>true</SignManifests>
|
||||
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestCertificateThumbprint>CF0BC153EA664CD97AC6F234B91F45D34630958F</ManifestCertificateThumbprint>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestKeyFile>ColorControl_1_TemporaryKey.pfx</ManifestKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<GenerateManifests>true</GenerateManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetZone>LocalIntranet</TargetZone>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignManifests>true</SignManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>pngbarn.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Win32.TaskScheduler, Version=2.9.1.0, Culture=neutral, PublicKeyToken=e25603a88b3aa7da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\TaskScheduler.2.9.1\lib\net452\Microsoft.Win32.TaskScheduler.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Native, Version=0.1.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Native.0.1.1\lib\net46\Native.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.7.10\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NStandard, Version=0.8.9.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NStandard.0.8.9\lib\net46\NStandard.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NvAPIWrapper, Version=0.8.1.100, Culture=neutral, PublicKeyToken=310fd07b25df79b3, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NvAPIWrapper.Net.0.8.1.101\lib\net45\NvAPIWrapper.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NWin32, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NWin32.1.2.0\lib\net46\NWin32.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PacketDotNet, Version=1.2.0.0, Culture=neutral, PublicKeyToken=451414c7667b2a58, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\PacketDotNet.1.2.0\lib\net45\PacketDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="SharpPcap, Version=5.4.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpPcap.5.4.0\lib\netstandard2.0\SharpPcap.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="Windows">
|
||||
<HintPath>..\..\..\..\..\..\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.18362.0\Windows.winmd</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ADL.cs" />
|
||||
<Compile Include="ADLWrapper.cs" />
|
||||
<Compile Include="AmdDisplayInfo.cs" />
|
||||
<Compile Include="AppContext.cs" />
|
||||
<Compile Include="CCD.cs" />
|
||||
<Compile Include="LgDevice.cs" />
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="RemoteControlPanel.cs">
|
||||
<Compile Update="RemoteControlPanel.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="RemoteControlPanel.Designer.cs">
|
||||
<DependentUpon>RemoteControlPanel.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UserSessionInfo.cs" />
|
||||
<Compile Include="ServiceBase.cs" />
|
||||
<Compile Include="NvDisplayInfo.cs" />
|
||||
<Compile Include="MessageForms.cs" />
|
||||
<Compile Include="GraphicsService.cs" />
|
||||
<Compile Include="AmdService.cs" />
|
||||
<Compile Include="AmdPreset.cs" />
|
||||
<Compile Include="RemoteControlForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="RemoteControlForm.Designer.cs">
|
||||
<DependentUpon>RemoteControlForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="StartUpParams.cs" />
|
||||
<Compile Include="LgServiceConfig.cs" />
|
||||
<Compile Include="WOL.cs" />
|
||||
<Compile Include="RestartDetector.cs" />
|
||||
<Compile Include="ExceptionExtensions.cs" />
|
||||
<Compile Include="lgtv\App.cs" />
|
||||
<Compile Include="lgtv\Channel.cs" />
|
||||
<Compile Include="lgtv\ClientKeyStore.cs" />
|
||||
<Compile Include="lgtv\ExternalInput.cs" />
|
||||
<Compile Include="lgtv\IsConnectedDelegate.cs" />
|
||||
<Compile Include="lgtv\LgTvApi.cs" />
|
||||
<Compile Include="lgtv\LgTvConnection.cs" />
|
||||
<Compile Include="lgtv\LgWebOsMouseService.cs" />
|
||||
<Compile Include="lgtv\MessageType.cs" />
|
||||
<Compile Include="lgtv\RawRequestMessage.cs" />
|
||||
<Compile Include="lgtv\RequestMessage.cs" />
|
||||
<Compile Include="PnpDev.cs" />
|
||||
<Compile Include="TreeNodeBuilder.cs" />
|
||||
<Compile Include="ColorDataConverter.cs" />
|
||||
<Compile Include="Config.cs" />
|
||||
<Compile Include="lgtv\LgApp.cs" />
|
||||
<Compile Include="PresetBase.cs" />
|
||||
<Compile Include="LgPreset.cs" />
|
||||
<Compile Include="NvPreset.cs" />
|
||||
<Compile Include="LgService.cs" />
|
||||
<Compile Include="NvService.cs" />
|
||||
<Compile Include="ScreenInterrogatory.cs" />
|
||||
<Compile Include="Utils.cs" />
|
||||
<Compile Include="MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Resources\LG_register.json" />
|
||||
<EmbeddedResource Include="Resources\RC_coords.txt" />
|
||||
<None Include="ColorControl_1_TemporaryKey.pfx" />
|
||||
<None Include="Resources\LG_remote.jpg" />
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="RemoteControlForm.resx">
|
||||
<DependentUpon>RemoteControlForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="RemoteControlPanel.resx">
|
||||
<DependentUpon>RemoteControlPanel.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<None Include="ColorControl_TemporaryKey.pfx" />
|
||||
<EmbeddedResource Include="Resources\LG_remote.jpg" />
|
||||
<Content Include="LgPresets.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="lgtv\README.md" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\app.manifest" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="pngbarn.ico" />
|
||||
@@ -305,5 +80,22 @@
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="NLog" Version="4.7.13" />
|
||||
<PackageReference Include="NStandard" Version="0.8.28" />
|
||||
<PackageReference Include="NvAPIWrapper.Net" Version="0.8.1.101" />
|
||||
<PackageReference Include="NWin32" Version="1.2.0" />
|
||||
<PackageReference Include="TaskScheduler" Version="2.9.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,27 +1,40 @@
|
||||
using NvAPIWrapper.Display;
|
||||
using Newtonsoft.Json;
|
||||
using NvAPIWrapper.Display;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
public class ColorDataConverter : JavaScriptConverter
|
||||
public class ColorDataConverter : JsonConverter<ColorData>
|
||||
{
|
||||
public override IEnumerable<Type> SupportedTypes
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override ColorData ReadJson(JsonReader reader, Type objectType, ColorData existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
get { return new List<Type>() { typeof(ColorData) }; }
|
||||
var result = new Dictionary<string, object>();
|
||||
|
||||
if (reader.TokenType == JsonToken.StartObject)
|
||||
{
|
||||
reader.Read();
|
||||
while (reader.TokenType == JsonToken.PropertyName)
|
||||
{
|
||||
var propertyName = reader.Value;
|
||||
|
||||
reader.Read();
|
||||
var value = reader.Value;
|
||||
|
||||
result.Add(propertyName.ToString(), value);
|
||||
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
return NvPreset.GenerateColorData(result);
|
||||
}
|
||||
|
||||
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
|
||||
public override void WriteJson(JsonWriter writer, ColorData value, JsonSerializer serializer)
|
||||
{
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
if (obj == null) return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
|
||||
{
|
||||
return NvPreset.GenerateColorData(dictionary);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-8
@@ -1,23 +1,30 @@
|
||||
namespace ColorControl
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
public class ListViewSortState
|
||||
{
|
||||
public SortOrder SortOrder { get; set; }
|
||||
public int SortIndex { get; set; }
|
||||
}
|
||||
|
||||
public class Config
|
||||
{
|
||||
public bool StartMinimized { get; set; }
|
||||
|
||||
public bool MinimizeOnClose { get; set; }
|
||||
|
||||
public bool MinimizeToTray { get; set; }
|
||||
|
||||
public bool CheckForUpdates { get; set; }
|
||||
public int DisplaySettingsDelay { get; set; }
|
||||
|
||||
public string ScreenSaverShortcut { get; set; }
|
||||
|
||||
public int FormWidth { get; set; }
|
||||
public int FormHeight { get; set; }
|
||||
|
||||
public int NvPresetId_ApplyOnStartup { get; set; }
|
||||
|
||||
public int AmdPresetId_ApplyOnStartup { get; set; }
|
||||
public bool FixChromeFonts { get; set; }
|
||||
public bool UseGdiScaling { get; set; }
|
||||
public ListViewSortState NvPresetsSortState { get; set; }
|
||||
public ListViewSortState AmdPresetsSortState { get; set; }
|
||||
public ListViewSortState LgPresetsSortState { get; set; }
|
||||
|
||||
public Config()
|
||||
{
|
||||
@@ -26,6 +33,12 @@
|
||||
FormWidth = 900;
|
||||
FormHeight = 600;
|
||||
MinimizeToTray = true;
|
||||
CheckForUpdates = true;
|
||||
FixChromeFonts = false;
|
||||
UseGdiScaling = true;
|
||||
LgPresetsSortState = new ListViewSortState();
|
||||
NvPresetsSortState = new ListViewSortState();
|
||||
AmdPresetsSortState = new ListViewSortState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,8 @@ namespace ColorControl
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public GraphicsService(string dataPath) : base(dataPath)
|
||||
public GraphicsService(string dataPath, string presetsBaseFilename) : base(dataPath, presetsBaseFilename)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
~GraphicsService()
|
||||
@@ -23,7 +22,7 @@ namespace ColorControl
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
public abstract bool HasDisplaysAttached();
|
||||
public abstract bool HasDisplaysAttached(bool reinitialize = false);
|
||||
|
||||
protected void ToggleHDR(int delay = 1000)
|
||||
{
|
||||
@@ -52,7 +51,7 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical)
|
||||
protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical, bool updateRegistry = false)
|
||||
{
|
||||
uint i = 0;
|
||||
DEVMODEA devMode;
|
||||
@@ -65,7 +64,7 @@ namespace ColorControl
|
||||
{
|
||||
IntPtr bla = Marshal.AllocHGlobal(Marshal.SizeOf(devMode));
|
||||
Marshal.StructureToPtr(devMode, bla, false);
|
||||
var result = NativeMethods.ChangeDisplaySettingsExA(displayName, bla, IntPtr.Zero, 0, IntPtr.Zero);
|
||||
var result = NativeMethods.ChangeDisplaySettingsExA(displayName, bla, IntPtr.Zero, updateRegistry ? (uint)NativeConstants.CDS_UPDATEREGISTRY : 0, IntPtr.Zero);
|
||||
if (result != NativeConstants.DISP_CHANGE_SUCCESSFUL)
|
||||
{
|
||||
Logger.Error($"Could not set refreshrate {refreshRate} on display {displayName} because ChangeDisplaySettingsExA returned a non-zero return code: {result}");
|
||||
|
||||
+304
-47
@@ -19,12 +19,18 @@ namespace ColorControl
|
||||
public decimal MinValue { get; set; }
|
||||
public decimal MaxValue { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int CurrentValue { get; set; }
|
||||
public int NumberOfValues { get; set; }
|
||||
public LgPreset Preset { get; set; }
|
||||
}
|
||||
|
||||
public class LgDevicePictureSettings
|
||||
{
|
||||
public int Backlight { get; set; }
|
||||
public int Contrast { get; set; }
|
||||
public int Brightness { get; set; }
|
||||
public int Color { get; set; }
|
||||
}
|
||||
|
||||
public enum PowerState
|
||||
@@ -33,7 +39,8 @@ namespace ColorControl
|
||||
Active,
|
||||
Power_Off,
|
||||
Suspend,
|
||||
Active_Standby
|
||||
Active_Standby,
|
||||
Screen_Off
|
||||
}
|
||||
|
||||
public enum PowerOffSource
|
||||
@@ -45,6 +52,7 @@ namespace ColorControl
|
||||
|
||||
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
public static Func<string, string[], bool> ExternalServiceHandler;
|
||||
public static List<string> DefaultActionsOnGameBar = new() { "backlight", "contrast", "brightness", "color" };
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string IpAddress { get; private set; }
|
||||
@@ -58,6 +66,30 @@ namespace ColorControl
|
||||
public bool PowerOffOnShutdown { get; set; }
|
||||
public bool PowerOffOnStandby { get; set; }
|
||||
public bool PowerSwitchOnScreenSaver { get; set; }
|
||||
public bool PowerOnAfterManualPowerOff { get; set; }
|
||||
public bool PowerByWindows { get; set; }
|
||||
public bool TriggersEnabled { get; set; }
|
||||
public int HDMIPortNumber { get; set; }
|
||||
|
||||
private List<string> _actionsForGameBar;
|
||||
|
||||
public List<string> ActionsOnGameBar {
|
||||
get
|
||||
{
|
||||
return _actionsForGameBar;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value?.Any() == false)
|
||||
{
|
||||
_actionsForGameBar = DefaultActionsOnGameBar;
|
||||
}
|
||||
else
|
||||
{
|
||||
_actionsForGameBar = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public PowerState CurrentState { get; set; }
|
||||
@@ -84,45 +116,82 @@ namespace ColorControl
|
||||
[JsonIgnore]
|
||||
public LgDevicePictureSettings PictureSettings { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string CurrentAppId { get; private set; }
|
||||
|
||||
public event EventHandler PictureSettingsChangedEvent;
|
||||
public event EventHandler PowerStateChangedEvent;
|
||||
|
||||
[JsonConstructor]
|
||||
public LgDevice(string name, string ipAddress, string macAddress, bool isCustom = true, bool isDummy = false)
|
||||
{
|
||||
PictureSettings = new LgDevicePictureSettings();
|
||||
ActionsOnGameBar = new List<string> { "backlight", "contrast", "brightness", "color" };
|
||||
|
||||
Name = name;
|
||||
IpAddress = ipAddress;
|
||||
MacAddress = macAddress;
|
||||
IsCustom = isCustom;
|
||||
IsDummy = isDummy;
|
||||
TriggersEnabled = true;
|
||||
|
||||
AddInvokableAction("WOL", new Func<Dictionary<string, object>, bool>(WakeAction));
|
||||
AddGenericPictureAction("backlight", minValue: 0, maxValue: 100);
|
||||
AddGenericPictureAction("brightness", minValue: 0, maxValue: 100);
|
||||
AddGenericPictureAction("contrast", minValue: 0, maxValue: 100);
|
||||
AddGenericPictureAction("color", minValue: 0, maxValue: 100);
|
||||
AddGenericPictureAction("pictureMode", typeof(PictureMode));
|
||||
AddGenericPictureAction("colorGamut", typeof(ColorGamut));
|
||||
AddGenericPictureAction("dynamicContrast", typeof(OffToAuto));
|
||||
AddGenericPictureAction("pictureMode", typeof(PictureMode), title: "Picture Mode");
|
||||
//AddGenericPictureAction("dynamicRange", typeof(DynamicRange), category: "dimensionInfo", title: "Dynamic Range");
|
||||
AddGenericPictureAction("colorGamut", typeof(ColorGamut), title: "Color Gamut");
|
||||
AddGenericPictureAction("dynamicContrast", typeof(OffToHigh), title: "Dynamic Contrast");
|
||||
AddGenericPictureAction("gamma", typeof(GammaExp));
|
||||
AddGenericPictureAction("whiteBalanceColorTemperature", typeof(WhiteBalanceColorTemperature), title: "White Balance Color Temperature");
|
||||
//AddGenericPictureAction("dynamicColor", typeof(OffToAuto));
|
||||
//AddGenericPictureAction("superResolution", typeof(OffToAuto));
|
||||
AddGenericPictureAction("peakBrightness", typeof(OffToAuto));
|
||||
AddGenericPictureAction("smoothGradation", typeof(OffToAuto));
|
||||
AddGenericPictureAction("energySaving", typeof(EnergySaving));
|
||||
AddGenericPictureAction("hdrDynamicToneMapping", typeof(DynamicTonemapping));
|
||||
AddGenericPictureAction("peakBrightness", typeof(OffToHigh), title: "Peak Brightness");
|
||||
AddGenericPictureAction("smoothGradation", typeof(OffToAuto), title: "Smooth Gradation");
|
||||
AddGenericPictureAction("energySaving", typeof(EnergySaving), title: "Energy Saving");
|
||||
AddGenericPictureAction("hdrDynamicToneMapping", typeof(DynamicTonemapping), title: "HDR Dynamic Tone Mapping");
|
||||
//AddGenericPictureAction("blackLevel", typeof(LowToAuto));
|
||||
//AddGenericPictureAction("ambientLightCompensation", typeof(OffToAuto2));
|
||||
//AddGenericPictureAction("truMotionMode", typeof(TruMotionMode));
|
||||
AddGenericPictureAction("motionProOLED", typeof(OffToHigh));
|
||||
AddGenericPictureAction("motionProOLED", typeof(OffToHigh), title: "OLED Motion Pro");
|
||||
AddGenericPictureAction("motionPro", typeof(OffToOn), title: "Motion Pro");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI1", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI2", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI3", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI4", typeof(OffToOn), category: "other");
|
||||
//AddGenericPictureAction("hdmiPcMode", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI1", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI2", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI3", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI4", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50);
|
||||
//AddGenericPictureAction("freesyncOLEDHDMI4", typeof(OffToOn), category: "other");
|
||||
//AddGenericPictureAction("freesyncSupport", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi1", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi2", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi3", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi4", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
//AddGenericPictureAction("wb20PointsGammaValue", minValue: -50, maxValue: 50);
|
||||
AddInvokableAction("turnScreenOff", new Func<Dictionary<string, object>, bool>(TurnScreenOffAction));
|
||||
AddInvokableAction("turnScreenOn", new Func<Dictionary<string, object>, bool>(TurnScreenOnAction));
|
||||
|
||||
AddInternalPresetAction(new LgPreset("InStart", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "inStart" }));
|
||||
AddInternalPresetAction(new LgPreset("EzAdjust", "com.webos.app.factorywin", new[] { "0", "4", "1", "3" }, new { id = "executeFactory", irKey = "ezAdjust" }));
|
||||
AddInternalPresetAction(new LgPreset("Software Update", "com.webos.app.softwareupdate", null, new { mode = "user", flagUpdate = true }));
|
||||
|
||||
AddSetDeviceConfigAction("HDMI_1_icon", typeof(HdmiIcon), "HDMI 1 icon");
|
||||
AddSetDeviceConfigAction("HDMI_2_icon", typeof(HdmiIcon), "HDMI 2 icon");
|
||||
AddSetDeviceConfigAction("HDMI_3_icon", typeof(HdmiIcon), "HDMI 3 icon");
|
||||
AddSetDeviceConfigAction("HDMI_4_icon", typeof(HdmiIcon), "HDMI 4 icon");
|
||||
|
||||
AddGenericPictureAction("soundMode", typeof(SoundMode), category: "sound", title: "Sound Mode");
|
||||
AddGenericPictureAction("soundOutput", typeof(SoundOutput), category: "sound", title: "Sound Output");
|
||||
//await ExecuteRequest("luna://com.webos.settingsservice/setSystemSettings", new { category = "network", settings = new { wolwowlOnOff = "true" } });
|
||||
AddGenericPictureAction("wolwowlOnOff", typeof(FalseToTrue), category: "network", title: "Wake-On-LAN");
|
||||
}
|
||||
|
||||
private void AddInvokableAction(string name, Func<Dictionary<string, object>, bool> function)
|
||||
@@ -136,7 +205,18 @@ namespace ColorControl
|
||||
_invokableActions.Add(action);
|
||||
}
|
||||
|
||||
private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture")
|
||||
private void AddInternalPresetAction(LgPreset preset)
|
||||
{
|
||||
var action = new InvokableAction
|
||||
{
|
||||
Name = preset.name,
|
||||
Preset = preset
|
||||
};
|
||||
|
||||
_invokableActions.Add(action);
|
||||
}
|
||||
|
||||
private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1)
|
||||
{
|
||||
var action = new InvokableAction
|
||||
{
|
||||
@@ -145,12 +225,40 @@ namespace ColorControl
|
||||
EnumType = type,
|
||||
MinValue = minValue,
|
||||
MaxValue = maxValue,
|
||||
Category = category
|
||||
NumberOfValues = numberOfValues,
|
||||
Category = category,
|
||||
Title = title == null ? Utils.FirstCharUpperCase(name) : title
|
||||
};
|
||||
|
||||
_invokableActions.Add(action);
|
||||
}
|
||||
|
||||
private void AddSetDeviceConfigAction(string name, Type type, string title)
|
||||
{
|
||||
var action = new InvokableAction
|
||||
{
|
||||
Name = name,
|
||||
Function = new Func<Dictionary<string, object>, bool>(GenericDeviceConfigAction),
|
||||
EnumType = type,
|
||||
Title = title == null ? Utils.FirstCharUpperCase(name) : title
|
||||
};
|
||||
|
||||
_invokableActions.Add(action);
|
||||
}
|
||||
|
||||
public void AddGameBarAction(string name)
|
||||
{
|
||||
if (!ActionsOnGameBar.Contains(name))
|
||||
{
|
||||
ActionsOnGameBar.Add(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveGameBarAction(string name)
|
||||
{
|
||||
ActionsOnGameBar.Remove(name);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
//return (IsDummy ? string.Empty : (IsCustom ? "Custom: " : "Auto detect: ")) + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $" ({IpAddress})" : string.Empty);
|
||||
@@ -186,6 +294,9 @@ namespace ColorControl
|
||||
//await _lgTvApi.SubscribeVolume(VolumeChanged);
|
||||
await _lgTvApi.SubscribePowerState(PowerStateChanged);
|
||||
await _lgTvApi.SubscribePictureSettings(PictureSettingsChanged);
|
||||
await _lgTvApi.SubscribeForegroundApp(ForegroundAppChanged);
|
||||
|
||||
//await _lgTvApi.Reboot();
|
||||
|
||||
//var result = await GetPictureSettings();
|
||||
|
||||
@@ -223,16 +334,28 @@ namespace ColorControl
|
||||
{
|
||||
var settings = payload.settings;
|
||||
|
||||
Logger.Debug($"[{Name}] PictureSettingsChanged: {JsonConvert.SerializeObject(settings)}");
|
||||
|
||||
if (settings.backlight != null)
|
||||
{
|
||||
PictureSettings.Backlight = int.Parse(settings.backlight.Value);
|
||||
PictureSettings.Backlight = Utils.ParseDynamicAsInt(settings.backlight, PictureSettings.Backlight);
|
||||
}
|
||||
if (settings.contrast != null)
|
||||
{
|
||||
PictureSettings.Contrast = int.Parse(settings.contrast.Value);
|
||||
PictureSettings.Contrast = Utils.ParseDynamicAsInt(settings.contrast, PictureSettings.Contrast);
|
||||
}
|
||||
if (settings.brightness != null)
|
||||
{
|
||||
PictureSettings.Brightness = Utils.ParseDynamicAsInt(settings.brightness, PictureSettings.Brightness);
|
||||
}
|
||||
if (settings.color != null)
|
||||
{
|
||||
PictureSettings.Color = Utils.ParseDynamicAsInt(settings.color, PictureSettings.Color);
|
||||
}
|
||||
}
|
||||
|
||||
PictureSettingsChangedEvent?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,6 +378,10 @@ namespace ColorControl
|
||||
PoweredOffBy = PowerOffSource.Unknown;
|
||||
_poweredOffViaAppDateTime = DateTimeOffset.MinValue;
|
||||
}
|
||||
else if (payload.processing != null && ((string)payload.processing).Equals("Request Power Off", StringComparison.Ordinal))
|
||||
{
|
||||
PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually;
|
||||
}
|
||||
}
|
||||
else {
|
||||
PoweredOffBy = PoweredOffViaApp ? PowerOffSource.App : PowerOffSource.Manually;
|
||||
@@ -268,6 +395,20 @@ namespace ColorControl
|
||||
|
||||
Logger.Debug($"PoweredOffBy: {PoweredOffBy}, PoweredOffViaApp: {PoweredOffViaApp}");
|
||||
|
||||
PowerStateChangedEvent?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ForegroundAppChanged(dynamic payload)
|
||||
{
|
||||
Logger.Debug($"[{Name}] ForegroundAppChanged: {JsonConvert.SerializeObject(payload)}");
|
||||
|
||||
if (payload.appId != null)
|
||||
{
|
||||
CurrentAppId = payload.appId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -284,7 +425,8 @@ namespace ColorControl
|
||||
_lgTvApi = null;
|
||||
}
|
||||
}
|
||||
public async Task<bool> ExecutePreset(LgPreset preset, bool reconnect = false)
|
||||
|
||||
public async Task<bool> ExecutePreset(LgPreset preset, bool reconnect, LgServiceConfig config)
|
||||
{
|
||||
var hasApp = !string.IsNullOrEmpty(preset.appId);
|
||||
|
||||
@@ -310,7 +452,27 @@ namespace ColorControl
|
||||
{
|
||||
try
|
||||
{
|
||||
await _lgTvApi.LaunchApp(preset.appId);
|
||||
var @params = preset.AppParams;
|
||||
if (@params == null && config.ShowAdvancedActions)
|
||||
{
|
||||
if (preset.appId.Equals("com.webos.app.softwareupdate"))
|
||||
{
|
||||
@params = new { mode = "user", flagUpdate = true };
|
||||
}
|
||||
else if (preset.appId.Equals("com.webos.app.factorywin"))
|
||||
{
|
||||
if (preset.name.Contains("ezadjust", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@params = new { id = "executeFactory", irKey = "ezAdjust" };
|
||||
}
|
||||
else
|
||||
{
|
||||
@params = new { id = "executeFactory", irKey = "inStart" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _lgTvApi.LaunchApp(preset.appId, @params);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -375,38 +537,49 @@ namespace ColorControl
|
||||
key = keyValue[0];
|
||||
parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Split(';');
|
||||
}
|
||||
|
||||
var delay = 0;
|
||||
if (keySpec.Length == 2)
|
||||
{
|
||||
delay = Utils.ParseInt(keySpec[1]);
|
||||
}
|
||||
|
||||
var executeKey = true;
|
||||
var action = _invokableActions.FirstOrDefault(a => a.Name.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
if (action != null)
|
||||
{
|
||||
if (!key.Equals("WOL"))
|
||||
{
|
||||
ExecuteAction(action, parameters);
|
||||
}
|
||||
ExecuteAction(action, parameters);
|
||||
|
||||
continue;
|
||||
executeKey = false;
|
||||
}
|
||||
if (ExternalServiceHandler != null && parameters != null)
|
||||
{
|
||||
if (ExternalServiceHandler(key, parameters))
|
||||
{
|
||||
continue;
|
||||
executeKey = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (keySpec.Length == 2)
|
||||
{
|
||||
SendKey(mouse, key, int.Parse(keySpec[1]));
|
||||
}
|
||||
else
|
||||
if (executeKey)
|
||||
{
|
||||
SendKey(mouse, key);
|
||||
delay = delay == 0 ? 180 : delay;
|
||||
}
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteAction(InvokableAction action, string[] parameters)
|
||||
public void ExecuteAction(InvokableAction action, string[] parameters)
|
||||
{
|
||||
var function = action.Function;
|
||||
if (function == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (parameters?.Length > 0)
|
||||
{
|
||||
@@ -423,15 +596,14 @@ namespace ColorControl
|
||||
function(null);
|
||||
}
|
||||
|
||||
private void SendKey(LgWebOsMouseService mouse, string key, int delay = 180)
|
||||
private void SendKey(LgWebOsMouseService mouse, string key)
|
||||
{
|
||||
if (key.Length == 1 && int.TryParse(key, out _))
|
||||
if (key.Length >= 1 && int.TryParse(key[0].ToString(), out _))
|
||||
{
|
||||
key = "_" + key;
|
||||
}
|
||||
var button = (ButtonType)Enum.Parse(typeof(ButtonType), key);
|
||||
mouse.SendButton(button);
|
||||
Thread.Sleep(delay);
|
||||
}
|
||||
|
||||
public async Task<LgWebOsMouseService> GetMouseAsync()
|
||||
@@ -441,6 +613,11 @@ namespace ColorControl
|
||||
|
||||
public async Task<IEnumerable<LgApp>> GetApps(bool force = false)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
|
||||
if (!await Connected(force))
|
||||
{
|
||||
Logger.Debug("Cannot refresh apps: no connection could be made");
|
||||
@@ -450,17 +627,30 @@ namespace ColorControl
|
||||
return await _lgTvApi.GetApps(force);
|
||||
}
|
||||
|
||||
internal async Task<bool> PowerOff()
|
||||
internal async Task<bool> PowerOff(bool checkHdmi = false)
|
||||
{
|
||||
if (!await Connected(true) || CurrentState != PowerState.Active)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkHdmi && CurrentAppId != null && HDMIPortNumber != 0 && !CurrentAppId.EndsWith($".hdmi{HDMIPortNumber}", StringComparison.InvariantCulture))
|
||||
{
|
||||
Logger.Debug($"[{Name}] PowerOff is ignored because current app {CurrentAppId} does not match configured HDMI port {HDMIPortNumber}");
|
||||
return true;
|
||||
}
|
||||
|
||||
PoweredOffViaApp = true;
|
||||
_poweredOffViaAppDateTime = DateTimeOffset.Now;
|
||||
|
||||
await _lgTvApi.TurnOff();
|
||||
try
|
||||
{
|
||||
await _lgTvApi.TurnOff().WaitAsync(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
Logger.Debug($"Timeout when turning off tv: {ex.Message}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -506,7 +696,7 @@ namespace ColorControl
|
||||
}
|
||||
Logger.Debug("WOL succeeded");
|
||||
await Task.Delay(connectDelay);
|
||||
result = await Connect();
|
||||
result = await Connect(1);
|
||||
Logger.Debug("Connect succeeded: " + result);
|
||||
return result;
|
||||
}
|
||||
@@ -557,15 +747,6 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
var resumeScript = Path.Combine(Program.DataDir, "ResumeScript.bat");
|
||||
if (File.Exists(resumeScript))
|
||||
{
|
||||
Utils.StartProcess(resumeScript);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -574,6 +755,18 @@ namespace ColorControl
|
||||
return _invokableActions;
|
||||
}
|
||||
|
||||
public List<InvokableAction> GetInvokableActionsForGameBar()
|
||||
{
|
||||
var actions = GetInvokableActions();
|
||||
return actions.Where(a => a.Category == "picture" && (a.EnumType != null && a.EnumType != typeof(PictureMode) || a.MinValue >= 0 && a.MaxValue > 0)).ToList();
|
||||
}
|
||||
|
||||
public List<InvokableAction> GetActionsForGameBar()
|
||||
{
|
||||
var actions = GetInvokableActions();
|
||||
return actions.Where(a => ActionsOnGameBar.Contains(a.Name)).ToList();
|
||||
}
|
||||
|
||||
public async Task PowerOn()
|
||||
{
|
||||
var mouse = await _lgTvApi.GetMouse();
|
||||
@@ -622,26 +815,33 @@ namespace ColorControl
|
||||
await _lgTvApi.SetSystemSettings("backlight", backlight.ToString());
|
||||
}
|
||||
|
||||
internal async Task SetContrast(int contrast)
|
||||
{
|
||||
await _lgTvApi.SetSystemSettings("contrast", contrast.ToString());
|
||||
}
|
||||
|
||||
public async Task SetOLEDMotionPro(string mode)
|
||||
{
|
||||
await _lgTvApi.SetConfig("tv.model.motionProMode", mode);
|
||||
}
|
||||
|
||||
internal async Task SetConfig(string key, string value)
|
||||
internal async Task SetConfig(string key, object value)
|
||||
{
|
||||
await _lgTvApi.SetConfig(key, value);
|
||||
}
|
||||
internal async Task SetSystemSettings(string name, string value)
|
||||
{
|
||||
await _lgTvApi.SetSystemSettings(name, value);
|
||||
|
||||
UpdateCurrentValueOfAction(name, value);
|
||||
}
|
||||
|
||||
internal async Task<dynamic> GetPictureSettings()
|
||||
{
|
||||
//var keys = new[] { "backlight", "brightness", "contrast", "color", "pictureMode", "colorGamut", "dynamicContrast", "peakBrightness", "smoothGradation", "energySaving", "motionProOLED" };
|
||||
var keys = new[] { "backlight", "brightness", "contrast", "color" };
|
||||
//var keys = new[] { "backlight", "brightness", "contrast", "color" };
|
||||
|
||||
return await _lgTvApi.GetSystemSettings("picture", keys);
|
||||
return await _lgTvApi.GetSystemSettings2("picture");
|
||||
}
|
||||
|
||||
private bool WakeAction(Dictionary<string, object> parameters)
|
||||
@@ -649,6 +849,22 @@ namespace ColorControl
|
||||
return Wake();
|
||||
}
|
||||
|
||||
private bool TurnScreenOffAction(Dictionary<string, object> parameters)
|
||||
{
|
||||
var task = _lgTvApi.TurnScreenOff();
|
||||
Utils.WaitForTask(task);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TurnScreenOnAction(Dictionary<string, object> parameters)
|
||||
{
|
||||
var task = _lgTvApi.TurnScreenOn();
|
||||
Utils.WaitForTask(task);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GenericPictureAction(Dictionary<string, object> parameters)
|
||||
{
|
||||
var settingName = parameters["name"].ToString();
|
||||
@@ -662,7 +878,48 @@ namespace ColorControl
|
||||
var task = _lgTvApi.SetSystemSettings(settingName, value, category);
|
||||
Utils.WaitForTask(task);
|
||||
|
||||
UpdateCurrentValueOfAction(settingName, value.ToString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GenericDeviceConfigAction(Dictionary<string, object> parameters)
|
||||
{
|
||||
var id = parameters["name"].ToString().Replace("_icon", string.Empty);
|
||||
var stringValues = parameters["value"] as string[];
|
||||
var value = stringValues[0];
|
||||
|
||||
var description = Utils.GetDescriptionByEnumName<HdmiIcon>(value);
|
||||
|
||||
var task = _lgTvApi.SetDeviceConfig(id, value, description);
|
||||
Utils.WaitForTask(task);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateCurrentValueOfAction(string settingName, string value)
|
||||
{
|
||||
if (settingName != "backlight" && settingName != "contrast" && settingName != "brightness" && settingName != "color")
|
||||
{
|
||||
var action = _invokableActions.FirstOrDefault(a => a.Name == settingName);
|
||||
if (action != null)
|
||||
{
|
||||
if (action.EnumType != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var enumValue = Enum.Parse(action.EnumType, value);
|
||||
var intEnum = (int)enumValue;
|
||||
action.CurrentValue = intEnum;
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
else
|
||||
{
|
||||
action.CurrentValue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+139
@@ -0,0 +1,139 @@
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
partial class LgGameBar
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.btnClose = new System.Windows.Forms.Button();
|
||||
this.lblTvName = new System.Windows.Forms.Label();
|
||||
this.tmrHide = new System.Windows.Forms.Timer(this.components);
|
||||
this.lblProcessName = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// flowPanel
|
||||
//
|
||||
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.flowPanel.Location = new System.Drawing.Point(14, 47);
|
||||
this.flowPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.flowPanel.Name = "flowPanel";
|
||||
this.flowPanel.Size = new System.Drawing.Size(270, 325);
|
||||
this.flowPanel.TabIndex = 0;
|
||||
this.flowPanel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.RemoteControlForm_MouseDown);
|
||||
this.flowPanel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.flowPanel_MouseMove);
|
||||
this.flowPanel.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.flowPanel_PreviewKeyDown);
|
||||
//
|
||||
// btnClose
|
||||
//
|
||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnClose.FlatAppearance.BorderColor = System.Drawing.Color.DarkGray;
|
||||
this.btnClose.FlatAppearance.BorderSize = 0;
|
||||
this.btnClose.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
this.btnClose.ForeColor = System.Drawing.Color.White;
|
||||
this.btnClose.Location = new System.Drawing.Point(280, 0);
|
||||
this.btnClose.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.btnClose.Name = "btnClose";
|
||||
this.btnClose.Size = new System.Drawing.Size(16, 27);
|
||||
this.btnClose.TabIndex = 1;
|
||||
this.btnClose.TabStop = false;
|
||||
this.btnClose.Text = "x";
|
||||
this.btnClose.UseVisualStyleBackColor = true;
|
||||
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
|
||||
//
|
||||
// lblTvName
|
||||
//
|
||||
this.lblTvName.AutoSize = true;
|
||||
this.lblTvName.ForeColor = System.Drawing.Color.White;
|
||||
this.lblTvName.Location = new System.Drawing.Point(14, 0);
|
||||
this.lblTvName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.lblTvName.Name = "lblTvName";
|
||||
this.lblTvName.Size = new System.Drawing.Size(25, 15);
|
||||
this.lblTvName.TabIndex = 0;
|
||||
this.lblTvName.Text = " ";
|
||||
//
|
||||
// tmrHide
|
||||
//
|
||||
this.tmrHide.Interval = 5000;
|
||||
this.tmrHide.Tick += new System.EventHandler(this.tmrHide_Tick);
|
||||
//
|
||||
// lblProcessName
|
||||
//
|
||||
this.lblProcessName.AutoSize = true;
|
||||
this.lblProcessName.ForeColor = System.Drawing.Color.White;
|
||||
this.lblProcessName.Location = new System.Drawing.Point(14, 23);
|
||||
this.lblProcessName.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.lblProcessName.Name = "lblProcessName";
|
||||
this.lblProcessName.Size = new System.Drawing.Size(25, 15);
|
||||
this.lblProcessName.TabIndex = 2;
|
||||
this.lblProcessName.Text = " ";
|
||||
//
|
||||
// LgGameBar
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.Black;
|
||||
this.ClientSize = new System.Drawing.Size(298, 387);
|
||||
this.Controls.Add(this.lblProcessName);
|
||||
this.Controls.Add(this.lblTvName);
|
||||
this.Controls.Add(this.btnClose);
|
||||
this.Controls.Add(this.flowPanel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.KeyPreview = true;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "LgGameBar";
|
||||
this.Opacity = 0.7D;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
|
||||
this.Text = "RemoteControlForm";
|
||||
this.TopMost = true;
|
||||
this.Activated += new System.EventHandler(this.LgGameBar_Activated);
|
||||
this.Deactivate += new System.EventHandler(this.LgGameBar_Deactivate);
|
||||
this.Load += new System.EventHandler(this.RemoteControlForm_Load);
|
||||
this.Shown += new System.EventHandler(this.RemoteControlForm_Shown);
|
||||
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.RemoteControlForm_KeyDown);
|
||||
this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.RemoteControlForm_KeyUp);
|
||||
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.RemoteControlForm_MouseDown);
|
||||
this.Move += new System.EventHandler(this.LgGameBar_Move);
|
||||
this.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.RemoteControlForm_PreviewKeyDown);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.FlowLayoutPanel flowPanel;
|
||||
private System.Windows.Forms.Button btnClose;
|
||||
private System.Windows.Forms.Label lblTvName;
|
||||
private System.Windows.Forms.Timer tmrHide;
|
||||
private System.Windows.Forms.Label lblProcessName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
using NWin32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
partial class LgGameBar : Form
|
||||
{
|
||||
private class TbSetting
|
||||
{
|
||||
public TrackBar TrackBar { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Type EnumType { get; set; }
|
||||
public string PropertyName { get; set; }
|
||||
}
|
||||
|
||||
private LgService _lgService;
|
||||
private LgDevice _lgDevice;
|
||||
private ToolTip _toolTip;
|
||||
private bool _settingChanged = false;
|
||||
private List<TrackBar> _trackBars = new List<TrackBar>();
|
||||
private List<string> _lastActions = new List<string>();
|
||||
|
||||
public LgGameBar(LgService lgService)
|
||||
{
|
||||
_lgService = lgService;
|
||||
|
||||
_lgDevice = _lgService.SelectedDevice;
|
||||
|
||||
_lgDevice.PictureSettingsChangedEvent += LgDevice_PictureSettingsChangedEvent;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
_toolTip = new ToolTip();
|
||||
}
|
||||
|
||||
private void GenerateComponents()
|
||||
{
|
||||
var actions = _lgDevice.GetActionsForGameBar();
|
||||
var actionNames = actions.Select(a => a.Name).ToList();
|
||||
|
||||
if (_lastActions.Count == actions.Count && _lastActions.All(a => actionNames.Contains(a))) {
|
||||
return;
|
||||
}
|
||||
|
||||
flowPanel.Controls.Clear();
|
||||
_trackBars.Clear();
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
CreateGroup(action.Title, action.Name, (int)action.MinValue, (int)action.MaxValue, action.EnumType);
|
||||
}
|
||||
|
||||
_lastActions.Clear();
|
||||
_lastActions.AddRange(actionNames);
|
||||
|
||||
Width = 28 + (_trackBars.Count() * 56);
|
||||
}
|
||||
|
||||
private TrackBar CreateGroup(string title, string settingName = null, int min = 0, int max = 100, Type enumType = null)
|
||||
{
|
||||
if (settingName == null)
|
||||
{
|
||||
settingName = title.ToLowerInvariant();
|
||||
}
|
||||
|
||||
var panel = new Panel();
|
||||
panel.Width = 50;
|
||||
panel.Height = flowPanel.Height;
|
||||
var label = new Label();
|
||||
label.Text = title;
|
||||
label.ForeColor = Color.White;
|
||||
label.Parent = panel;
|
||||
|
||||
if (min == 0 && max == 0 && enumType != null)
|
||||
{
|
||||
max = Enum.GetValues(enumType).Length - 1;
|
||||
}
|
||||
|
||||
var trackBar = CreateTrackBar(min, max);
|
||||
trackBar.Top = label.Height;
|
||||
trackBar.Height = panel.Height - trackBar.Top - 4;
|
||||
trackBar.Parent = panel;
|
||||
trackBar.Scroll += tbTrackBar_Scroll;
|
||||
var margin = trackBar.Margin;
|
||||
margin.Top = 0;
|
||||
margin.Bottom = 0;
|
||||
trackBar.Margin = margin;
|
||||
trackBar.ValueChanged += TrackBar_ValueChanged;
|
||||
|
||||
var setting = new TbSetting
|
||||
{
|
||||
TrackBar = trackBar,
|
||||
Name = settingName,
|
||||
EnumType = enumType,
|
||||
PropertyName = title
|
||||
};
|
||||
|
||||
trackBar.Tag = setting;
|
||||
|
||||
panel.Parent = flowPanel;
|
||||
|
||||
_trackBars.Add(trackBar);
|
||||
|
||||
return trackBar;
|
||||
}
|
||||
|
||||
private void TrackBar_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
//var trackBar = sender as TrackBar;
|
||||
|
||||
//if (_toolTip != null)
|
||||
//{
|
||||
// _toolTip.SetToolTip(trackBar, trackBar.Value.ToString());
|
||||
//}
|
||||
}
|
||||
|
||||
private TrackBar CreateTrackBar(int min, int max)
|
||||
{
|
||||
var trackBar = new TrackBar();
|
||||
trackBar.BackColor = Color.Black;
|
||||
trackBar.Orientation = Orientation.Vertical;
|
||||
trackBar.TickFrequency = (max >= 50) ? 5 : 1;
|
||||
trackBar.TickStyle = TickStyle.Both;
|
||||
trackBar.Minimum = min;
|
||||
trackBar.Maximum = max;
|
||||
|
||||
return trackBar;
|
||||
}
|
||||
|
||||
private void LgDevice_PictureSettingsChangedEvent(object sender, EventArgs e)
|
||||
{
|
||||
if (_settingChanged)
|
||||
{
|
||||
_settingChanged = false;
|
||||
return;
|
||||
}
|
||||
BeginInvoke(new Action(UpdateValues));
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
var dynType = _lgDevice.PictureSettings.GetType();
|
||||
var actions = _lgDevice.GetInvokableActions();
|
||||
foreach (var trackbar in _trackBars.Where(t => !t.Focused))
|
||||
{
|
||||
var setting = (TbSetting)trackbar.Tag;
|
||||
|
||||
if (setting.PropertyName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var prop = dynType.GetProperty(setting.PropertyName);
|
||||
|
||||
if (prop != null)
|
||||
{
|
||||
var value = prop.GetValue(_lgDevice.PictureSettings);
|
||||
|
||||
if (value is int intValue)
|
||||
{
|
||||
trackbar.Value = intValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var action = actions.FirstOrDefault(a => a.Name == setting.Name);
|
||||
if (action != null)
|
||||
{
|
||||
trackbar.Value = action.CurrentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoteControlForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
Left = _lgService.Config.GameBarLeft;
|
||||
Top = _lgService.Config.GameBarTop;
|
||||
}
|
||||
|
||||
private void ButtonClick(object sender, EventArgs e)
|
||||
{
|
||||
if (e is MouseEventArgs)
|
||||
{
|
||||
var preset = (LgPreset)(sender as Button).Tag;
|
||||
|
||||
var _ = _lgService.ApplyPreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnClose_Click(object sender, EventArgs e)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void RemoteControlForm_MouseDown(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
NativeMethods.ReleaseCapture();
|
||||
var intPtr = new UIntPtr(2);
|
||||
NativeMethods.SendMessageW(Handle, NativeConstants.WM_NCLBUTTONDOWN, intPtr, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoteControlForm_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
//var key = e.KeyCode;
|
||||
//HandleKey(key);
|
||||
}
|
||||
|
||||
private void flowPanel_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
|
||||
{
|
||||
//HandleKey(e.KeyCode);
|
||||
}
|
||||
|
||||
private void HandleKey(Keys key)
|
||||
{
|
||||
var cvt = new KeysConverter();
|
||||
var shortcut = (string)cvt.ConvertTo(key, typeof(string));
|
||||
}
|
||||
|
||||
private void RemoteControlForm_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
|
||||
{
|
||||
//HandleKey(e.KeyCode);
|
||||
}
|
||||
|
||||
private void RemoteControlForm_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.F1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleKey(e.KeyCode);
|
||||
}
|
||||
|
||||
private void flowPanel_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
ResetAutoHide();
|
||||
}
|
||||
|
||||
private void RemoteControlForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
GenerateComponents();
|
||||
UpdateInfo();
|
||||
ResetAutoHide();
|
||||
}
|
||||
|
||||
private void UpdateInfo()
|
||||
{
|
||||
lblTvName.Text = $"Device: {_lgDevice?.Name ?? "No tv selected"}";
|
||||
lblProcessName.Text = $"Current process: {_lgService.MonitorContext?.ForegroundProcess?.ProcessName ?? _lgService.MonitorContext?.LastFullScreenProcessName ?? "No full screen process"}";
|
||||
|
||||
UpdateValues();
|
||||
}
|
||||
|
||||
private void LgGameBar_Deactivate(object sender, EventArgs e)
|
||||
{
|
||||
_lgService.Config.GameBarLeft = Left;
|
||||
_lgService.Config.GameBarTop = Top;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void tmrHide_Tick(object sender, EventArgs e)
|
||||
{
|
||||
_lgService.Config.GameBarLeft = Left;
|
||||
_lgService.Config.GameBarTop = Top;
|
||||
Hide();
|
||||
}
|
||||
|
||||
private void tbTrackBar_Scroll(object sender, EventArgs e)
|
||||
{
|
||||
_settingChanged = true;
|
||||
ResetAutoHide();
|
||||
|
||||
var trackBar = sender as TrackBar;
|
||||
var setting = (TbSetting)trackBar.Tag;
|
||||
|
||||
var value = string.Empty;
|
||||
|
||||
if (setting.EnumType == null)
|
||||
{
|
||||
value = trackBar.Value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
var enumValue = Enum.Parse(setting.EnumType, trackBar.Value.ToString());
|
||||
value = enumValue.ToString();
|
||||
}
|
||||
|
||||
_lgDevice.SetSystemSettings(setting.Name, value).ConfigureAwait(false);
|
||||
|
||||
if (_toolTip != null)
|
||||
{
|
||||
_toolTip.Show(value, trackBar, 10, 50, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
private void LgGameBar_Activated(object sender, EventArgs e)
|
||||
{
|
||||
GenerateComponents();
|
||||
UpdateInfo();
|
||||
ResetAutoHide();
|
||||
}
|
||||
|
||||
private void LgGameBar_Move(object sender, EventArgs e)
|
||||
{
|
||||
ResetAutoHide();
|
||||
}
|
||||
|
||||
private void ResetAutoHide()
|
||||
{
|
||||
tmrHide.Enabled = false;
|
||||
tmrHide.Enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="tmrHide.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -9,11 +9,10 @@ namespace ColorControl
|
||||
{
|
||||
public static List<LgApp> LgApps { get; set; }
|
||||
public static List<LgDevice> LgDevices { get; set; }
|
||||
|
||||
|
||||
public string appId { get; set; }
|
||||
public List<string> steps { get; set; }
|
||||
public string DeviceMacAddress { get; set; }
|
||||
public dynamic AppParams { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public int X { get; set; }
|
||||
@@ -37,6 +36,17 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
|
||||
public LgPreset(string name, string appId, IEnumerable<string> steps = null, dynamic appParams = null) : this()
|
||||
{
|
||||
this.name = name;
|
||||
this.appId = appId;
|
||||
if (steps != null)
|
||||
{
|
||||
this.steps.AddRange(steps);
|
||||
}
|
||||
AppParams = appParams;
|
||||
}
|
||||
|
||||
public LgPreset Clone()
|
||||
{
|
||||
var preset = new LgPreset(this);
|
||||
|
||||
+279
-168
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -23,6 +24,13 @@ namespace ColorControl
|
||||
StandBy = 5
|
||||
}
|
||||
|
||||
internal enum WindowsPowerSetting
|
||||
{
|
||||
Off = 0,
|
||||
On = 1,
|
||||
Dimmed = 2
|
||||
}
|
||||
|
||||
internal class ProcessMonitorContext
|
||||
{
|
||||
public List<LgDevice> Devices { get; set; }
|
||||
@@ -52,6 +60,7 @@ namespace ColorControl
|
||||
}
|
||||
|
||||
public LgServiceConfig Config { get; private set; }
|
||||
public ProcessMonitorContext MonitorContext { get; private set; }
|
||||
|
||||
private string _configFilename;
|
||||
private bool _allowPowerOn;
|
||||
@@ -64,13 +73,17 @@ namespace ColorControl
|
||||
private int _poweredOffByScreenSaverProcessId;
|
||||
private Task _monitorTask;
|
||||
private int _monitorTaskCounter;
|
||||
|
||||
public LgService(string dataPath, bool allowPowerOn) : base(dataPath)
|
||||
private LgPreset _lastTriggeredPreset;
|
||||
private RestartDetector _restartDetector;
|
||||
|
||||
public LgService(string dataPath, bool allowPowerOn) : base(dataPath, "LgPresets.json")
|
||||
{
|
||||
_allowPowerOn = allowPowerOn;
|
||||
|
||||
LgPreset.LgApps = _lgApps;
|
||||
|
||||
_restartDetector = new RestartDetector();
|
||||
|
||||
LoadConfig();
|
||||
LoadPresets();
|
||||
LoadRemoteControlButtons();
|
||||
@@ -127,46 +140,36 @@ namespace ColorControl
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
private void LoadPresets()
|
||||
protected override List<LgPreset> GetDefaultPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, "LgPresets.json");
|
||||
var toCopy = Path.Combine(Directory.GetCurrentDirectory(), "LgPresets.json");
|
||||
if (!File.Exists(_presetsFilename) && File.Exists(toCopy))
|
||||
List<LgPreset> presets = null;
|
||||
|
||||
var defaultPresetsFileName = Path.Combine(Directory.GetCurrentDirectory(), "LgPresets.json");
|
||||
if (File.Exists(defaultPresetsFileName))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(toCopy, _presetsFilename);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error while copying {toCopy} to {_presetsFilename}: {e.Message}");
|
||||
}
|
||||
var json = File.ReadAllText(defaultPresetsFileName);
|
||||
|
||||
presets = JsonConvert.DeserializeObject<List<LgPreset>>(json);
|
||||
}
|
||||
|
||||
if (File.Exists(_presetsFilename))
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
_presets = _JsonSerializer.Deserialize<List<LgPreset>>(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presets = new List<LgPreset>();
|
||||
}
|
||||
return presets;
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
_configFilename = Path.Combine(_dataPath, "LgConfig.json");
|
||||
if (File.Exists(_configFilename))
|
||||
try
|
||||
{
|
||||
Config = JsonConvert.DeserializeObject<LgServiceConfig>(File.ReadAllText(_configFilename));
|
||||
if (File.Exists(_configFilename))
|
||||
{
|
||||
Config = JsonConvert.DeserializeObject<LgServiceConfig>(File.ReadAllText(_configFilename));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Config = new LgServiceConfig();
|
||||
Logger.Error($"LoadConfig: {ex.Message}");
|
||||
}
|
||||
Config ??= new LgServiceConfig();
|
||||
|
||||
LgPreset.LgDevices = Config.Devices;
|
||||
}
|
||||
@@ -207,6 +210,7 @@ namespace ColorControl
|
||||
}
|
||||
|
||||
list.Add(GeneratePreset("Power", step: "POWER"));
|
||||
list.Add(GeneratePreset("Wol", step: "WOL"));
|
||||
list.Add(GeneratePreset("Home", appId: "com.webos.app.home", key: Keys.H));
|
||||
list.Add(GeneratePreset("Settings", appId: "com.palm.app.settings", key: Keys.S));
|
||||
list.Add(GeneratePreset("TV Guide", appId: "com.webos.app.livemenu"));
|
||||
@@ -266,7 +270,7 @@ namespace ColorControl
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = _JsonSerializer.Serialize(_presets);
|
||||
var json = JsonConvert.SerializeObject(_presets);
|
||||
File.WriteAllText(_presetsFilename, json);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -277,7 +281,7 @@ namespace ColorControl
|
||||
|
||||
private void SaveConfig()
|
||||
{
|
||||
Config.PowerOnAfterStartup = Devices.Any(d => d.PowerOnAfterStartup);
|
||||
Config.PowerOnAfterStartup = Devices?.Any(d => d.PowerOnAfterStartup) ?? false;
|
||||
|
||||
var json = JsonConvert.SerializeObject(Config);
|
||||
File.WriteAllText(_configFilename, json);
|
||||
@@ -301,7 +305,7 @@ namespace ColorControl
|
||||
|
||||
Devices.RemoveAll(d => !d.IsCustom && !autoIpAddresses.Contains(d.IpAddress));
|
||||
|
||||
var newAutoDevices = autoDevices.Where(ad => !Devices.Any(d => d.IpAddress.Equals(ad.IpAddress)));
|
||||
var newAutoDevices = autoDevices.Where(ad => ad.IpAddress != null && !Devices.Any(d => d.IpAddress != null && d.IpAddress.Equals(ad.IpAddress)));
|
||||
Devices.AddRange(newAutoDevices);
|
||||
|
||||
if (Devices.Any())
|
||||
@@ -328,6 +332,11 @@ namespace ColorControl
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var device in Devices)
|
||||
{
|
||||
device.PowerStateChangedEvent += LgDevice_PowerStateChangedEvent;
|
||||
}
|
||||
|
||||
if (connect && SelectedDevice != null)
|
||||
{
|
||||
if (_allowPowerOn)
|
||||
@@ -364,7 +373,7 @@ namespace ColorControl
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = await device.ExecutePreset(preset, reconnect);
|
||||
var result = await device.ExecutePreset(preset, reconnect, Config);
|
||||
|
||||
_lastAppliedPreset = preset;
|
||||
|
||||
@@ -392,7 +401,7 @@ namespace ColorControl
|
||||
return;
|
||||
}
|
||||
|
||||
var apps = await SelectedDevice.GetApps();
|
||||
var apps = await SelectedDevice.GetApps(force);
|
||||
|
||||
if (apps.Any())
|
||||
{
|
||||
@@ -411,22 +420,6 @@ namespace ColorControl
|
||||
return await SelectedDevice?.PowerOff();
|
||||
}
|
||||
|
||||
internal Task PowerOffOnShutdownOrResume(PowerOnOffState state = PowerOnOffState.ShutDown)
|
||||
{
|
||||
var devices = Devices.Where(d => state == PowerOnOffState.ShutDown && d.PowerOffOnShutdown ||
|
||||
state == PowerOnOffState.StandBy && d.PowerOffOnStandby);
|
||||
|
||||
var tasks = new List<Task>();
|
||||
foreach (var device in devices)
|
||||
{
|
||||
var task = device.PowerOff();
|
||||
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
return Task.WhenAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
internal bool WakeSelectedDevice()
|
||||
{
|
||||
return SelectedDevice?.Wake() ?? false;
|
||||
@@ -434,26 +427,11 @@ namespace ColorControl
|
||||
|
||||
internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true)
|
||||
{
|
||||
if (checkUserSession && !UserSessionInfo.UserLocalSession)
|
||||
{
|
||||
Logger.Debug($"WakeAfterStartupOrResume: not waking because session info indicates no local session");
|
||||
return;
|
||||
}
|
||||
|
||||
var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup ||
|
||||
state == PowerOnOffState.Resume && d.PowerOnAfterResume ||
|
||||
var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup ||
|
||||
state == PowerOnOffState.Resume && d.PowerOnAfterResume ||
|
||||
state == PowerOnOffState.ScreenSaver && d.PowerSwitchOnScreenSaver);
|
||||
foreach (var device in wakeDevices)
|
||||
{
|
||||
if (device.PoweredOffBy == LgDevice.PowerOffSource.Manually)
|
||||
{
|
||||
Logger.Debug($"[{device.Name}]: device was manually powered off by user, not powering on");
|
||||
continue;
|
||||
}
|
||||
|
||||
device.DisposeConnection();
|
||||
var _ = device.WakeAndConnectWithRetries(Config.PowerOnRetries);
|
||||
}
|
||||
PowerOnDevices(wakeDevices, state, checkUserSession);
|
||||
}
|
||||
|
||||
public void InstallEventHandlers()
|
||||
@@ -499,27 +477,8 @@ namespace ColorControl
|
||||
return;
|
||||
}
|
||||
|
||||
if (Devices.Any(d => d.PowerOffOnStandby))
|
||||
{
|
||||
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED);
|
||||
try
|
||||
{
|
||||
var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat");
|
||||
if (File.Exists(standByScript))
|
||||
{
|
||||
Utils.StartProcess(standByScript);
|
||||
}
|
||||
|
||||
Logger.Debug("Powering off tv...");
|
||||
var task = PowerOffOnShutdownOrResume(PowerOnOffState.StandBy);
|
||||
Utils.WaitForTask(task);
|
||||
Logger.Debug("Done powering off tv");
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
var devices = Devices.Where(d => d.PowerOffOnStandby);
|
||||
PowerOffDevices(devices, PowerOnOffState.StandBy);
|
||||
}
|
||||
|
||||
private void SessionEnded(object sender, SessionEndedEventArgs e)
|
||||
@@ -535,6 +494,20 @@ namespace ColorControl
|
||||
//}
|
||||
}
|
||||
|
||||
private void LgDevice_PowerStateChangedEvent(object sender, EventArgs e)
|
||||
{
|
||||
if (!(sender is LgDevice device))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//if (device.CurrentState == LgDevice.PowerState.Active)
|
||||
//{
|
||||
// _monitorTask = null;
|
||||
// MonitorProcesses();
|
||||
//}
|
||||
}
|
||||
|
||||
private void MonitorProcesses()
|
||||
{
|
||||
var enableMonitoring = ShouldMonitorProcesses();
|
||||
@@ -560,7 +533,9 @@ namespace ColorControl
|
||||
var validCounter = _monitorTaskCounter;
|
||||
var lastProcessId = 0;
|
||||
|
||||
var monitorContext = new ProcessMonitorContext();
|
||||
await Task.Delay(2000);
|
||||
|
||||
MonitorContext ??= new ProcessMonitorContext();
|
||||
//Process[] lastProcesses = null;
|
||||
|
||||
while (validCounter == _monitorTaskCounter)
|
||||
@@ -569,12 +544,12 @@ namespace ColorControl
|
||||
|
||||
try
|
||||
{
|
||||
var applicableDevices = Devices.Where(d => d.PowerSwitchOnScreenSaver || _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) &&
|
||||
((p.DeviceMacAddress == null && d == SelectedDevice) || p.DeviceMacAddress == d.MacAddress)));
|
||||
var applicableDevices = Devices.Where(d => d.PowerSwitchOnScreenSaver || d.TriggersEnabled && _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None) &&
|
||||
((string.IsNullOrEmpty(p.DeviceMacAddress) && d == SelectedDevice) || p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))));
|
||||
|
||||
if (!applicableDevices.Any())
|
||||
{
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_poweredOffByScreenSaver && !UserSessionInfo.UserLocalSession)
|
||||
@@ -635,78 +610,15 @@ namespace ColorControl
|
||||
wasConnected = true;
|
||||
}
|
||||
|
||||
monitorContext.Devices = connectedDevices;
|
||||
monitorContext.RunningProcesses = processes;
|
||||
//if (lastProcesses != null)
|
||||
//{
|
||||
// monitorContext.StartedProcesses = processes.Where(p => !lastProcesses.Any(lp => lp.Id == p.Id)).ToArray();
|
||||
// monitorContext.StoppedProcesses = lastProcesses.Where(lp => !processes.Any(p => p.Id == lp.Id)).ToArray();
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// monitorContext.StartedProcesses = null;
|
||||
// monitorContext.StoppedProcesses = null;
|
||||
//}
|
||||
|
||||
//if (monitorContext.StartedProcesses?.Any() ?? false)
|
||||
//{
|
||||
// Logger.Debug("Started processes: " + string.Join(", ", monitorContext.StartedProcesses.Select(p => p.ProcessName)));
|
||||
//}
|
||||
//lastProcesses = processes;
|
||||
|
||||
var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr"));
|
||||
|
||||
if (process != null)
|
||||
if (connectedDevices.Any(d => d.PowerSwitchOnScreenSaver))
|
||||
{
|
||||
var parent = process.Parent();
|
||||
|
||||
if (parent?.ProcessName.Contains("winlogon") ?? false)
|
||||
{
|
||||
Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}");
|
||||
try
|
||||
{
|
||||
foreach (var device in connectedDevices)
|
||||
{
|
||||
Logger.Debug($"Screensaver check: test connection with {device.Name}...");
|
||||
var test = await device.TestConnection();
|
||||
Logger.Debug("Screensaver check: test connection result: " + test);
|
||||
if (!test)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver");
|
||||
try
|
||||
{
|
||||
_poweredOffByScreenSaver = await device.PowerOff();
|
||||
}
|
||||
catch (Exception pex)
|
||||
{
|
||||
// Assume powering off was successful
|
||||
_poweredOffByScreenSaver = true;
|
||||
Logger.Error($"Screensaver check: exception while powering off (probably connection closed): " + pex.Message);
|
||||
}
|
||||
Logger.Debug($"Screensaver check: powered off tv {device.Name}");
|
||||
}
|
||||
|
||||
_poweredOffByScreenSaverProcessId = process.Id;
|
||||
lastProcessId = 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Screensaver check: can't power off: " + e.ToLogString());
|
||||
}
|
||||
}
|
||||
else if (process.Id != lastProcessId)
|
||||
{
|
||||
Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}");
|
||||
lastProcessId = process.Id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await ExecuteProcessPresets(monitorContext);
|
||||
lastProcessId = await HandleScreenSaverProcessAsync(lastProcessId, processes, connectedDevices);
|
||||
}
|
||||
|
||||
MonitorContext.Devices = connectedDevices.Where(d => d.TriggersEnabled).ToList();
|
||||
MonitorContext.RunningProcesses = processes;
|
||||
|
||||
await ExecuteProcessPresets(MonitorContext);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -715,9 +627,76 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> HandleScreenSaverProcessAsync(int lastProcessId, Process[] processes, List<LgDevice> connectedDevices)
|
||||
{
|
||||
var process = processes.FirstOrDefault(p => p.ProcessName.ToLowerInvariant().EndsWith(".scr"));
|
||||
|
||||
if (process == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var parent = process.Parent(processes);
|
||||
|
||||
if (parent?.ProcessName.Contains("winlogon") ?? false)
|
||||
{
|
||||
Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}");
|
||||
try
|
||||
{
|
||||
foreach (var device in connectedDevices)
|
||||
{
|
||||
Logger.Debug($"Screensaver check: test connection with {device.Name}...");
|
||||
var test = await device.TestConnection();
|
||||
Logger.Debug("Screensaver check: test connection result: " + test);
|
||||
if (!test)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.Debug($"Screensaver check: powering off tv {device.Name} because of screensaver");
|
||||
try
|
||||
{
|
||||
_poweredOffByScreenSaver = await device.PowerOff().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
// Assume powering off was successful
|
||||
_poweredOffByScreenSaver = true;
|
||||
Logger.Error($"Screensaver check: timeout exception while powering off (probably connection closed)");
|
||||
}
|
||||
catch (Exception pex)
|
||||
{
|
||||
// Assume powering off was successful
|
||||
_poweredOffByScreenSaver = true;
|
||||
Logger.Error($"Screensaver check: exception while powering off (probably connection closed): " + pex.Message);
|
||||
}
|
||||
Logger.Debug($"Screensaver check: powered off tv {device.Name}");
|
||||
}
|
||||
|
||||
_poweredOffByScreenSaverProcessId = process.Id;
|
||||
lastProcessId = 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Screensaver check: can't power off: " + e.ToLogString());
|
||||
}
|
||||
}
|
||||
else if (process.Id != lastProcessId)
|
||||
{
|
||||
Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}");
|
||||
lastProcessId = process.Id;
|
||||
}
|
||||
|
||||
return lastProcessId;
|
||||
}
|
||||
|
||||
private async Task ExecuteProcessPresets(ProcessMonitorContext context)
|
||||
{
|
||||
var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch));
|
||||
var selectedDevice = SelectedDevice;
|
||||
|
||||
var presets = _presets.Where(p => p.Triggers.Any(t => t.Trigger == PresetTriggerType.ProcessSwitch) &&
|
||||
((string.IsNullOrEmpty(p.DeviceMacAddress) && selectedDevice?.TriggersEnabled == true)
|
||||
|| Devices.Any(d => d.TriggersEnabled && p.DeviceMacAddress.Equals(d.MacAddress, StringComparison.OrdinalIgnoreCase))));
|
||||
if (!presets.Any())
|
||||
{
|
||||
return;
|
||||
@@ -727,9 +706,21 @@ namespace ColorControl
|
||||
|
||||
var (processId, isFullScreen) = Utils.GetForegroundProcessIdAndIfFullScreen();
|
||||
|
||||
if (processId == Environment.ProcessId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (processId > 0)
|
||||
{
|
||||
context.ForegroundProcess = context.RunningProcesses.FirstOrDefault(p => p.Id == processId);
|
||||
var process = context.RunningProcesses.FirstOrDefault(p => p.Id == processId);
|
||||
|
||||
if (process?.ProcessName?.Contains(".scr") ?? false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.ForegroundProcess = process;
|
||||
context.ForegroundProcessIsFullScreen = isFullScreen;
|
||||
}
|
||||
else
|
||||
@@ -795,9 +786,11 @@ namespace ColorControl
|
||||
toApplyPreset = toApplyPresets.First();
|
||||
}
|
||||
|
||||
if (_lastAppliedPreset != toApplyPreset)
|
||||
if (_lastTriggeredPreset != toApplyPreset)
|
||||
{
|
||||
await ApplyPreset(toApplyPreset);
|
||||
|
||||
_lastTriggeredPreset = toApplyPreset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -813,7 +806,7 @@ namespace ColorControl
|
||||
|
||||
if (!string.IsNullOrEmpty(device.MacAddress))
|
||||
{
|
||||
var presets = _presets.Where(p => !string.IsNullOrEmpty(p.DeviceMacAddress) && p.DeviceMacAddress.Equals(device.MacAddress)).ToList();
|
||||
var presets = _presets.Where(p => !string.IsNullOrEmpty(p.DeviceMacAddress) && p.DeviceMacAddress.Equals(device.MacAddress, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
presets.ForEach(preset =>
|
||||
{
|
||||
preset.DeviceMacAddress = null;
|
||||
@@ -825,5 +818,123 @@ namespace ColorControl
|
||||
SelectedDevice = Devices.Any() ? Devices.First() : null;
|
||||
}
|
||||
}
|
||||
|
||||
public void PowerSettingChanged(WindowsPowerSetting setting)
|
||||
{
|
||||
var devices = Devices.Where(d => d.PowerByWindows).ToList();
|
||||
|
||||
if (setting == WindowsPowerSetting.Off)
|
||||
{
|
||||
PowerOffDevices(devices, PowerOnOffState.StandBy);
|
||||
}
|
||||
else if (setting == WindowsPowerSetting.On)
|
||||
{
|
||||
PowerOnDevices(devices);
|
||||
}
|
||||
}
|
||||
|
||||
public void PowerOffDevices(IEnumerable<LgDevice> devices, PowerOnOffState state = PowerOnOffState.ShutDown)
|
||||
{
|
||||
if (!devices.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NativeMethods.GetAsyncKeyState(NativeConstants.VK_CONTROL) < 0 || NativeMethods.GetAsyncKeyState(NativeConstants.VK_RCONTROL) < 0)
|
||||
{
|
||||
Logger.Debug("Not powering off because CONTROL-key is down");
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED);
|
||||
try
|
||||
{
|
||||
if (state == PowerOnOffState.ShutDown)
|
||||
{
|
||||
var sleep = Config.ShutdownDelay;
|
||||
|
||||
Logger.Debug($"PowerOffDevices: Waiting for {sleep} milliseconds...");
|
||||
|
||||
while (sleep > 0 && _restartDetector?.PowerOffDetected == false)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
|
||||
if (_restartDetector != null && (_restartDetector.RestartDetected || _restartDetector.IsRebootInProgress()))
|
||||
{
|
||||
Logger.Debug("Not powering off because of a restart");
|
||||
return;
|
||||
}
|
||||
|
||||
sleep -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Debug("Powering off tv(s)...");
|
||||
var tasks = new List<Task>();
|
||||
foreach (var device in devices)
|
||||
{
|
||||
var task = device.PowerOff(true);
|
||||
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
if (state == PowerOnOffState.StandBy)
|
||||
{
|
||||
var standByScript = Path.Combine(Program.DataDir, "StandByScript.bat");
|
||||
if (File.Exists(standByScript))
|
||||
{
|
||||
Utils.StartProcess(standByScript, hidden: true, wait: true);
|
||||
}
|
||||
}
|
||||
|
||||
var waitTask = Task.WhenAll(tasks.ToArray());
|
||||
Utils.WaitForTask(waitTask);
|
||||
Logger.Debug("Done powering off tv(s)");
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
|
||||
public void PowerOnDevices(IEnumerable<LgDevice> devices, PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true)
|
||||
{
|
||||
if (!devices.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkUserSession && !UserSessionInfo.UserLocalSession)
|
||||
{
|
||||
Logger.Debug($"WakeAfterStartupOrResume: not waking because session info indicates no local session");
|
||||
return;
|
||||
}
|
||||
|
||||
var resumeScript = Path.Combine(Program.DataDir, "ResumeScript.bat");
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
if (!device.PowerOnAfterManualPowerOff && device.PoweredOffBy == LgDevice.PowerOffSource.Manually)
|
||||
{
|
||||
Logger.Debug($"[{device.Name}]: device was manually powered off by user, not powering on");
|
||||
continue;
|
||||
}
|
||||
|
||||
device.DisposeConnection();
|
||||
|
||||
var task = device.WakeAndConnectWithRetries(Config.PowerOnRetries);
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
if (File.Exists(resumeScript))
|
||||
{
|
||||
var waitTask = Task.WhenAll(tasks.ToArray());
|
||||
Utils.WaitForTask(waitTask);
|
||||
|
||||
Utils.StartProcess(resumeScript, hidden: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,27 +5,27 @@ namespace ColorControl
|
||||
class LgServiceConfig
|
||||
{
|
||||
public bool PowerOnAfterStartup { get; set; }
|
||||
|
||||
public int PowerOnDelayAfterResume { get; set; }
|
||||
|
||||
public int PowerOnRetries { get; set; }
|
||||
|
||||
public string PreferredMacAddress { get; set; }
|
||||
|
||||
public string DeviceSearchKey { get; set; }
|
||||
|
||||
public bool UseOldNpcapWol { get; set; }
|
||||
|
||||
public List<LgDevice> Devices { get; set; }
|
||||
|
||||
public bool ShowRemoteControl { get; set; }
|
||||
public bool ShowAdvancedActions { get; set; }
|
||||
public string GameBarShortcut { get; set; }
|
||||
public int GameBarTop { get; set; }
|
||||
public int GameBarLeft { get; set; }
|
||||
public int ShutdownDelay { get; set; }
|
||||
|
||||
public LgServiceConfig()
|
||||
{
|
||||
PowerOnDelayAfterResume = 5000;
|
||||
ShutdownDelay = 1000;
|
||||
DeviceSearchKey = "[LG]";
|
||||
PowerOnRetries = 10;
|
||||
Devices = new List<LgDevice>();
|
||||
GameBarTop = 100;
|
||||
GameBarLeft = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is an implementation of the 'IComparer' interface.
|
||||
/// </summary>
|
||||
public class ListViewColumnSorter : IComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the column to be sorted
|
||||
/// </summary>
|
||||
private int ColumnToSort;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the order in which to sort (i.e. 'Ascending').
|
||||
/// </summary>
|
||||
private SortOrder OrderOfSort;
|
||||
|
||||
/// <summary>
|
||||
/// Case insensitive comparer object
|
||||
/// </summary>
|
||||
private CaseInsensitiveComparer ObjectCompare;
|
||||
|
||||
/// <summary>
|
||||
/// Class constructor. Initializes various elements
|
||||
/// </summary>
|
||||
public ListViewColumnSorter()
|
||||
{
|
||||
// Initialize the column to '0'
|
||||
ColumnToSort = 0;
|
||||
|
||||
// Initialize the sort order to 'none'
|
||||
OrderOfSort = SortOrder.None;
|
||||
|
||||
// Initialize the CaseInsensitiveComparer object
|
||||
ObjectCompare = new CaseInsensitiveComparer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison.
|
||||
/// </summary>
|
||||
/// <param name="x">First object to be compared</param>
|
||||
/// <param name="y">Second object to be compared</param>
|
||||
/// <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
|
||||
public int Compare(object x, object y)
|
||||
{
|
||||
int compareResult;
|
||||
ListViewItem listviewX, listviewY;
|
||||
|
||||
// Cast the objects to be compared to ListViewItem objects
|
||||
listviewX = (ListViewItem)x;
|
||||
listviewY = (ListViewItem)y;
|
||||
|
||||
if (listviewX.Tag == null)
|
||||
{
|
||||
compareResult = OrderOfSort == SortOrder.Ascending ? -1 : 1;
|
||||
}
|
||||
else if (listviewY.Tag == null)
|
||||
{
|
||||
compareResult = OrderOfSort == SortOrder.Ascending ? 1 : -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compare the two items
|
||||
compareResult = ObjectCompare.Compare(listviewX.SubItems[ColumnToSort].Text, listviewY.SubItems[ColumnToSort].Text);
|
||||
}
|
||||
|
||||
// Calculate correct return value based on object comparison
|
||||
if (OrderOfSort == SortOrder.Ascending)
|
||||
{
|
||||
// Ascending sort is selected, return normal result of compare operation
|
||||
return compareResult;
|
||||
}
|
||||
else if (OrderOfSort == SortOrder.Descending)
|
||||
{
|
||||
// Descending sort is selected, return negative result of compare operation
|
||||
return (-compareResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return '0' to indicate they are equal
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
|
||||
/// </summary>
|
||||
public int SortColumn
|
||||
{
|
||||
set
|
||||
{
|
||||
ColumnToSort = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return ColumnToSort;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
|
||||
/// </summary>
|
||||
public SortOrder Order
|
||||
{
|
||||
set
|
||||
{
|
||||
OrderOfSort = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return OrderOfSort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+590
-276
File diff suppressed because it is too large.
Load diff
+669
-180
File diff suppressed because it is too large.
Load diff
@@ -1,64 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace ColorControl
|
||||
public IEnumerable<string> Values { get; set; }
|
||||
public decimal MinValue { get; set; }
|
||||
public decimal MaxValue { get; set; }
|
||||
public int NumberOfValues { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
@@ -38,9 +39,16 @@ namespace ColorControl
|
||||
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
public static void InfoOk(string text)
|
||||
public static void InfoOk(string text, string title = null, string url = null)
|
||||
{
|
||||
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
if (url != null)
|
||||
{
|
||||
MessageBox.Show(text, title ?? MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1, 0, url);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show(text, title ?? MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
|
||||
public static DialogResult QuestionYesNo(string text)
|
||||
@@ -90,8 +98,8 @@ namespace ColorControl
|
||||
|
||||
if (label.Contains("Ip-address"))
|
||||
{
|
||||
textBox.Mask = "###.###.###.###";
|
||||
textBox.ValidatingType = typeof(System.Net.IPAddress);
|
||||
//textBox.Mask = "990.990.990.990";
|
||||
//textBox.ValidatingType = typeof(System.Net.IPAddress);
|
||||
textBox.Culture = CultureInfo.InvariantCulture;
|
||||
}
|
||||
else if (label.Contains("MAC-address"))
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NonInvasiveKeyboardHookLibrary
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct to represent a keybind (key + modifiers)
|
||||
/// When the keybind struct is compared to another keybind struct, the equality is based on the
|
||||
/// modifiers and the virtual key code.
|
||||
/// </summary>
|
||||
internal class KeybindStruct : IEquatable<KeybindStruct>
|
||||
{
|
||||
public readonly int VirtualKeyCode;
|
||||
public readonly List<ModifierKeys> Modifiers;
|
||||
public readonly Guid? Identifier;
|
||||
|
||||
public KeybindStruct(IEnumerable<ModifierKeys> modifiers, int virtualKeyCode, Guid? identifier = null)
|
||||
{
|
||||
this.VirtualKeyCode = virtualKeyCode;
|
||||
this.Modifiers = new List<ModifierKeys>(modifiers);
|
||||
this.Identifier = identifier;
|
||||
}
|
||||
|
||||
public bool Equals(KeybindStruct other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (this.VirtualKeyCode != other.VirtualKeyCode)
|
||||
return false;
|
||||
|
||||
if (this.Modifiers.Count != other.Modifiers.Count)
|
||||
return false;
|
||||
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
if (!other.Modifiers.Contains(modifier))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
|
||||
return Equals((KeybindStruct) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 13;
|
||||
hash = (hash * 7) + VirtualKeyCode.GetHashCode();
|
||||
|
||||
// Sum the modifiers separately, so that their order does not affect the final hash
|
||||
var modifiersHashSum = 0;
|
||||
foreach (var modifier in this.Modifiers)
|
||||
{
|
||||
modifiersHashSum += modifier.GetHashCode();
|
||||
}
|
||||
|
||||
hash = (hash * 7) + modifiersHashSum;
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace NonInvasiveKeyboardHookLibrary
|
||||
{
|
||||
internal struct KeyboardParams
|
||||
{
|
||||
public IntPtr wParam;
|
||||
public int vkCode;
|
||||
|
||||
public KeyboardParams(IntPtr wParam, int vkCode)
|
||||
{
|
||||
this.wParam = wParam;
|
||||
this.vkCode = vkCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A hotkey manager that uses a low-level global keyboard hook, but eventually only fires events for
|
||||
/// pre-registered hotkeys, i.e. not invading a user's privacy.
|
||||
/// </summary>
|
||||
public class KeyboardHookManager
|
||||
{
|
||||
#region Private Attributes
|
||||
/// <summary>
|
||||
/// Keeps track of all registered hotkeys
|
||||
/// </summary>
|
||||
private readonly Dictionary<KeybindStruct, Action> _registeredCallbacks;
|
||||
/// <summary>
|
||||
/// Keeps track of modifier keys that are held down
|
||||
/// </summary>
|
||||
private readonly HashSet<ModifierKeys> _downModifierKeys;
|
||||
/// <summary>
|
||||
/// Keeps track of all keys that are held down to prevent firing callbacks
|
||||
/// more than once for a single keypress
|
||||
/// </summary>
|
||||
private readonly HashSet<int> _downKeys;
|
||||
private readonly object _modifiersLock = new object();
|
||||
private LowLevelKeyboardProc _hook;
|
||||
private bool _isStarted;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Instantiates an empty keyboard hook manager.
|
||||
/// It is best practice to keep a single instance per process.
|
||||
/// Start() must be called to start the low-level keyboard hook manager
|
||||
/// </summary>
|
||||
public KeyboardHookManager()
|
||||
{
|
||||
this._registeredCallbacks = new Dictionary<KeybindStruct, Action>();
|
||||
this._downModifierKeys = new HashSet<ModifierKeys>();
|
||||
this._downKeys = new HashSet<int>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
/// <summary>
|
||||
/// Starts the low-level keyboard hook.
|
||||
/// Hotkeys can be registered regardless of the low-level keyboard hook's state, but their callbacks
|
||||
/// will only ever be invoked if the low-level keyboard hook is running and intercepting keys.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (this._isStarted) return;
|
||||
|
||||
this._hook = this.HookCallback;
|
||||
_hookId = SetHook(this._hook);
|
||||
this._isStarted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the low-level keyboard hook (without unregistering the existing hotkeys)
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (this._isStarted)
|
||||
{
|
||||
UnhookWindowsHookEx(_hookId);
|
||||
this._isStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a hotkey.
|
||||
/// </summary>
|
||||
/// <param name="virtualKeyCode">The virtual key code of the hotkey</param>
|
||||
/// <param name="action">The callback action to invoke when this hotkey is pressed</param>
|
||||
/// <exception cref="HotkeyAlreadyRegisteredException">Thrown when the given key is already mapped to a callback</exception>
|
||||
/// /// <returns>Unique identifier of this hotkey, which can be used to remove it later</returns>
|
||||
public Guid RegisterHotkey(int virtualKeyCode, Action action)
|
||||
{
|
||||
return this.RegisterHotkey(new ModifierKeys[0], virtualKeyCode, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new key combination with one or more modifiers
|
||||
/// </summary>
|
||||
/// <param name="modifiers">Modifiers that must be held while hitting the key. Multiple modifiers can be provided using the flags bitwise OR operation</param>
|
||||
/// <param name="virtualKeyCode">The virtual key code of the standard key</param>
|
||||
/// <param name="action">The callback action to invoke when this combination is pressed</param>
|
||||
/// <exception cref="HotkeyAlreadyRegisteredException">Thrown when the given key combination is already mapped to a callback</exception>
|
||||
/// <returns>Unique identifier of this hotkey, which can be used to remove it later</returns>
|
||||
public Guid RegisterHotkey(ModifierKeys modifiers, int virtualKeyCode, Action action)
|
||||
{
|
||||
var allModifiers = Enum.GetValues(typeof(ModifierKeys)).Cast<ModifierKeys>().ToArray();
|
||||
|
||||
// Get the modifiers that were chained with bitwise OR operation as an array of modifiers
|
||||
var selectedModifiers = allModifiers.Where(modifier => modifiers.HasFlag(modifier)).ToArray();
|
||||
|
||||
return RegisterHotkey(selectedModifiers, virtualKeyCode, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new key combination.
|
||||
/// </summary>
|
||||
/// <param name="modifiers">Modifiers that must be held while hitting the key</param>
|
||||
/// <param name="virtualKeyCode">The virtual key code of the standard key</param>
|
||||
/// <param name="action">The callback action to invoke when this combination is pressed</param>
|
||||
/// <exception cref="HotkeyAlreadyRegisteredException">Thrown when the given key combination is already mapped to a callback</exception>
|
||||
/// <returns>Unique identifier of this hotkey, which can be used to remove it later</returns>
|
||||
public Guid RegisterHotkey(ModifierKeys[] modifiers, int virtualKeyCode, Action action)
|
||||
{
|
||||
var keybindIdentity = Guid.NewGuid();
|
||||
var keybind = new KeybindStruct(modifiers, virtualKeyCode, keybindIdentity);
|
||||
if (this._registeredCallbacks.ContainsKey(keybind))
|
||||
{
|
||||
throw new HotkeyAlreadyRegisteredException();
|
||||
}
|
||||
|
||||
this._registeredCallbacks[keybind] = action;
|
||||
return keybindIdentity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters all hotkeys (the low-level keyboard hook continues running)
|
||||
/// </summary>
|
||||
public void UnregisterAll()
|
||||
{
|
||||
this._registeredCallbacks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a specific single-key hotkey
|
||||
/// </summary>
|
||||
/// <param name="virtualKeyCode">Virtual key code of the unregistered key</param>
|
||||
/// <exception cref="HotkeyNotRegisteredException">Thrown when the given key combination is not registered</exception>
|
||||
public void UnregisterHotkey(int virtualKeyCode)
|
||||
{
|
||||
this.UnregisterHotkey(new ModifierKeys[0], virtualKeyCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a specific key combination
|
||||
/// </summary>
|
||||
/// <param name="modifiers">The modifiers of the combination</param>
|
||||
/// <param name="virtualKeyCode">The key of the combination</param>
|
||||
/// <exception cref="HotkeyNotRegisteredException">Thrown when the given key combination is not registered</exception>
|
||||
public void UnregisterHotkey(ModifierKeys[] modifiers, int virtualKeyCode)
|
||||
{
|
||||
var keybind = new KeybindStruct(modifiers, virtualKeyCode);
|
||||
|
||||
if (!this._registeredCallbacks.Remove(keybind))
|
||||
{
|
||||
throw new HotkeyNotRegisteredException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a specific key by its unique identifier
|
||||
/// </summary>
|
||||
/// <param name="keybindIdentity">Keybind GUID, as returned by RegisterHotkey</param>
|
||||
public void UnregisterHotkey(Guid keybindIdentity)
|
||||
{
|
||||
var keybindToRemove = this._registeredCallbacks.Keys.FirstOrDefault(keybind =>
|
||||
keybind.Identifier.HasValue && keybind.Identifier.Value.Equals(keybindIdentity));
|
||||
|
||||
if (keybindToRemove == null || !this._registeredCallbacks.Remove(keybindToRemove))
|
||||
{
|
||||
throw new HotkeyNotRegisteredException();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private methods
|
||||
private void HandleKeyPress(int virtualKeyCode)
|
||||
{
|
||||
var currentKey = new KeybindStruct(this._downModifierKeys, virtualKeyCode);
|
||||
|
||||
if (!this._registeredCallbacks.ContainsKey(currentKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._registeredCallbacks.TryGetValue(currentKey, out var callback))
|
||||
{
|
||||
callback.Invoke();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Low level keyboard hook
|
||||
// Source: https://blogs.msdn.microsoft.com/toub/2006/05/03/low-level-keyboard-hook-in-c/
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
private const int WM_KEYDOWN = 0x0100;
|
||||
private const int WM_KEYUP = 0x0101;
|
||||
private const int WM_SYSKEYDOWN = 0x0104;
|
||||
private const int WM_SYSKEYUP = 0x0105;
|
||||
|
||||
private static IntPtr _hookId = IntPtr.Zero;
|
||||
|
||||
private static IntPtr SetHook(LowLevelKeyboardProc proc)
|
||||
{
|
||||
var userLibrary = LoadLibrary("User32");
|
||||
|
||||
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
|
||||
userLibrary, 0);
|
||||
}
|
||||
|
||||
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (nCode >= 0)
|
||||
{
|
||||
var vkCode = Marshal.ReadInt32(lParam);
|
||||
|
||||
// Debug.WriteLine("Starting");
|
||||
// To prevent slowing keyboard input down, we use handle keyboard inputs in a separate thread
|
||||
ThreadPool.QueueUserWorkItem(this.HandleSingleKeyboardInput, new KeyboardParams(wParam, vkCode));
|
||||
// Debug.WriteLine("Ending");
|
||||
}
|
||||
|
||||
return CallNextHookEx(_hookId, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a keyboard event based on the KeyboardParams it receives
|
||||
/// </summary>
|
||||
/// <param name="keyboardParamsObj">KeyboardParams struct (object type to comply with QueueUserWorkItem)</param>
|
||||
private void HandleSingleKeyboardInput(object keyboardParamsObj)
|
||||
{
|
||||
var keyboardParams = (KeyboardParams)keyboardParamsObj;
|
||||
var wParam = keyboardParams.wParam;
|
||||
var vkCode = keyboardParams.vkCode;
|
||||
|
||||
var modifierKey = ModifierKeysUtilities.GetModifierKeyFromCode(vkCode);
|
||||
|
||||
// If the keyboard event is a KeyDown event (i.e. key pressed)
|
||||
if (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
|
||||
{
|
||||
// In this case, we only care about modifier keys
|
||||
if (modifierKey != null)
|
||||
{
|
||||
lock (this._modifiersLock)
|
||||
{
|
||||
this._downModifierKeys.Add(modifierKey.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger callbacks that are registered for this key, but only once per key press
|
||||
if (!this._downKeys.Contains(vkCode))
|
||||
{
|
||||
this.HandleKeyPress(vkCode);
|
||||
this._downKeys.Add(vkCode);
|
||||
}
|
||||
}
|
||||
|
||||
// If the keyboard event is a KeyUp event (i.e. key released)
|
||||
if (wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP)
|
||||
{
|
||||
// If the released key is a modifier key, remove it from the HashSet of modifier keys
|
||||
if (modifierKey != null)
|
||||
{
|
||||
lock (this._modifiersLock)
|
||||
{
|
||||
this._downModifierKeys.Remove(modifierKey.Value);
|
||||
}
|
||||
}
|
||||
|
||||
this._downKeys.Remove(vkCode);
|
||||
}
|
||||
}
|
||||
|
||||
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr SetWindowsHookEx(int idHook,
|
||||
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
|
||||
IntPtr wParam, IntPtr lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the library.
|
||||
/// </summary>
|
||||
/// <param name="lpFileName">Name of the library</param>
|
||||
/// <returns>A handle to the library</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr LoadLibrary(string lpFileName);
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Exceptions
|
||||
public class NonInvasiveKeyboardHookException : Exception
|
||||
{
|
||||
}
|
||||
|
||||
public class HotkeyAlreadyRegisteredException : NonInvasiveKeyboardHookException
|
||||
{
|
||||
}
|
||||
|
||||
public class HotkeyNotRegisteredException : NonInvasiveKeyboardHookException
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace NonInvasiveKeyboardHookLibrary
|
||||
{
|
||||
[Flags]
|
||||
public enum ModifierKeys
|
||||
{
|
||||
Alt = 1,
|
||||
Control = 2,
|
||||
Shift = 4,
|
||||
WindowsKey = 8,
|
||||
}
|
||||
|
||||
public static class ModifierKeysUtilities
|
||||
{
|
||||
public static ModifierKeys? GetModifierKeyFromCode(int keyCode)
|
||||
{
|
||||
switch (keyCode)
|
||||
{
|
||||
case 0xA0:
|
||||
case 0xA1:
|
||||
case 0x10:
|
||||
return ModifierKeys.Shift;
|
||||
|
||||
case 0xA2:
|
||||
case 0xA3:
|
||||
case 0x11:
|
||||
return ModifierKeys.Control;
|
||||
|
||||
case 0x12:
|
||||
case 0xA4:
|
||||
case 0xA5:
|
||||
return ModifierKeys.Alt;
|
||||
|
||||
case 0x5B:
|
||||
case 0x5C:
|
||||
return ModifierKeys.WindowsKey;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+191
-128
@@ -1,7 +1,7 @@
|
||||
using NvAPIWrapper;
|
||||
using Newtonsoft.Json;
|
||||
using NvAPIWrapper;
|
||||
using NvAPIWrapper.Display;
|
||||
using NvAPIWrapper.Native.Display;
|
||||
using NvAPIWrapper.Native.Display.Structures;
|
||||
using NvAPIWrapper.Native.GPU.Structures;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -13,6 +13,16 @@ using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
enum NvDitherState
|
||||
{
|
||||
[Description("Auto")]
|
||||
Auto = 0,
|
||||
[Description("Enabled")]
|
||||
Enabled = 1,
|
||||
[Description("Disabled")]
|
||||
Disabled = 2
|
||||
}
|
||||
|
||||
enum NvDitherBits
|
||||
{
|
||||
[Description("6-bit")]
|
||||
@@ -45,7 +55,7 @@ namespace ColorControl
|
||||
PreserveSig = true)]
|
||||
private static extern IntPtr NvAPI64_QueryInterface(uint interfaceId);
|
||||
|
||||
public delegate long NvAPI_Disp_SetDitherControl(
|
||||
public delegate int NvAPI_Disp_SetDitherControl(
|
||||
[In] PhysicalGPUHandle physicalGpu,
|
||||
[In] uint OutputId,
|
||||
[In] uint state,
|
||||
@@ -53,26 +63,26 @@ namespace ColorControl
|
||||
[In] uint mode
|
||||
);
|
||||
|
||||
public delegate long NvAPI_Disp_GetDitherControl(
|
||||
[In] PhysicalGPUHandle physicalGpu,
|
||||
[In] uint OutputId,
|
||||
[In] IntPtr ditherControl);
|
||||
public delegate int NvAPI_Disp_GetDitherControl(
|
||||
[In] uint DisplayId,
|
||||
[MarshalAs(UnmanagedType.Struct)] ref NV_GPU_DITHER_CONTROL_V1 ditherControl);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct NV_GPU_DITHER_CONTROL_V1
|
||||
{
|
||||
public int version;
|
||||
public uint state;
|
||||
public uint bits;
|
||||
public uint mode;
|
||||
public uint version;
|
||||
public int state;
|
||||
public int bits;
|
||||
public int mode;
|
||||
public uint bitsCaps;
|
||||
public uint modeCaps;
|
||||
};
|
||||
|
||||
private Display _currentDisplay;
|
||||
|
||||
public NvService(string dataPath) : base(dataPath)
|
||||
public NvService(string dataPath) : base(dataPath, "NvPresets.json")
|
||||
{
|
||||
var converter = new ColorDataConverter();
|
||||
_JsonDeserializer.RegisterConverters(new[] { converter });
|
||||
AddJsonConverter(new ColorDataConverter());
|
||||
|
||||
LoadPresets();
|
||||
}
|
||||
@@ -101,6 +111,12 @@ namespace ColorControl
|
||||
|
||||
public Display GetCurrentDisplay()
|
||||
{
|
||||
if (!HasDisplaysAttached())
|
||||
{
|
||||
_currentDisplay = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_currentDisplay == null)
|
||||
{
|
||||
_currentDisplay = Display.GetDisplays()[0];
|
||||
@@ -109,15 +125,28 @@ namespace ColorControl
|
||||
return _currentDisplay;
|
||||
}
|
||||
|
||||
public override bool HasDisplaysAttached()
|
||||
public override bool HasDisplaysAttached(bool reinitialize = false)
|
||||
{
|
||||
if (reinitialize)
|
||||
{
|
||||
try
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Could not reinitialize NVIDIA API: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return DisplayDevice.GetGDIPrimaryDisplayDevice() != null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
return reinitialize ? false : HasDisplaysAttached(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,27 +155,16 @@ namespace ColorControl
|
||||
SavePresets();
|
||||
}
|
||||
|
||||
private void LoadPresets()
|
||||
protected override List<NvPreset> GetDefaultPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, "NvPresets.json");
|
||||
|
||||
if (File.Exists(_presetsFilename))
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
_presets = _JsonDeserializer.Deserialize<List<NvPreset>>(json);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presets = NvPreset.GetDefaultPresets();
|
||||
}
|
||||
return NvPreset.GetDefaultPresets();
|
||||
}
|
||||
|
||||
private void SavePresets()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = _JsonSerializer.Serialize(_presets);
|
||||
var json = JsonConvert.SerializeObject(_presets);
|
||||
File.WriteAllText(_presetsFilename, json);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -193,6 +211,13 @@ namespace ColorControl
|
||||
|
||||
SetCurrentDisplay(preset);
|
||||
|
||||
var display = GetCurrentDisplay();
|
||||
|
||||
if (display == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hdrEnabled = IsHDREnabled();
|
||||
|
||||
var newHdrEnabled = preset.applyHDR && (preset.HDREnabled || (preset.toggleHDR && !hdrEnabled));
|
||||
@@ -200,12 +225,20 @@ namespace ColorControl
|
||||
|
||||
if (preset.applyColorData && (ColorDataDiffers(preset.colorData) || (!newHdrEnabled && preset.applyColorData && preset.colorData.Colorimetry != ColorDataColorimetry.Auto)))
|
||||
{
|
||||
var display = GetCurrentDisplay();
|
||||
if (hdrEnabled)
|
||||
{
|
||||
SetHDRState(display, false);
|
||||
//if (hdrEnabled)
|
||||
//{
|
||||
// SetHDRState(display, false);
|
||||
|
||||
applyHdr = false;
|
||||
// applyHdr = false;
|
||||
//}
|
||||
|
||||
if (preset.applyRefreshRate)
|
||||
{
|
||||
var timing = display.DisplayDevice.CurrentTiming;
|
||||
if (preset.refreshRate < timing.Extra.RefreshRate)
|
||||
{
|
||||
SetRefreshRate(preset.refreshRate, true);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
@@ -218,18 +251,16 @@ namespace ColorControl
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (hdrEnabled && newHdrEnabled)
|
||||
{
|
||||
SetHDRState(display, true, useSwitch: appContext.StartUpParams.NoGui);
|
||||
//if (hdrEnabled && newHdrEnabled)
|
||||
//{
|
||||
// SetHDRState(display, true, useSwitch: appContext.StartUpParams.NoGui);
|
||||
|
||||
applyHdr = false;
|
||||
}
|
||||
// applyHdr = false;
|
||||
//}
|
||||
}
|
||||
|
||||
if (applyHdr)
|
||||
{
|
||||
var display = GetCurrentDisplay();
|
||||
|
||||
var colorData = preset.applyColorData ? preset.colorData : display.DisplayDevice.CurrentColorData;
|
||||
|
||||
SetHDRState(display, newHdrEnabled, colorData, appContext.StartUpParams.NoGui);
|
||||
@@ -237,7 +268,9 @@ namespace ColorControl
|
||||
|
||||
if (preset.applyRefreshRate)
|
||||
{
|
||||
if (!SetRefreshRate(preset.refreshRate))
|
||||
var timing = display.DisplayDevice.CurrentTiming;
|
||||
|
||||
if (preset.refreshRate != timing.Extra.RefreshRate && !SetRefreshRate(preset.refreshRate, true))
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
@@ -245,7 +278,7 @@ namespace ColorControl
|
||||
|
||||
if (preset.applyDithering)
|
||||
{
|
||||
if (!SetDithering(preset.ditheringEnabled, preset: preset))
|
||||
if (!SetDithering(preset.ditheringEnabled ? NvDitherState.Enabled : NvDitherState.Disabled, preset: preset))
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
@@ -316,7 +349,7 @@ namespace ColorControl
|
||||
//}
|
||||
}
|
||||
|
||||
public bool SetDithering(bool enabled, uint bits = 1, uint mode = 4, NvPreset preset = null)
|
||||
public bool SetDithering(NvDitherState state, uint bits = 1, uint mode = 4, NvPreset preset = null)
|
||||
{
|
||||
var result = true;
|
||||
|
||||
@@ -325,18 +358,30 @@ namespace ColorControl
|
||||
{
|
||||
var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_SetDitherControl)) as NvAPI_Disp_SetDitherControl;
|
||||
|
||||
var displayDevice = GetCurrentDisplay().DisplayDevice;
|
||||
var display = GetCurrentDisplay();
|
||||
if (display == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var displayDevice = display.DisplayDevice;
|
||||
|
||||
var gpuHandle = displayDevice.PhysicalGPU.Handle;
|
||||
var displayId = displayDevice.DisplayId;
|
||||
|
||||
if (preset != null)
|
||||
|
||||
if (state == NvDitherState.Auto)
|
||||
{
|
||||
// These seem to be ignored in Auto-state
|
||||
bits = 0;
|
||||
mode = 0;
|
||||
}
|
||||
else if (preset != null)
|
||||
{
|
||||
bits = preset.ditheringBits;
|
||||
mode = preset.ditheringMode;
|
||||
}
|
||||
|
||||
var resultValue = delegateValue(gpuHandle, displayId, (uint)(enabled ? 1 : 2), bits, mode);
|
||||
var resultValue = delegateValue(gpuHandle, displayId, (uint)state, bits, mode);
|
||||
if (resultValue != 0)
|
||||
{
|
||||
Logger.Error($"Could not set dithering because NvAPI_Disp_SetDitherControl returned a non-zero return code: {resultValue}");
|
||||
@@ -352,45 +397,48 @@ namespace ColorControl
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool GetDithering()
|
||||
public NV_GPU_DITHER_CONTROL_V1 GetDithering()
|
||||
{
|
||||
// Requires elevation, too bad :(
|
||||
//Utils.GetRegistryKeyValue(@"SYSTEM\CurrentControlSet\Services\nvlddmkm\State\DisplayDatabase", "DitherRegistryKey");
|
||||
var dither = new NV_GPU_DITHER_CONTROL_V1 { version = 0x10018 };
|
||||
|
||||
/*
|
||||
var ptr = NvAPI64_QueryInterface(0x932AC8FB);
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_GetDitherControl)) as NvAPI_Disp_GetDitherControl;
|
||||
|
||||
var displayDevice = GetCurrentDisplay().DisplayDevice;
|
||||
var display = GetCurrentDisplay();
|
||||
if (display == null)
|
||||
{
|
||||
dither.state = -1;
|
||||
return dither;
|
||||
}
|
||||
|
||||
var gpuHandle = displayDevice.PhysicalGPU.Handle;
|
||||
var displayDevice = display.DisplayDevice;
|
||||
var displayId = displayDevice.DisplayId;
|
||||
|
||||
NV_GPU_DITHER_CONTROL_V1 info = new NV_GPU_DITHER_CONTROL_V1();
|
||||
info.version = 1;
|
||||
var size = Marshal.SizeOf(info.GetType());
|
||||
IntPtr bla = Marshal.AllocHGlobal(size);
|
||||
Marshal.StructureToPtr(info, bla, false);
|
||||
|
||||
// Does not work yet...What is the exact interface of NvAPI_Disp_GetDitherControl?
|
||||
|
||||
var result = delegateValue(gpuHandle, displayId, bla);
|
||||
var result = delegateValue(displayId, ref dither);
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error($"Could not get dithering because NvAPI_Disp_GetDitherControl returned a non-zero return code: {result}");
|
||||
dither.state = -1;
|
||||
}
|
||||
|
||||
return info.state == 1;
|
||||
}
|
||||
*/
|
||||
return false;
|
||||
else
|
||||
{
|
||||
dither.state = -2;
|
||||
}
|
||||
|
||||
return dither;
|
||||
}
|
||||
|
||||
public bool SetRefreshRate(uint refreshRate)
|
||||
public bool SetRefreshRate(uint refreshRate, bool updateRegistry = false)
|
||||
{
|
||||
var display = GetCurrentDisplay();
|
||||
if (display == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var timing = display.DisplayDevice.CurrentTiming;
|
||||
|
||||
if (timing.Extra.RefreshRate == refreshRate)
|
||||
@@ -400,7 +448,7 @@ namespace ColorControl
|
||||
|
||||
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
|
||||
|
||||
return SetRefreshRateInternal(display.Name, refreshRate, portrait, timing.HorizontalVisible, timing.VerticalVisible);
|
||||
return SetRefreshRateInternal(display.Name, refreshRate, portrait, timing.HorizontalVisible, timing.VerticalVisible, updateRegistry);
|
||||
}
|
||||
|
||||
public List<uint> GetAvailableRefreshRates(NvPreset preset = null)
|
||||
@@ -411,6 +459,11 @@ namespace ColorControl
|
||||
}
|
||||
|
||||
var display = GetCurrentDisplay();
|
||||
if (display == null)
|
||||
{
|
||||
return new List<uint>();
|
||||
}
|
||||
|
||||
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
|
||||
var timing = display.DisplayDevice.CurrentTiming;
|
||||
|
||||
@@ -419,7 +472,13 @@ namespace ColorControl
|
||||
|
||||
public bool IsHDREnabled()
|
||||
{
|
||||
var displayDevice = GetCurrentDisplay().DisplayDevice;
|
||||
var display = GetCurrentDisplay();
|
||||
if (display == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var displayDevice = display.DisplayDevice;
|
||||
var hdr = displayDevice.HDRColorData;
|
||||
return hdr?.HDRMode == ColorDataHDRMode.UHDA;
|
||||
}
|
||||
@@ -431,71 +490,75 @@ namespace ColorControl
|
||||
|
||||
public List<NvDisplayInfo> GetDisplayInfos()
|
||||
{
|
||||
Display[] displays;
|
||||
try
|
||||
{
|
||||
displays = GetDisplays();
|
||||
var displays = GetDisplays();
|
||||
var list = new List<NvDisplayInfo>();
|
||||
|
||||
foreach (var display in displays)
|
||||
{
|
||||
var values = new List<string>();
|
||||
|
||||
values.Add("Current settings");
|
||||
|
||||
var name = display.Name;
|
||||
|
||||
var screen = Screen.AllScreens.FirstOrDefault(x => x.DeviceName.Equals(name));
|
||||
if (screen != null)
|
||||
{
|
||||
name += " (" + screen.DeviceFriendlyName() + ")";
|
||||
}
|
||||
|
||||
values.Add(name);
|
||||
|
||||
var colorData = GetCurrentColorData(display);
|
||||
|
||||
var colorSettings = string.Format("{0}, {1}, {2}, {3}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange, colorData.Colorimetry);
|
||||
|
||||
values.Add(colorSettings);
|
||||
|
||||
var refreshRate = display.DisplayDevice.CurrentTiming.Extra.RefreshRate;
|
||||
|
||||
values.Add($"{refreshRate}Hz");
|
||||
|
||||
var ditherInfo = GetDithering();
|
||||
|
||||
string dithering;
|
||||
if (ditherInfo.state == -1)
|
||||
{
|
||||
dithering = "Error";
|
||||
}
|
||||
else
|
||||
{
|
||||
var state = (NvDitherState)ditherInfo.state;
|
||||
dithering = state switch { NvDitherState.Disabled => "Disabled", NvDitherState.Auto => "Auto: ", _ => string.Empty };
|
||||
if (state is NvDitherState.Enabled or NvDitherState.Auto)
|
||||
{
|
||||
var ditherBitsDescription = ((NvDitherBits)ditherInfo.bits).GetDescription();
|
||||
var ditherModeDescription = ((NvDitherMode)ditherInfo.mode).GetDescription();
|
||||
dithering = string.Format("{0}{1} {2}", dithering, ditherBitsDescription, ditherModeDescription);
|
||||
}
|
||||
}
|
||||
|
||||
values.Add(dithering);
|
||||
|
||||
var hdrEnabled = IsHDREnabled();
|
||||
values.Add(hdrEnabled ? "Yes" : "No");
|
||||
|
||||
var infoLine = string.Format("{0}: {1}, {2}Hz, HDR: {3}", name, colorSettings, refreshRate, hdrEnabled ? "Yes" : "No");
|
||||
|
||||
var displayInfo = new NvDisplayInfo(display, values, infoLine);
|
||||
|
||||
list.Add(displayInfo);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("Error while getting displays: " + e.ToLogString());
|
||||
return null;
|
||||
}
|
||||
|
||||
var list = new List<NvDisplayInfo>();
|
||||
|
||||
foreach (var display in displays)
|
||||
{
|
||||
var values = new List<string>();
|
||||
|
||||
values.Add("Current settings");
|
||||
|
||||
var name = display.Name;
|
||||
|
||||
var screen = Screen.AllScreens.FirstOrDefault(x => x.DeviceName.Equals(name));
|
||||
if (screen != null)
|
||||
{
|
||||
name += " (" + screen.DeviceFriendlyName() + ")";
|
||||
}
|
||||
|
||||
values.Add(name);
|
||||
|
||||
var colorData = GetCurrentColorData(display);
|
||||
|
||||
var colorSettings = string.Format("{0}, {1}, {2}, {3}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange, colorData.Colorimetry);
|
||||
|
||||
values.Add(colorSettings);
|
||||
|
||||
var refreshRate = display.DisplayDevice.CurrentTiming.Extra.RefreshRate;
|
||||
|
||||
values.Add($"{refreshRate}Hz");
|
||||
|
||||
var lastPreset = GetLastAppliedPreset();
|
||||
if (lastPreset != null)
|
||||
{
|
||||
values.Add(lastPreset.GetDitheringDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
values.Add(string.Empty);
|
||||
}
|
||||
|
||||
var hdrEnabled = IsHDREnabled();
|
||||
values.Add(hdrEnabled ? "Yes" : "No");
|
||||
|
||||
var infoLine = string.Format("{0}: {1}, {2}Hz, HDR: {3}", name, colorSettings, refreshRate, hdrEnabled ? "Yes" : "No");
|
||||
|
||||
var displayInfo = new NvDisplayInfo(display, values, infoLine);
|
||||
|
||||
list.Add(displayInfo);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public NvPreset GetLastAppliedPreset()
|
||||
{
|
||||
return _lastAppliedPreset;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
|
||||
@@ -19,10 +19,14 @@ namespace ColorControl
|
||||
Shutdown = 3,
|
||||
[Description("Standby")]
|
||||
Standby = 4,
|
||||
[Description("Screensaver")]
|
||||
Screensaver = 5,
|
||||
[Description("Reserved5")]
|
||||
Reserved5 = 5,
|
||||
[Description("Process switch")]
|
||||
ProcessSwitch = 6,
|
||||
[Description("Screensaver start")]
|
||||
ScreensaverStart = 7,
|
||||
[Description("Screensaver stop")]
|
||||
ScreensaverStop = 8,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
@@ -110,6 +114,7 @@ namespace ColorControl
|
||||
public string name { get; set; }
|
||||
public string shortcut { get; set; }
|
||||
public List<PresetTrigger> Triggers { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
private int _id;
|
||||
|
||||
|
||||
+36
-10
@@ -1,4 +1,5 @@
|
||||
using NLog;
|
||||
using Newtonsoft.Json;
|
||||
using NLog;
|
||||
using NWin32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@@ -10,11 +11,14 @@ namespace ColorControl
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
public const string TS_TASKNAME = "ColorControl";
|
||||
public static string DataDir { get; private set; }
|
||||
public static string ConfigFilename { get; private set; }
|
||||
public static Config Config { get; private set; }
|
||||
public static AppContext AppContext { get; private set; }
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
@@ -32,6 +36,11 @@ namespace ColorControl
|
||||
|
||||
LoadConfig();
|
||||
|
||||
if (Config.UseGdiScaling)
|
||||
{
|
||||
Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
|
||||
}
|
||||
|
||||
var startUpParams = StartUpParams.Parse(args);
|
||||
|
||||
AppContext = new AppContext(Config, startUpParams);
|
||||
@@ -109,15 +118,19 @@ namespace ColorControl
|
||||
{
|
||||
ConfigFilename = Path.Combine(DataDir, "Settings.json");
|
||||
|
||||
if (File.Exists(ConfigFilename))
|
||||
try
|
||||
{
|
||||
var data = File.ReadAllText(ConfigFilename);
|
||||
Config = Utils.JsonSerializer.Deserialize<Config>(data);
|
||||
if (File.Exists(ConfigFilename))
|
||||
{
|
||||
var data = File.ReadAllText(ConfigFilename);
|
||||
Config = JsonConvert.DeserializeObject<Config>(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
Config = new Config();
|
||||
Logger.Error($"LoadConfig: {ex.Message}");
|
||||
}
|
||||
Config ??= new Config();
|
||||
}
|
||||
|
||||
private static bool HandleStartupParams(StartUpParams startUpParams, Process existingProcess)
|
||||
@@ -127,6 +140,11 @@ namespace ColorControl
|
||||
Utils.InstallChromeFix(startUpParams.ActivateChromeFontFix);
|
||||
return true;
|
||||
}
|
||||
if (startUpParams.EnableAutoStart || startUpParams.DisableAutoStart)
|
||||
{
|
||||
Utils.RegisterTask(TS_TASKNAME, startUpParams.EnableAutoStart);
|
||||
return true;
|
||||
}
|
||||
|
||||
var useConsole = startUpParams.NoGui || existingProcess != null;
|
||||
|
||||
@@ -215,14 +233,22 @@ namespace ColorControl
|
||||
|
||||
private static void GlobalUnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = (Exception)e.ExceptionObject;
|
||||
MessageBox.Show("Unhandled exception: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
GlobalHandleException((Exception)e.ExceptionObject, "Unhandled exception");
|
||||
}
|
||||
|
||||
private static void GlobalThreadExceptionHandler(object sender, System.Threading.ThreadExceptionEventArgs e)
|
||||
{
|
||||
var ex = e.Exception;
|
||||
MessageBox.Show("Exception in thread: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
GlobalHandleException(e.Exception, "Exception in thread");
|
||||
}
|
||||
|
||||
private static void GlobalHandleException(Exception exception, string type)
|
||||
{
|
||||
var trace = exception.ToLogString(Environment.StackTrace);
|
||||
var message = $"{type}: {trace}";
|
||||
|
||||
Logger.Error(message);
|
||||
|
||||
MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("ColorControl")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Maassoft")]
|
||||
[assembly: AssemblyProduct("ColorControl")]
|
||||
[assembly: AssemblyCopyright("Copyright © Maassoft 2021")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("a6005d08-70f0-44cb-a384-9219eba84f21")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("4.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("4.0.0.0")]
|
||||
+21
-4
@@ -19,7 +19,7 @@ namespace ColorControl.Properties {
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
@@ -71,7 +71,7 @@ namespace ColorControl.Properties {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Power;120;132
|
||||
/// Looks up a localized string similar to Power;120;136
|
||||
///1;106;228
|
||||
///2;196;228
|
||||
///3;286;228
|
||||
@@ -81,15 +81,32 @@ namespace ColorControl.Properties {
|
||||
///7;106;362
|
||||
///8;196;362
|
||||
///9;286;362
|
||||
///List;106;425
|
||||
///0;196;425
|
||||
///SAP;286;425
|
||||
///Vol +;98;513
|
||||
///Mute;198;513
|
||||
///Channel +;298;513
|
||||
///Vol -;98;622
|
||||
///Speech;198;633
|
||||
///Channel -;298;622
|
||||
///Home;78;695
|
||||
///Settings;318;695.
|
||||
///Settings;318;695
|
||||
///Up;200;748
|
||||
///Left;98;852
|
||||
///Enter;200;852
|
||||
///Right;304;852
|
||||
///Down;200;952
|
||||
///Back;80;1000
|
||||
///TV Guide;320;1000
|
||||
///Netflix;92;1076
|
||||
///Inputs;200;1066
|
||||
///Amazon Prime;312;1076
|
||||
///Red;90;1150
|
||||
///Green;164;1150
|
||||
///Yellow;240;1150
|
||||
///Blue;316;1150
|
||||
///Rakuten TV;116;1218
|
||||
///Play;202 [rest of string was truncated]";.
|
||||
/// </summary>
|
||||
internal static string RC_coords {
|
||||
get {
|
||||
|
||||
+9
-13
@@ -8,21 +8,17 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace ColorControl.Properties
|
||||
{
|
||||
|
||||
|
||||
namespace ColorControl.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.3.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
@@ -9,6 +10,7 @@ namespace ColorControl
|
||||
partial class RemoteControlPanel : UserControl
|
||||
{
|
||||
private static int Range = 35;
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private LgService _lgService;
|
||||
private List<LgPreset> _buttons;
|
||||
@@ -65,6 +67,17 @@ namespace ColorControl
|
||||
|
||||
if (preset != null)
|
||||
{
|
||||
if (preset.steps.Contains("POWER") && _lgService.SelectedDevice?.CurrentState != LgDevice.PowerState.Active)
|
||||
{
|
||||
Logger.Debug("Executing WOL instead of POWER");
|
||||
|
||||
var wolPreset = _buttons.FirstOrDefault(p => p.name.Equals("Wol"));
|
||||
if (wolPreset != null)
|
||||
{
|
||||
preset = wolPreset;
|
||||
}
|
||||
}
|
||||
|
||||
var _ = _lgService.ApplyPreset(preset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"type":"register",
|
||||
"id":"register_0",
|
||||
"payload":{
|
||||
"forcePairing":false,
|
||||
"pairingType":"PROMPT",
|
||||
"client-key":"CLIENTKEYGOESHERE",
|
||||
"manifest":{
|
||||
"manifestVersion":1,
|
||||
"appVersion":"1.1",
|
||||
"signed":{
|
||||
"created":"20140509",
|
||||
"appId":"com.lge.test",
|
||||
"vendorId":"com.lge",
|
||||
"localizedAppNames":{
|
||||
"":"LG Remote App",
|
||||
"ko-KR":"리모컨 앱",
|
||||
"zxx-XX":"ЛГ Rэмotэ AПП"
|
||||
},
|
||||
"localizedVendorNames":{
|
||||
"":"LG Electronics"
|
||||
},
|
||||
"permissions":[
|
||||
"TEST_SECURE",
|
||||
"CONTROL_INPUT_TEXT",
|
||||
"CONTROL_MOUSE_AND_KEYBOARD",
|
||||
"READ_INSTALLED_APPS",
|
||||
"READ_LGE_SDX",
|
||||
"READ_NOTIFICATIONS",
|
||||
"SEARCH",
|
||||
"WRITE_SETTINGS",
|
||||
"WRITE_NOTIFICATION_ALERT",
|
||||
"CONTROL_POWER",
|
||||
"READ_CURRENT_CHANNEL",
|
||||
"READ_RUNNING_APPS",
|
||||
"READ_UPDATE_INFO",
|
||||
"UPDATE_FROM_REMOTE_APP",
|
||||
"READ_LGE_TV_INPUT_EVENTS",
|
||||
"READ_TV_CURRENT_TIME"
|
||||
],
|
||||
"serial":"2f930e2d2cfe083771f68e4fe7bb07"
|
||||
},
|
||||
"permissions":[
|
||||
"LAUNCH",
|
||||
"LAUNCH_WEBAPP",
|
||||
"APP_TO_APP",
|
||||
"CLOSE",
|
||||
"TEST_OPEN",
|
||||
"TEST_PROTECTED",
|
||||
"CONTROL_AUDIO",
|
||||
"CONTROL_DISPLAY",
|
||||
"CONTROL_INPUT_JOYSTICK",
|
||||
"CONTROL_INPUT_MEDIA_RECORDING",
|
||||
"CONTROL_INPUT_MEDIA_PLAYBACK",
|
||||
"CONTROL_INPUT_TV",
|
||||
"CONTROL_POWER",
|
||||
"CONTROL_TV_SCREEN",
|
||||
"READ_APP_STATUS",
|
||||
"READ_CURRENT_CHANNEL",
|
||||
"READ_INPUT_DEVICE_LIST",
|
||||
"READ_NETWORK_STATE",
|
||||
"READ_RUNNING_APPS",
|
||||
"READ_TV_CHANNEL_LIST",
|
||||
"WRITE_NOTIFICATION_TOAST",
|
||||
"READ_POWER_STATE",
|
||||
"READ_COUNTRY_INFO",
|
||||
"READ_SETTINGS"
|
||||
],
|
||||
"signatures":[
|
||||
{
|
||||
"signatureVersion":1,
|
||||
"signature":"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw=="
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Win32;
|
||||
using NLog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Eventing.Reader;
|
||||
using System.Linq;
|
||||
|
||||
@@ -9,16 +10,19 @@ namespace ColorControl
|
||||
public class RestartDetector : IDisposable
|
||||
{
|
||||
public bool RestartDetected { get; private set; }
|
||||
|
||||
public bool PowerOffDetected { get; private set; }
|
||||
|
||||
private EventLogWatcher watcher = null;
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private static readonly IEnumerable<string> RestartNames = new[] { "restart", "herstart", "reinicio" };
|
||||
private static readonly IEnumerable<string> PowerOffNames = new[] { "power off", "uitschakelen", "apagar" };
|
||||
|
||||
public RestartDetector()
|
||||
{
|
||||
try
|
||||
{
|
||||
EventLogQuery subscriptionQuery = new EventLogQuery(
|
||||
"System", PathType.LogName, "*[System[Provider[@Name='USER32'] and (EventID=1074)]]");
|
||||
EventLogQuery subscriptionQuery = new EventLogQuery("System", PathType.LogName, "*[System[Provider[@Name='USER32'] and (EventID=1074)]]");
|
||||
|
||||
watcher = new EventLogWatcher(subscriptionQuery);
|
||||
|
||||
@@ -47,6 +51,7 @@ namespace ColorControl
|
||||
public void EventLogEventRead(object obj, EventRecordWrittenEventArgs arg)
|
||||
{
|
||||
RestartDetected = false;
|
||||
PowerOffDetected = false;
|
||||
try
|
||||
{
|
||||
Logger.Debug("Logging event: " + arg);
|
||||
@@ -55,31 +60,20 @@ namespace ColorControl
|
||||
{
|
||||
foreach (EventProperty x in ((EventLogRecord)arg.EventRecord).Properties)
|
||||
{
|
||||
Logger.Debug("Event value: " + x.Value);
|
||||
if (x.Value.Equals("restart"))
|
||||
var strValue = x.Value.ToString();
|
||||
Logger.Debug("Event value: " + strValue);
|
||||
if (RestartNames.Any(n => n.Equals(strValue, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
RestartDetected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (PowerOffNames.Any(n => n.Equals(strValue, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
PowerOffDetected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (arg.EventRecord != null)
|
||||
//{
|
||||
// String[] xPathRefs = new String[1];
|
||||
// xPathRefs[0] = "Event/EventData/Data";
|
||||
// IEnumerable<String> xPathEnum = xPathRefs;
|
||||
|
||||
// EventLogPropertySelector logPropertyContext = new EventLogPropertySelector(xPathEnum);
|
||||
// IList<object> logEventProps = ((EventLogRecord)arg.EventRecord).GetPropertyValues(logPropertyContext);
|
||||
|
||||
// string[] eventData = (string[])logEventProps[0];
|
||||
|
||||
// foreach (string attribute in eventData)
|
||||
// {
|
||||
// if (attribute.Contains("restart")) { restart = true; break; }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
namespace ColorControl
|
||||
{
|
||||
abstract class ServiceBase<T> where T : PresetBase
|
||||
{
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
public event EventHandler<T> AfterApplyPreset;
|
||||
|
||||
protected string _dataPath;
|
||||
protected string _presetsBaseFilename;
|
||||
protected string _presetsFilename;
|
||||
protected JavaScriptSerializer _JsonSerializer;
|
||||
protected JavaScriptSerializer _JsonDeserializer;
|
||||
protected string _presetsBackupFilename;
|
||||
protected bool _initialized = false;
|
||||
protected List<T> _presets;
|
||||
protected T _lastAppliedPreset;
|
||||
protected string _loadPresetsError;
|
||||
private List<JsonConverter> _jsonConverters;
|
||||
|
||||
public ServiceBase(string dataPath)
|
||||
public ServiceBase(string dataPath, string presetsBaseFilename)
|
||||
{
|
||||
_dataPath = dataPath;
|
||||
|
||||
_JsonSerializer = new JavaScriptSerializer();
|
||||
_JsonDeserializer = new JavaScriptSerializer();
|
||||
_presetsBaseFilename = presetsBaseFilename;
|
||||
_jsonConverters = new List<JsonConverter>();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
@@ -48,6 +52,11 @@ namespace ColorControl
|
||||
return preset;
|
||||
}
|
||||
|
||||
public T GetLastAppliedPreset()
|
||||
{
|
||||
return _lastAppliedPreset;
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
|
||||
@@ -62,5 +71,70 @@ namespace ColorControl
|
||||
{
|
||||
AfterApplyPreset?.Invoke(this, _lastAppliedPreset);
|
||||
}
|
||||
|
||||
protected void LoadPresets()
|
||||
{
|
||||
_presetsFilename = Path.Combine(_dataPath, _presetsBaseFilename);
|
||||
_presetsBackupFilename = Path.Combine(_dataPath, $"{_presetsBaseFilename}.backup");
|
||||
|
||||
try
|
||||
{
|
||||
var presetsExists = File.Exists(_presetsFilename);
|
||||
if (presetsExists)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(_presetsFilename);
|
||||
|
||||
// Hack to convert incorrect triggers
|
||||
json = json.Replace(@"""Triggers"":0", @"""Triggers"":[]");
|
||||
|
||||
_presets = JsonConvert.DeserializeObject<List<T>>(json, _jsonConverters.ToArray());
|
||||
|
||||
if (_presets != null)
|
||||
{
|
||||
if (File.Exists(_presetsBackupFilename))
|
||||
{
|
||||
File.Delete(_presetsBackupFilename);
|
||||
}
|
||||
File.Copy(_presetsFilename, _presetsBackupFilename);
|
||||
}
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
Logger.Error($"Error while loading presets, reverting to default presets: {ex1.Message}");
|
||||
_loadPresetsError = ex1.Message;
|
||||
}
|
||||
}
|
||||
if (!presetsExists || _presets == null)
|
||||
{
|
||||
Logger.Debug("Reverting to default presets");
|
||||
try
|
||||
{
|
||||
_presets = GetDefaultPresets();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error loading default presets, reverting to empty list: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"General error while loading presets, reverting to empty list: {ex.Message}");
|
||||
_loadPresetsError = ex.Message;
|
||||
}
|
||||
if (_presets == null)
|
||||
{
|
||||
_presets = new List<T>();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract List<T> GetDefaultPresets();
|
||||
|
||||
protected void AddJsonConverter(JsonConverter jsonConverter)
|
||||
{
|
||||
_jsonConverters.Add(jsonConverter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace ColorControl
|
||||
public const string ExecuteAmdPresetParam = "--amdpreset";
|
||||
public const string ExecuteLgPresetParam = "--lgpreset";
|
||||
public const string NoGuiParam = "--nogui";
|
||||
public const string EnableAutoStartParam = "--enable-auto-start";
|
||||
public const string DisableAutoStartParam = "--disable-auto-start";
|
||||
|
||||
public bool RunningFromScheduledTask { get; private set; }
|
||||
public bool ActivateChromeFontFix { get; private set; }
|
||||
@@ -24,6 +26,8 @@ namespace ColorControl
|
||||
public bool ExecuteLgPreset { get; private set; }
|
||||
public string LgPresetName { get; private set; }
|
||||
public bool NoGui { get; set; }
|
||||
public bool EnableAutoStart { get; private set; }
|
||||
public bool DisableAutoStart { get; private set; }
|
||||
|
||||
public static StartUpParams Parse(IEnumerable<string> args)
|
||||
{
|
||||
@@ -102,6 +106,16 @@ namespace ColorControl
|
||||
settings.NoGui = true;
|
||||
break;
|
||||
}
|
||||
case EnableAutoStartParam:
|
||||
{
|
||||
settings.EnableAutoStart = true;
|
||||
break;
|
||||
}
|
||||
case DisableAutoStartParam:
|
||||
{
|
||||
settings.DisableAutoStart = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Script.Serialization;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ColorControl
|
||||
@@ -19,7 +14,6 @@ namespace ColorControl
|
||||
|
||||
public static TreeNode CreateTree(object obj, string text)
|
||||
{
|
||||
JavaScriptSerializer jss = new JavaScriptSerializer();
|
||||
var serialized = JsonConvert.SerializeObject(obj, Formatting.None,
|
||||
new JsonSerializerSettings {
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
@@ -30,7 +24,7 @@ namespace ColorControl
|
||||
},
|
||||
Converters = new [] { new Newtonsoft.Json.Converters.StringEnumConverter() }
|
||||
});
|
||||
Dictionary<string, object> dic = jss.Deserialize<Dictionary<string, object>>(serialized);
|
||||
var dic = JsonConvert.DeserializeObject<Dictionary<string, object>>(serialized);
|
||||
var root = new TreeNode();
|
||||
root.Text = text;
|
||||
BuildTree(dic, root);
|
||||
@@ -48,9 +42,9 @@ namespace ColorControl
|
||||
node.Nodes.Add(keyValueNode);
|
||||
BuildTree(kv.Value, keyValueNode);
|
||||
}
|
||||
else if (item is ArrayList)
|
||||
else if (item.GetType() == typeof(JArray))
|
||||
{
|
||||
ArrayList list = (ArrayList)item;
|
||||
var list = (JArray)item;
|
||||
int index = 0;
|
||||
foreach (object value in list)
|
||||
{
|
||||
@@ -70,6 +64,14 @@ namespace ColorControl
|
||||
BuildTree(d, node);
|
||||
}
|
||||
}
|
||||
else if (item.GetType() == typeof(JObject))
|
||||
{
|
||||
var jobject = item as JObject;
|
||||
foreach (var property in jobject.Properties())
|
||||
{
|
||||
BuildTree(new KeyValuePair<string, object>(property.Name, property.Value), node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetValueAsString(object value)
|
||||
@@ -82,13 +84,13 @@ namespace ColorControl
|
||||
return "[]";
|
||||
}
|
||||
|
||||
if (value is ArrayList)
|
||||
if (value.GetType() == typeof(JArray))
|
||||
{
|
||||
var arr = value as ArrayList;
|
||||
var arr = value as JArray;
|
||||
return $"[{arr.Count}]";
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
if (type.IsGenericType || value.GetType() == typeof(JObject))
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
|
||||
+265
-64
@@ -1,24 +1,24 @@
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32.TaskScheduler;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NStandard;
|
||||
using NWin32;
|
||||
using NWin32.NativeTypes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Deployment.Application;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Script.Serialization;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Windows.Devices.Enumeration;
|
||||
using Windows.Devices.Enumeration.Pnp;
|
||||
@@ -53,6 +53,14 @@ namespace ColorControl
|
||||
UserNotificationState.QUNS_PRESENTATION_MODE,
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct POWERBROADCAST_SETTING
|
||||
{
|
||||
public Guid PowerSetting;
|
||||
public uint DataLength;
|
||||
public byte Data;
|
||||
}
|
||||
|
||||
public const int WM_BRINGTOFRONT = NativeConstants.WM_USER + 1;
|
||||
|
||||
public const int ENUM_CURRENT_SETTINGS = -1;
|
||||
@@ -60,14 +68,14 @@ namespace ColorControl
|
||||
|
||||
public static string PKEY_PNPX_IpAddress = "{656a3bb3-ecc0-43fd-8477-4ae0404a96cd} 12297";
|
||||
public static string PKEY_PNPX_PhysicalAddress = "{656a3bb3-ecc0-43fd-8477-4ae0404a96cd} 12294";
|
||||
public static Guid GUID_CONSOLE_DISPLAY_STATE = Guid.Parse("6FE69556-704A-47A0-8F24-C28D936FDA47");
|
||||
public static int PBT_POWERSETTINGCHANGE = 32787;
|
||||
|
||||
public delegate void PCREATE_PROCESS_NOTIFY_ROUTINE(IntPtr ParentId, IntPtr ProcessId, bool Create);
|
||||
|
||||
public static JavaScriptSerializer JsonSerializer = new JavaScriptSerializer();
|
||||
|
||||
public static bool ConsoleOpened { get; private set; }
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
@@ -98,6 +106,12 @@ namespace ColorControl
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
|
||||
|
||||
[DllImport(@"User32", SetLastError = true)]
|
||||
public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
|
||||
|
||||
[DllImport("User32.dll", SetLastError = true)]
|
||||
public static extern bool UnregisterPowerSettingNotification(IntPtr hWnd);
|
||||
|
||||
//[DllImport("Wtsapi32.dll")]
|
||||
//public extern static bool WTSRegisterSessionNotification(IntPtr hWnd, uint dwFlags);
|
||||
|
||||
@@ -107,6 +121,7 @@ namespace ColorControl
|
||||
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private static bool WinKeyDown = false;
|
||||
private static UserNotificationState LastNotificationState;
|
||||
private static Keys[] KeysWithoutModifiers = new[] { Keys.F13, Keys.F14, Keys.F15, Keys.F16, Keys.F17, Keys.F18, Keys.F19, Keys.F20, Keys.F21, Keys.F22, Keys.F23, Keys.F24 };
|
||||
|
||||
public static Bitmap SubPixelShift(Bitmap bitmap)
|
||||
{
|
||||
@@ -203,9 +218,10 @@ namespace ColorControl
|
||||
|
||||
internal static bool ExecuteElevated(string args)
|
||||
{
|
||||
var info = new ProcessStartInfo(Assembly.GetEntryAssembly().Location, args)
|
||||
var info = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName, args)
|
||||
{
|
||||
Verb = "runas", // indicates to elevate privileges
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
var process = new Process
|
||||
@@ -425,26 +441,65 @@ namespace ColorControl
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static string FirstCharUpperCase(string name)
|
||||
{
|
||||
return name.Substring(0, 1).ToUpper() + name.Substring(1);
|
||||
}
|
||||
|
||||
public static string GetDescriptionByEnumName<T>(string value) where T : IConvertible
|
||||
{
|
||||
string description = null;
|
||||
|
||||
if (Enum.TryParse(typeof(T), value, out var result))
|
||||
{
|
||||
description = (result as IConvertible)?.GetDescription();
|
||||
}
|
||||
|
||||
description = description ?? FirstCharUpperCase(value);
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public static string GetDescription<T>(this T e) where T : IConvertible
|
||||
{
|
||||
if (e is Enum)
|
||||
return GetDescription(e.GetType(), e);
|
||||
}
|
||||
|
||||
public static string GetDescription(Type enumType, IConvertible value)
|
||||
{
|
||||
if (enumType.BaseType == typeof(Enum))
|
||||
{
|
||||
var type = e.GetType();
|
||||
var values = Enum.GetValues(type);
|
||||
var val = value.ToInt32(CultureInfo.InvariantCulture);
|
||||
|
||||
foreach (int val in values)
|
||||
var enumName = enumType.GetEnumName(val);
|
||||
if (enumName == null)
|
||||
{
|
||||
if (val == e.ToInt32(CultureInfo.InvariantCulture))
|
||||
{
|
||||
var memInfo = type.GetMember(type.GetEnumName(val));
|
||||
var descriptionAttribute = memInfo[0]
|
||||
.GetCustomAttributes(typeof(DescriptionAttribute), false)
|
||||
.FirstOrDefault() as DescriptionAttribute;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (descriptionAttribute != null)
|
||||
{
|
||||
return descriptionAttribute.Description;
|
||||
}
|
||||
var memInfo = enumType.GetMember(enumName);
|
||||
var descriptionAttribute = memInfo[0]
|
||||
.GetCustomAttributes(typeof(DescriptionAttribute), false)
|
||||
.FirstOrDefault() as DescriptionAttribute;
|
||||
|
||||
if (descriptionAttribute != null)
|
||||
{
|
||||
return descriptionAttribute.Description;
|
||||
}
|
||||
}
|
||||
|
||||
return null; // could also return string.Empty
|
||||
}
|
||||
|
||||
public static string GetEnumNameByDescription(Type enumType, string description)
|
||||
{
|
||||
if (enumType.BaseType == typeof(Enum))
|
||||
{
|
||||
foreach (var enumValue in Enum.GetValues(enumType))
|
||||
{
|
||||
if (description.Equals(GetDescription(enumType, enumValue as IConvertible), StringComparison.Ordinal))
|
||||
{
|
||||
return Enum.GetName(enumType, enumValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,22 +508,27 @@ namespace ColorControl
|
||||
}
|
||||
|
||||
public static List<string> GetDescriptions<T>(int value = -1, int fromValue = 0) where T : IConvertible
|
||||
{
|
||||
return GetDescriptions(typeof(T), value, fromValue);
|
||||
}
|
||||
|
||||
public static List<string> GetDescriptions(Type enumType, int value = -1, int fromValue = 0)
|
||||
{
|
||||
var list = new List<string>();
|
||||
foreach (var enumValue in Enum.GetValues(typeof(T)))
|
||||
foreach (var enumValue in Enum.GetValues(enumType))
|
||||
{
|
||||
if ((int)enumValue < fromValue || value >= 0 && ((int)enumValue & value) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
list.Add(((T)enumValue).GetDescription());
|
||||
list.Add(GetDescription(enumType, enumValue as IConvertible));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void RegisterTask(string taskName, bool enabled)
|
||||
{
|
||||
var file = Assembly.GetExecutingAssembly().Location;
|
||||
var file = Process.GetCurrentProcess().MainModule.FileName;
|
||||
var directory = Path.GetDirectoryName(file);
|
||||
|
||||
try
|
||||
@@ -507,26 +567,23 @@ namespace ColorControl
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
if (update && ApplicationDeployment.IsNetworkDeployed)
|
||||
{
|
||||
var action = task.Definition.Actions.FirstOrDefault(x => x.ActionType == TaskActionType.Execute);
|
||||
if (action != null)
|
||||
{
|
||||
var execAction = action as ExecAction;
|
||||
if (!execAction.Path.Equals(file))
|
||||
{
|
||||
RegisterTask(taskName, false);
|
||||
RegisterTask(taskName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
//var action = task.Definition.Actions.FirstOrDefault(x => x.ActionType == TaskActionType.Execute);
|
||||
//if (action != null)
|
||||
//{
|
||||
// var execAction = action as ExecAction;
|
||||
// if (!execAction.Path.Equals(file))
|
||||
// {
|
||||
// RegisterTask(taskName, false);
|
||||
// RegisterTask(taskName, true);
|
||||
// }
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterShortcut(IntPtr handle, int id, string shortcut, bool clear = false)
|
||||
public static bool RegisterShortcut(IntPtr handle, int id, string shortcut, bool clear = false)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
@@ -536,27 +593,70 @@ namespace ColorControl
|
||||
if (!string.IsNullOrEmpty(shortcut))
|
||||
{
|
||||
var (mods, key) = ParseShortcut(shortcut);
|
||||
RegisterHotKey(handle, id, mods, key);
|
||||
var result = RegisterHotKey(handle, id, mods, key);
|
||||
if (!result)
|
||||
{
|
||||
var errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
|
||||
Logger.Error($"Could not register shortcut {shortcut}: {errorMessage}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void BuildDropDownMenuEx(ContextMenuStrip mnuParent, string name, Type enumType, EventHandler clickEvent, object tag = null)
|
||||
public static ToolStripMenuItem BuildDropDownMenuEx(ContextMenuStrip mnuParent, string name, Type enumType, EventHandler clickEvent, object tag = null, int min = 0, int max = 0)
|
||||
{
|
||||
var subMenuName = $"{mnuParent.Name}_{name}";
|
||||
var subMenuItems = mnuParent.Items.Find(subMenuName, false);
|
||||
ToolStripMenuItem subMenuItem;
|
||||
if (subMenuItems.Length == 0)
|
||||
{
|
||||
subMenuItem = (ToolStripMenuItem)mnuParent.Items.Add(name);
|
||||
subMenuItem.Name = subMenuName;
|
||||
|
||||
if (subMenuItems.Length > 0)
|
||||
{
|
||||
return subMenuItems[0] as ToolStripMenuItem;
|
||||
}
|
||||
|
||||
ToolStripMenuItem subMenuItem;
|
||||
subMenuItem = (ToolStripMenuItem)mnuParent.Items.Add(name);
|
||||
subMenuItem.Name = subMenuName;
|
||||
|
||||
if (enumType != null)
|
||||
{
|
||||
foreach (var enumValue in Enum.GetValues(enumType))
|
||||
{
|
||||
var item = subMenuItem.DropDownItems.Add(enumValue.ToString());
|
||||
var itemText = (enumValue as IConvertible).GetDescription() ?? enumValue.ToString();
|
||||
var item = subMenuItem.DropDownItems.Add(itemText);
|
||||
item.Tag = tag;
|
||||
var strValue = enumValue.ToString();
|
||||
if (strValue.EndsWith("_"))
|
||||
{
|
||||
strValue = strValue.Substring(0, strValue.Length - 1);
|
||||
}
|
||||
item.AccessibleName = strValue;
|
||||
item.Click += clickEvent;
|
||||
}
|
||||
}
|
||||
else if (min >= 0 && max > min)
|
||||
{
|
||||
for (var i = 0; i <= 10; i++)
|
||||
{
|
||||
var value = i * (max / 10);
|
||||
|
||||
var subSubItemName = $"{subMenuName}_{value}";
|
||||
|
||||
var item = subMenuItem.DropDownItems.Add(value.ToString());
|
||||
item.Name = subSubItemName;
|
||||
item.Tag = tag;
|
||||
item.Click += clickEvent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
subMenuItem.Tag = tag;
|
||||
subMenuItem.Click += clickEvent;
|
||||
}
|
||||
|
||||
return subMenuItem;
|
||||
}
|
||||
|
||||
public static void BuildDropDownMenu(ToolStripDropDownItem mnuParent, string name, Type enumType, object colorData, string propertyName, EventHandler clickEvent)
|
||||
@@ -630,6 +730,11 @@ namespace ColorControl
|
||||
|
||||
public static int SetComboBoxEnumIndex(ComboBox comboBox, int value)
|
||||
{
|
||||
if (comboBox.Items.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
|
||||
for (var i = 0; i < comboBox.Items.Count; i++)
|
||||
@@ -676,12 +781,17 @@ namespace ColorControl
|
||||
shortcutString += "Win";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(shortcutString) && keyEvent.KeyCode != Keys.ControlKey && keyEvent.KeyCode != Keys.ShiftKey && keyEvent.KeyCode != Keys.Menu && keyEvent.KeyCode != Keys.LWin)
|
||||
var empty = string.IsNullOrEmpty(shortcutString);
|
||||
if ((!empty || KeysWithoutModifiers.Contains(keyEvent.KeyCode)) && keyEvent.KeyCode != Keys.ControlKey && keyEvent.KeyCode != Keys.ShiftKey && keyEvent.KeyCode != Keys.Menu && keyEvent.KeyCode != Keys.LWin)
|
||||
{
|
||||
shortcutString += " + " + keyEvent.KeyCode.ToString();
|
||||
if (!empty)
|
||||
{
|
||||
shortcutString += " + ";
|
||||
}
|
||||
shortcutString += keyEvent.KeyCode.ToString();
|
||||
}
|
||||
|
||||
if (pressedModifiers == 0 && !WinKeyDown)
|
||||
if (string.IsNullOrEmpty(shortcutString))
|
||||
{
|
||||
keyEvent.SuppressKeyPress = true;
|
||||
}
|
||||
@@ -689,6 +799,23 @@ namespace ColorControl
|
||||
return shortcutString;
|
||||
}
|
||||
|
||||
public static bool ValidateShortcut(string shortcut)
|
||||
{
|
||||
var valid = string.IsNullOrWhiteSpace(shortcut) || shortcut.Contains("+") || ShortcutIsAllowedWithoutModifier(shortcut);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
MessageForms.WarningOk("Invalid shortcut. The shortcut should have modifiers and a normal key or be F13-F24.");
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
public static bool ShortcutIsAllowedWithoutModifier(string shortcut)
|
||||
{
|
||||
return Enum.TryParse<Keys>(shortcut, out var key) && KeysWithoutModifiers.Contains(key);
|
||||
}
|
||||
|
||||
public static void HandleKeyboardShortcutUp(KeyEventArgs keyEvent)
|
||||
{
|
||||
if (keyEvent.KeyCode == Keys.LWin)
|
||||
@@ -764,6 +891,11 @@ namespace ColorControl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listView.ListViewItemSorter is ListViewColumnSorter sorter && sorter?.Order != SortOrder.None)
|
||||
{
|
||||
listView.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool OpenConsole()
|
||||
@@ -798,7 +930,7 @@ namespace ColorControl
|
||||
|
||||
public static Process GetProcessByName(string name, bool skipCurrent = true)
|
||||
{
|
||||
var currentProcessId = Process.GetCurrentProcess().Id;
|
||||
var currentProcessId = Environment.ProcessId;
|
||||
foreach (var process in Process.GetProcesses())
|
||||
{
|
||||
if (process.ProcessName.Equals(name) && (!skipCurrent || process.Id != currentProcessId))
|
||||
@@ -810,9 +942,13 @@ namespace ColorControl
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void StartProcess(string fileName, string arguments = null)
|
||||
public static void StartProcess(string fileName, string arguments = null, bool hidden = false, bool wait = false)
|
||||
{
|
||||
var process = Process.Start(fileName, arguments);
|
||||
var process = Process.Start(new ProcessStartInfo(fileName, arguments) { UseShellExecute = true, WindowStyle = hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal });
|
||||
if (wait)
|
||||
{
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetResourceFile(string resourceName)
|
||||
@@ -926,6 +1062,57 @@ namespace ColorControl
|
||||
}
|
||||
target.AddRange(words);
|
||||
}
|
||||
|
||||
public static int ParseInt(string value, int def = 0)
|
||||
{
|
||||
if (int.TryParse(value, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
public static int ParseDynamicAsInt(dynamic value, int def = 0)
|
||||
{
|
||||
if (value.Type == JTokenType.Integer)
|
||||
{
|
||||
return (int)value.Value;
|
||||
}
|
||||
if (value.Type == JTokenType.String)
|
||||
{
|
||||
return ParseInt(value.Value, def);
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
public static async Task<dynamic> GetRestJsonAsync(string url, Action<dynamic> callBack = null)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("request");
|
||||
|
||||
var response = await httpClient.GetAsync(url);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var data = JsonConvert.DeserializeObject<dynamic>(result);
|
||||
|
||||
if (callBack != null)
|
||||
{
|
||||
callBack(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
if (callBack != null)
|
||||
{
|
||||
callBack(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class EnumComboBoxItem
|
||||
@@ -941,34 +1128,48 @@ namespace ColorControl
|
||||
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
private static string FindIndexedProcessName(int pid)
|
||||
private static string FindIndexedProcessName(int pid, string processName, IEnumerable<Process> processes = null)
|
||||
{
|
||||
var processName = Process.GetProcessById(pid).ProcessName;
|
||||
var processesByName = Process.GetProcessesByName(processName);
|
||||
string processIndexdName = null;
|
||||
var processesByName = processes == null ? Process.GetProcessesByName(processName) : processes.Where(p => p.ProcessName.Equals(processName, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
string processIndexedName = null;
|
||||
|
||||
for (var index = 0; index < processesByName.Length; index++)
|
||||
{
|
||||
processIndexdName = index == 0 ? processName : processName + "#" + index;
|
||||
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
|
||||
processIndexedName = index == 0 ? processName : processName + "#" + index;
|
||||
var processId = new PerformanceCounter("Process", "ID Process", processIndexedName);
|
||||
if ((int)processId.NextValue() == pid)
|
||||
{
|
||||
return processIndexdName;
|
||||
return processIndexedName;
|
||||
}
|
||||
}
|
||||
|
||||
return processIndexdName;
|
||||
return processIndexedName;
|
||||
}
|
||||
|
||||
private static Process FindPidFromIndexedProcessName(string indexedProcessName)
|
||||
private static Process FindPidFromIndexedProcessName(string indexedProcessName, IEnumerable<Process> processes = null)
|
||||
{
|
||||
var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName);
|
||||
return Process.GetProcessById((int)parentId.NextValue());
|
||||
var parentProcessId = (int)parentId.NextValue();
|
||||
|
||||
try
|
||||
{
|
||||
return processes == null ? Process.GetProcessById(parentProcessId) : processes.FirstOrDefault(p => p.Id == parentProcessId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Process Parent(this Process process)
|
||||
public static Process Parent(this Process process, IEnumerable<Process> processes = null)
|
||||
{
|
||||
return FindPidFromIndexedProcessName(FindIndexedProcessName(process.Id));
|
||||
var indexedProcessName = FindIndexedProcessName(process.Id, process.ProcessName, processes);
|
||||
if (indexedProcessName == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return FindPidFromIndexedProcessName(indexedProcessName, processes);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-75
@@ -1,9 +1,6 @@
|
||||
using NLog;
|
||||
using System;
|
||||
using PacketDotNet;
|
||||
using System.Net.NetworkInformation;
|
||||
using SharpPcap;
|
||||
using SharpPcap.Npcap;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
@@ -17,13 +14,8 @@ namespace ColorControl
|
||||
private const string ArgumentExceptionInvalidPasswordLength = "Invalid password length.";
|
||||
private const int DefaultWolPort = 9;
|
||||
|
||||
public static bool WakeFunction(string macAddress, bool usePcap = false)
|
||||
public static bool WakeFunction(string macAddress)
|
||||
{
|
||||
if (usePcap)
|
||||
{
|
||||
return WakeFunctionPcap(macAddress);
|
||||
}
|
||||
|
||||
return WakeFunctionToAllNics(macAddress);
|
||||
}
|
||||
|
||||
@@ -117,70 +109,5 @@ namespace ColorControl
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
||||
//You need SharpPcap for this to work
|
||||
private static bool WakeFunctionPcap(string macAddress)
|
||||
{
|
||||
/* Retrieve the device list */
|
||||
var devices = CaptureDeviceList.Instance;
|
||||
|
||||
Logger.Debug("Waking device with MAC-address " + macAddress);
|
||||
|
||||
/*If no device exists, print error */
|
||||
if (devices.Count < 1)
|
||||
{
|
||||
Logger.Debug("No network device found on this machine");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var device in devices)
|
||||
{
|
||||
//if (device is NpcapDevice npcapDevice)
|
||||
//{
|
||||
// if (!npcapDevice.Interface.GatewayAddresses.Any())
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
//}
|
||||
|
||||
if (device is NpcapDevice npcapDevice)
|
||||
{
|
||||
Logger.Debug($"Opening network device (NpcapDevice): FriendlyName: {npcapDevice.Interface.FriendlyName}, Description: {npcapDevice.Interface.Description}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug($"Opening network device (NOT A NpcapDevice!): {device.Name}, Description: {device.Description}");
|
||||
}
|
||||
|
||||
//Open the device
|
||||
device.Open();
|
||||
|
||||
//A magic packet is a broadcast frame containing anywhere within its payload: 6 bytes of ones
|
||||
//(resulting in hexadecimal FF FF FF FF FF FF), followed by sixteen repetitions
|
||||
|
||||
string response = macAddress.Replace(":", "-");
|
||||
|
||||
var address = PhysicalAddress.Parse("FFFFFFFFFFFF");
|
||||
EthernetPacket ethernet = new EthernetPacket(address, address, EthernetType.WakeOnLan);
|
||||
ethernet.PayloadPacket = new WakeOnLanPacket(PhysicalAddress.Parse(response));
|
||||
|
||||
byte[] bytes = ethernet.Bytes;
|
||||
|
||||
try
|
||||
{
|
||||
//Send the packet out the network device
|
||||
device.SendPacket(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Debug(e.Message);
|
||||
}
|
||||
|
||||
device.Close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+237
-35
@@ -2,6 +2,7 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -19,11 +20,17 @@ namespace LgTv
|
||||
|
||||
public enum PictureMode
|
||||
{
|
||||
cinema, eco, expert1, expert2, game,
|
||||
normal, photo, sports, technicolor, vivid, hdrEffect, hdrCinema,
|
||||
hdrCinemaBright, hdrExternal, hdrGame, hdrStandard, hdrTechnicolor,
|
||||
hdrVivid, dolbyHdrCinema, dolbyHdrCinemaBright, dolbyHdrDarkAmazon,
|
||||
dolbyHdrGame, dolbyHdrStandard, dolbyHdrVivid, dolbyStandard
|
||||
cinema, eco, expert1, expert2, game, normal, photo, sports, technicolor, vivid, hdrEffect, filmMaker,
|
||||
|
||||
hdrCinema, hdrCinemaBright, hdrExternal, hdrGame, hdrStandard, hdrTechnicolor, hdrVivid, hdrFilmMaker,
|
||||
|
||||
dolbyHdrCinema, dolbyHdrCinemaBright, dolbyHdrDarkAmazon, dolbyHdrGame, dolbyHdrStandard, dolbyHdrVivid, dolbyStandard
|
||||
}
|
||||
|
||||
public enum DynamicRange
|
||||
{
|
||||
sdr, hdr, technicolorHdr, dolbyHdr,
|
||||
sdrALLM, hdrALLM, technicolorHdrALLM, dolbyHdrALLM
|
||||
}
|
||||
|
||||
public enum OffToHigh
|
||||
@@ -70,7 +77,8 @@ namespace LgTv
|
||||
auto,
|
||||
extended,
|
||||
wide,
|
||||
srgb
|
||||
srgb,
|
||||
native
|
||||
}
|
||||
|
||||
public enum EnergySaving
|
||||
@@ -93,9 +101,119 @@ namespace LgTv
|
||||
public enum TruMotionMode
|
||||
{
|
||||
off,
|
||||
smooth,
|
||||
clear,
|
||||
clearPlus,
|
||||
user
|
||||
}
|
||||
|
||||
public enum WhiteBalanceColorTemperature
|
||||
{
|
||||
cool,
|
||||
medium,
|
||||
warm1,
|
||||
warm2,
|
||||
warm3
|
||||
}
|
||||
|
||||
public enum FalseToTrue
|
||||
{
|
||||
[Description("Disabled")]
|
||||
false_,
|
||||
[Description("Enabled")]
|
||||
true_
|
||||
}
|
||||
|
||||
public enum GammaExp
|
||||
{
|
||||
low,
|
||||
medium,
|
||||
high1,
|
||||
high2
|
||||
}
|
||||
|
||||
public enum HdmiIcon
|
||||
{
|
||||
[Description("HDMI")]
|
||||
hdmigeneric,
|
||||
[Description("Satellite")]
|
||||
satellite,
|
||||
[Description("Set-Top Box")]
|
||||
settopbox,
|
||||
[Description("DVD Player")]
|
||||
dvd,
|
||||
[Description("Blu-ray Player")]
|
||||
bluray,
|
||||
[Description("Home Theater")]
|
||||
hometheater,
|
||||
[Description("Game Console")]
|
||||
gameconsole,
|
||||
[Description("Streaming Box")]
|
||||
streamingbox,
|
||||
[Description("Generic Camera")]
|
||||
camera,
|
||||
[Description("PC")]
|
||||
pc,
|
||||
[Description("Mobile Device")]
|
||||
mobile
|
||||
}
|
||||
|
||||
public enum SoundMode
|
||||
{
|
||||
[Description("AI Sound Pro")]
|
||||
aiSoundPlus,
|
||||
[Description("AI Sound")]
|
||||
aiSound,
|
||||
[Description("Standard")]
|
||||
standard,
|
||||
[Description("Clear Voice")]
|
||||
news,
|
||||
[Description("Music")]
|
||||
music,
|
||||
[Description("Cinema")]
|
||||
movie,
|
||||
[Description("Sports")]
|
||||
sports,
|
||||
[Description("Game Optimizer")]
|
||||
game,
|
||||
[Description("Pagode")]
|
||||
pagode,
|
||||
[Description("Serta Wego")]
|
||||
sertaWego,
|
||||
[Description("Brazilian Punk")]
|
||||
brazilianPunk,
|
||||
[Description("ASC")]
|
||||
asc,
|
||||
[Description("Bass Boost")]
|
||||
bass,
|
||||
}
|
||||
|
||||
public enum SoundOutput
|
||||
{
|
||||
[Description("TV Speaker")]
|
||||
tv_speaker,
|
||||
[Description("HDMI(ARC) Device")]
|
||||
external_arc,
|
||||
[Description("Optical Out Device")]
|
||||
external_optical,
|
||||
[Description("Bluetooth Device")]
|
||||
bt_soundbar,
|
||||
[Description("Mobile Device")]
|
||||
mobile_phone,
|
||||
[Description("Audio Out Device")]
|
||||
lineout,
|
||||
[Description("Wired Headphones")]
|
||||
headphone,
|
||||
[Description("Bluetooth Device + TV Speaker")]
|
||||
tv_speaker_bluetooth,
|
||||
[Description("Optical Out Device + TV Speaker")]
|
||||
tv_external_speaker,
|
||||
[Description("Wired Headphones + TV Speaker")]
|
||||
tv_speaker_headphone,
|
||||
[Description("WiSA Speakers")]
|
||||
wisa_speaker,
|
||||
}
|
||||
|
||||
public class LgTvApi:IDisposable
|
||||
{
|
||||
public bool ConnectionClosed { get => _connection?.ConnectionClosed ?? true; }
|
||||
@@ -108,11 +226,6 @@ namespace LgTv
|
||||
private List<Channel> _channelList;
|
||||
private List<App> _appList;
|
||||
private string _ip;
|
||||
|
||||
private const string BEFORE_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\",\"READ_SETTINGS\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}";
|
||||
|
||||
private const string AFTER_PAIR_HAND_SHAKE = "{\"type\":\"register\",\"id\":\"register_0\",\"payload\":{\"forcePairing\":false,\"pairingType\":\"PROMPT\",\"client-key\":\"CLIENTKEYGOESHERE\",\"manifest\":{\"manifestVersion\":1,\"appVersion\":\"1.1\",\"signed\":{\"created\":\"20140509\",\"appId\":\"com.lge.test\",\"vendorId\":\"com.lge\",\"localizedAppNames\":{\"\":\"LG Remote App\",\"ko-KR\":\"리모컨 앱\",\"zxx-XX\":\"ЛГ Rэмotэ AПП\"},\"localizedVendorNames\":{\"\":\"LG Electronics\"},\"permissions\":[\"TEST_SECURE\",\"CONTROL_INPUT_TEXT\",\"CONTROL_MOUSE_AND_KEYBOARD\",\"READ_INSTALLED_APPS\",\"READ_LGE_SDX\",\"READ_NOTIFICATIONS\",\"SEARCH\",\"WRITE_SETTINGS\",\"WRITE_NOTIFICATION_ALERT\",\"CONTROL_POWER\",\"READ_CURRENT_CHANNEL\",\"READ_RUNNING_APPS\",\"READ_UPDATE_INFO\",\"UPDATE_FROM_REMOTE_APP\",\"READ_LGE_TV_INPUT_EVENTS\",\"READ_TV_CURRENT_TIME\"],\"serial\":\"2f930e2d2cfe083771f68e4fe7bb07\"},\"permissions\":[\"LAUNCH\",\"LAUNCH_WEBAPP\",\"APP_TO_APP\",\"CLOSE\",\"TEST_OPEN\",\"TEST_PROTECTED\",\"CONTROL_AUDIO\",\"CONTROL_DISPLAY\",\"CONTROL_INPUT_JOYSTICK\",\"CONTROL_INPUT_MEDIA_RECORDING\",\"CONTROL_INPUT_MEDIA_PLAYBACK\",\"CONTROL_INPUT_TV\",\"CONTROL_POWER\",\"READ_APP_STATUS\",\"READ_CURRENT_CHANNEL\",\"READ_INPUT_DEVICE_LIST\",\"READ_NETWORK_STATE\",\"READ_RUNNING_APPS\",\"READ_TV_CHANNEL_LIST\",\"WRITE_NOTIFICATION_TOAST\",\"READ_POWER_STATE\",\"READ_COUNTRY_INFO\",\"READ_SETTINGS\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}";
|
||||
|
||||
private string _currentPairKey;
|
||||
private string webSocketUri;
|
||||
|
||||
@@ -149,18 +262,21 @@ namespace LgTv
|
||||
|
||||
public async Task MakeHandShake()
|
||||
{
|
||||
var registerJson = Utils.GetResourceFile("LG_register.json");
|
||||
|
||||
_currentPairKey = _keyStore.GetClientKey();
|
||||
if (_currentPairKey != null && !_currentPairKey.All(k => k == '\0') && _keyStore.HasValidHandShake(BEFORE_PAIR_HAND_SHAKE))
|
||||
if (_currentPairKey != null && !_currentPairKey.All(k => k == '\0') && _keyStore.HasValidHandShake(registerJson))
|
||||
{
|
||||
var key = AFTER_PAIR_HAND_SHAKE.Replace("CLIENTKEYGOESHERE", _currentPairKey);
|
||||
var key = registerJson.Replace("CLIENTKEYGOESHERE", _currentPairKey);
|
||||
var conn = await _connection.SendCommandAsync(key);
|
||||
_keyStore.SaveClientKey((string)conn.clientKey);
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic result = await _connection.SendCommandAsync(BEFORE_PAIR_HAND_SHAKE);
|
||||
var registerJsonRemovedKey = registerJson.Replace("CLIENTKEYGOESHERE", string.Empty);
|
||||
dynamic result = await _connection.SendCommandAsync(registerJsonRemovedKey);
|
||||
_keyStore.SaveClientKey(result.clientKey);
|
||||
_keyStore.SaveHandShake(BEFORE_PAIR_HAND_SHAKE);
|
||||
_keyStore.SaveHandShake(registerJson);
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +418,17 @@ namespace LgTv
|
||||
{
|
||||
await _connection.SendCommandAsync(new RequestMessage("3d", "ssap://com.webos.service.tv.display/set3DOff"));
|
||||
}
|
||||
|
||||
public async Task TurnScreenOff()
|
||||
{
|
||||
await _connection.SendCommandAsync(new RequestMessage("", "ssap://com.webos.service.tvpower/power/turnOffScreen"));
|
||||
}
|
||||
|
||||
public async Task TurnScreenOn()
|
||||
{
|
||||
await _connection.SendCommandAsync(new RequestMessage("", "ssap://com.webos.service.tvpower/power/turnOnScreen"));
|
||||
}
|
||||
|
||||
public async Task<bool> IsTurnedOn3D()
|
||||
{
|
||||
//Response: { returnValue: true, status3D: { status: true, pattern: ’2Dto3D’ } }
|
||||
@@ -364,11 +491,18 @@ namespace LgTv
|
||||
await _connection.SubscribeAsync(requestMessage, callback);
|
||||
}
|
||||
|
||||
public async Task SubscribeForegroundApp(Func<dynamic, bool> callback, dynamic payload = null)
|
||||
{
|
||||
var requestMessage = new RequestMessage("ssap://com.webos.applicationManager/getForegroundAppInfo", null, "subscribe");
|
||||
|
||||
await _connection.SubscribeAsync(requestMessage, callback);
|
||||
}
|
||||
|
||||
public async Task SubscribePictureSettings(Func<dynamic, bool> callback, dynamic payload = null)
|
||||
{
|
||||
if (payload == null)
|
||||
{
|
||||
var keys = @"[ ""contrast"", ""backlight"" ]";
|
||||
var keys = @"[ ""contrast"", ""backlight"", ""brightness"", ""color"" ]";
|
||||
|
||||
var json = @"{ ""category"": ""picture"", ""keys"": " + keys + @", ""subscribe"": true }";
|
||||
|
||||
@@ -438,9 +572,19 @@ namespace LgTv
|
||||
return appList.OrderBy(e => e.title);
|
||||
}
|
||||
|
||||
public async Task<string> LaunchApp(string appId)
|
||||
public async Task<string> LaunchApp(string appId, dynamic @params = null)
|
||||
{
|
||||
var requestMessage = new RequestMessage("ssap://system.launcher/launch", new { id = appId });
|
||||
dynamic payload;
|
||||
if (@params != null)
|
||||
{
|
||||
payload = new { id = appId, @params = @params };
|
||||
}
|
||||
else
|
||||
{
|
||||
payload = new { id = appId };
|
||||
}
|
||||
|
||||
var requestMessage = new RequestMessage("ssap://system.launcher/launch", payload);
|
||||
var response = await _connection.SendCommandAsync(requestMessage);
|
||||
return (string)response.sessionId;
|
||||
}
|
||||
@@ -472,18 +616,7 @@ namespace LgTv
|
||||
|
||||
public async Task SetSystemSettings(string key, object value, string category = "picture")
|
||||
{
|
||||
string jsonValue;
|
||||
var valueType = value.GetType();
|
||||
var intType = typeof(int);
|
||||
if (valueType.IsArray && intType.IsAssignableFrom(valueType.GetElementType()))
|
||||
{
|
||||
var values = ((Array)value).Cast<int>();
|
||||
jsonValue = "[" + string.Join(", ", values.Select(x => x.ToString()).ToArray()) + ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonValue = $"\"{value}\"";
|
||||
}
|
||||
var jsonValue = ParamToJson(value, ref key);
|
||||
|
||||
var lunauri = "luna://com.webos.settingsservice/setSystemSettings";
|
||||
|
||||
@@ -494,24 +627,76 @@ namespace LgTv
|
||||
await ExecuteRequest(lunauri, @params);
|
||||
}
|
||||
|
||||
public async Task SetConfig(string key, string value)
|
||||
private static string ParamToJson(object value, ref string key)
|
||||
{
|
||||
string jsonValue;
|
||||
var valueType = value.GetType();
|
||||
var intType = typeof(int);
|
||||
if (valueType.IsArray && intType.IsAssignableFrom(valueType.GetElementType()))
|
||||
{
|
||||
var values = ((Array)value).Cast<int>();
|
||||
jsonValue = "[" + string.Join(", ", values.Select(x => x.ToString()).ToArray()) + ']';
|
||||
}
|
||||
else if (key?.Contains("_") == true)
|
||||
{
|
||||
var keys = key.Split('_');
|
||||
key = keys[0];
|
||||
var childKey = keys[1];
|
||||
|
||||
jsonValue = @"{ """ + childKey + @""": " + value + " }";
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
jsonValue = value.ToString().ToLowerInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonValue = $"\"{value}\"";
|
||||
}
|
||||
|
||||
return jsonValue;
|
||||
}
|
||||
|
||||
public async Task SetConfig(string key, object value)
|
||||
{
|
||||
var lunauri = "luna://com.webos.service.config/setConfigs";
|
||||
|
||||
var @params = JObject.Parse(@"{ ""configs"": { """ + key + @""": """ + value + @""" } }");
|
||||
var jsonValue = ParamToJson(value, ref key);
|
||||
|
||||
var @params = JObject.Parse(@"{ ""configs"": { """ + key + @""": " + jsonValue + @" } }");
|
||||
|
||||
await ExecuteRequest(lunauri, @params);
|
||||
}
|
||||
|
||||
public async Task SetConfig(string key, bool value)
|
||||
public async Task SetDeviceConfig(string id, string icon, string label)
|
||||
{
|
||||
var lunauri = "luna://com.webos.service.config/setConfigs";
|
||||
var lunauri = "luna://com.webos.service.eim/setDeviceInfo";
|
||||
|
||||
var @params = JObject.Parse(@"{ ""configs"": { """ + key + @""": " + value.ToString().ToLowerInvariant() + @" } }");
|
||||
var iconPng = icon + ".png";
|
||||
|
||||
var @params = new
|
||||
{
|
||||
id = id,
|
||||
label = label,
|
||||
icon = iconPng
|
||||
};
|
||||
|
||||
await ExecuteRequest(lunauri, @params);
|
||||
}
|
||||
|
||||
public async Task Reboot()
|
||||
{
|
||||
//await ExecuteRequest("luna://com.webos.service.oledepl/setGlobalStressReduction", new { enable = true });
|
||||
|
||||
//var requestMessage = new RequestMessage("ssap://com.webos.service.devicereset/requestReboot", new { reason = "broadcastSystemChanged" });
|
||||
//var response = await _connection.SendCommandAsync(requestMessage);
|
||||
|
||||
//await ExecuteRequest("luna://com.webos.settingsservice/setSystemSettings", new { category = "network", settings = new { wolwowlOnOff = "true" } });
|
||||
|
||||
//await ExecuteRequest("luna://com.webos.settingsservice/setSystemSettings", new { category = "general", settings = new { powerOffBySCA3SystemChanged = "true" } });
|
||||
//await ExecuteRequest("luna://com.webos.service.devicereset/requestReboot", new { reason = "broadcastSystemChanged" });
|
||||
}
|
||||
|
||||
private async Task ExecuteRequest(string lunauri, object @params)
|
||||
{
|
||||
var buttons = new[]
|
||||
@@ -573,6 +758,23 @@ namespace LgTv
|
||||
return res;
|
||||
}
|
||||
|
||||
public async Task<dynamic> GetSystemSettings2(string category)
|
||||
{
|
||||
var payload = new
|
||||
{
|
||||
category = "dimensionInfo",
|
||||
//dimension = new { input = "default", _3dStatus = "2d", dynamicRange = "sdr" },
|
||||
keys = new[] { "input", "_3dStatus", "colorSystem", "dynamicRange", "gameInput" }
|
||||
};
|
||||
//await ExecuteRequest("luna://com.webos.settingsservice/getSystemSettings", payload);
|
||||
//return null;
|
||||
|
||||
var requestMessage = new RequestMessage("ssap://settings/getSystemSettings", payload);
|
||||
var command = _connection.SendCommandAsync(requestMessage);
|
||||
var res = await command;
|
||||
return res;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_connection.Close();
|
||||
|
||||
@@ -41,33 +41,46 @@ namespace LgTv
|
||||
_commandCount = 0;
|
||||
_connection = new MessageWebSocket();
|
||||
_connection.Control.MessageType = SocketMessageType.Utf8;
|
||||
// if(ignoreReceiver==false)
|
||||
_connection.MessageReceived += Connection_MessageReceived;
|
||||
|
||||
_connection.MessageReceived += Connection_MessageReceived;
|
||||
_connection.Closed += Connection_Closed;
|
||||
using (var cancellationTokenSource = new CancellationTokenSource(5000))
|
||||
|
||||
try
|
||||
{
|
||||
var connectTask = _connection.ConnectAsync(uri).AsTask(cancellationTokenSource.Token);
|
||||
var result = await connectTask.ContinueWith((antecedent) =>
|
||||
{
|
||||
if (antecedent.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
// connectTask ran to completion, so we know that the MessageWebSocket is connected.
|
||||
// Add additional code here to use the MessageWebSocket.
|
||||
IsConnected?.Invoke(true);
|
||||
await _connection.ConnectAsync(uri).AsTask().WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
_messageWriter = new DataWriter(_connection.OutputStream);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// connectTask timed out, or faulted.
|
||||
return false;
|
||||
}
|
||||
});
|
||||
_messageWriter = new DataWriter(_connection.OutputStream);
|
||||
|
||||
return result;
|
||||
IsConnected?.Invoke(true);
|
||||
return true;
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//using (var cancellationTokenSource = new CancellationTokenSource(5000))
|
||||
//{
|
||||
// var connectTask = _connection.ConnectAsync(uri).AsTask(cancellationTokenSource.Token);
|
||||
// var result = await connectTask.ContinueWith((antecedent) =>
|
||||
// {
|
||||
// if (antecedent.Status == TaskStatus.RanToCompletion)
|
||||
// {
|
||||
// // connectTask ran to completion, so we know that the MessageWebSocket is connected.
|
||||
// // Add additional code here to use the MessageWebSocket.
|
||||
// IsConnected?.Invoke(true);
|
||||
|
||||
// _messageWriter = new DataWriter(_connection.OutputStream);
|
||||
// return true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // connectTask timed out, or faulted.
|
||||
// return false;
|
||||
// }
|
||||
// });
|
||||
|
||||
// return result;
|
||||
//}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -88,6 +101,11 @@ namespace LgTv
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_messageWriter == null)
|
||||
{
|
||||
throw new Exception("SendMessageAsync: no writer");
|
||||
}
|
||||
|
||||
_messageWriter.WriteString(message);
|
||||
|
||||
using (var cancellationTokenSource = new CancellationTokenSource(5000))
|
||||
@@ -172,7 +190,17 @@ namespace LgTv
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var dr = args.GetDataReader())
|
||||
var task = new Task<DataReader>(new Func<DataReader>(args.GetDataReader));
|
||||
task.Start();
|
||||
var result = task.Wait(5000);
|
||||
|
||||
var dr = result ? task.Result : null;
|
||||
if (!result)
|
||||
{
|
||||
throw new Exception("Timeout while reading response, possible disconnect");
|
||||
}
|
||||
|
||||
using (dr)
|
||||
{
|
||||
dr.UnicodeEncoding = UnicodeEncoding.Utf8;
|
||||
var message = dr.ReadString(dr.UnconsumedBufferLength);
|
||||
@@ -205,7 +233,14 @@ namespace LgTv
|
||||
|
||||
if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
|
||||
{
|
||||
callback(obj.payload);
|
||||
try
|
||||
{
|
||||
callback(obj.payload);
|
||||
}
|
||||
catch (Exception callbackException)
|
||||
{
|
||||
Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,8 +265,9 @@ namespace LgTv
|
||||
{
|
||||
try
|
||||
{
|
||||
if (taskCompletion.Task?.Status == TaskStatus.Running)
|
||||
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
|
||||
taskCompletion.SetException(new Exception(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,21 @@ namespace LgTv
|
||||
SAP,
|
||||
PROGRAM,
|
||||
PLAY,
|
||||
PAUSE
|
||||
PAUSE,
|
||||
STOP,
|
||||
REWIND,
|
||||
FASTFORWARD,
|
||||
GUIDE,
|
||||
AMAZON,
|
||||
NETFLIX,
|
||||
MAGNIFIER_ZOOM,
|
||||
LIVE_ZOOM,
|
||||
_3D_MODE,
|
||||
ASPECT_RATIO,
|
||||
RECENT,
|
||||
RECORD,
|
||||
SCREEN_REMOTE,
|
||||
MYAPPS
|
||||
}
|
||||
|
||||
public class LgWebOsMouseService : IDisposable
|
||||
@@ -59,7 +73,7 @@ namespace LgTv
|
||||
}
|
||||
public void SendButton(int number)
|
||||
{
|
||||
_connection.SendMessageAsync($"type:button\nname:{number}\n\n");
|
||||
_connection.SendMessageAsync($"type:button\nname:{number}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
public void SendButton(ButtonType bt)
|
||||
{
|
||||
@@ -68,23 +82,23 @@ namespace LgTv
|
||||
{
|
||||
text = text.Substring(1);
|
||||
}
|
||||
_connection.SendMessageAsync($"type:button\nname:{text}\n\n");
|
||||
_connection.SendMessageAsync($"type:button\nname:{text}\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");
|
||||
_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");
|
||||
_connection.SendMessageAsync($"type:scroll\ndx:{dx}\ndy:{dy}\n\n").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Click()
|
||||
{
|
||||
_connection.SendMessageAsync("type:click\n\n");
|
||||
_connection.SendMessageAsync("type:click\n\n").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Native" version="0.1.1" targetFramework="net472" />
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net472" />
|
||||
<package id="NLog" version="4.7.10" targetFramework="net472" />
|
||||
<package id="NStandard" version="0.8.9" targetFramework="net472" />
|
||||
<package id="NvAPIWrapper.Net" version="0.8.1.101" targetFramework="net472" />
|
||||
<package id="NWin32" version="1.2.0" targetFramework="net472" />
|
||||
<package id="PacketDotNet" version="1.2.0" targetFramework="net472" />
|
||||
<package id="SharpPcap" version="5.4.0" targetFramework="net472" />
|
||||
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />
|
||||
<package id="System.Memory" version="4.5.4" targetFramework="net472" />
|
||||
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />
|
||||
<package id="TaskScheduler" version="2.9.1" targetFramework="net472" />
|
||||
</packages>
|
||||
@@ -1,19 +1,55 @@
|
||||
# ColorControl
|
||||
Easily change NVIDIA display settings and/or control LG TV's
|
||||
Easily change NVIDIA and AMD display settings and/or control LG TV's
|
||||
|
||||
If you own a NVIDIA graphics card, this app allows you not to only adjust basic display settings, but some hidden settings as well:
|
||||
## NVIDIA/AMD controller
|
||||
|
||||

|
||||
If you own a NVIDIA or AMD graphics card, this app allows you not to only adjust basic display settings, but some hidden settings as well.
|
||||
For both NVIDIA and AMD graphics cards, you can configure your own presets to change the color depth (6 to 16 bpc), color format (RGB/YUV), refresh rate, dithering and HDR setting. You can assign a global keyboard shortcut to each preset to change the display settings (and HDR!) with just a couple of key presses.
|
||||
The NVIDIA controller even has some more options:
|
||||
* Dynamic range: VESA (Full RGB) or CEA (Limited RGB/YUV)
|
||||
* Color space: to change the color space, but most may not be supported by your tv
|
||||
* Dithering: you can define the dithering mode (Temporal or Spatial) and the dithering bit depth
|
||||
|
||||
And if you own a recent LG TV, you can control your TV through the app (no NVIDIA graphics card needed):
|
||||
Notes:
|
||||
* For a specific setting to be applied, you must include it within the preset. Just click the "Include" menu item and once it is checked, you're good to go.
|
||||
|
||||

|
||||
Screenshot:
|
||||

|
||||
|
||||
For this to work correctly:
|
||||
## LG controller
|
||||
|
||||
If you own a recent LG TV (2018 or newer, older might work), you can control your TV through the app (no NVIDIA or AMD graphics card needed).
|
||||
At startup of the application it will automatically detect your tv's (see below) if they are on the same network as your pc. If a tv is powered on, it will show a popup by which you can allow ColorControl to send commands to you tv. This will only happen the first time or whenever there's a change in the required permissions. A new version of ColorControl might need this.
|
||||
It is also possible to add a tv manually by using the "Add" button. A name and ip address are required, the MAC address is only necessary for Wake-On-Lan.
|
||||
You can configure as well when to automatically power your tv on or off:
|
||||
* Power on after startup of pc
|
||||
* Power on after resume
|
||||
* Power off on shutdown
|
||||
* Power off on standby
|
||||
* Power off on screensaver and on when screensaver deactivates
|
||||
|
||||
### Presets
|
||||
|
||||
With the presets you can peform actions on your tv you would normally do via the remote control. Properties of a preset:
|
||||
* Name: fill in your own name/description
|
||||
* Device: select the tv to perform the action on. Defaults to "Globally selected device", which is the selected device in the top devices drop down.
|
||||
* App: select the app to launch that is installed on your tv (optional)
|
||||
* Shortcut: enter the global shortcut to execute this preset
|
||||
* Steps: steps to execute sequentially. These steps can be:
|
||||
* Remote control buttons: like RIGHT, LEFT, ENTER, etc.
|
||||
* Actions: directly change picture settings like backlight, contrast, pictureMode, etc. In a dialog you have to specify the value.
|
||||
* NVIDIA/AMD presets: add NVIDIA or AMD presets here that have to execute as well
|
||||
|
||||
Furthermore, you can add a trigger to a preset which means it will execute automatically when a process on your pc is running. See for more information: https://github.com/Maassoft/ColorControl/releases/tag/v4.0.0.0
|
||||
|
||||
Screenshot:
|
||||

|
||||
|
||||
### Auto detecting your tv
|
||||
Check if the TV is listed in Windows Device Manager (Win+X -> Device Manager) under Digital Media Devices. If not then add the TV using Settings (Win+I) -> "Devices" -> "Add Bluetooth or other device" -> "Everything Else", then select your TV by name. It should now appear in Device Manager. (If your TV is not shown when adding devices then your PC is unable to see the TV on the network, check your network settings on both the PC & TV)
|
||||
NOTE: You may have to add the TV as a device more than once before it appears in Device Manager, as Windows can detect the TV as multiple devices.
|
||||
|
||||
If you receive WinPcap errors download and install Npcap (https://nmap.org/npcap/#download) in WinPcap compatibility mode. WinPcap is depreciated under windows 10.
|
||||
WinPcap is no longer used by default, but if you receive WinPcap errors, download and install Npcap (https://nmap.org/npcap/#download) in WinPcap compatibility mode. WinPcap is depreciated under windows 10.
|
||||
|
||||
On the Options-tabpage you can finetune some parameters and/or enable some settings:
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Stellt Methoden für das Senden von Anfragen über das ARP-Protokoll bereit.</summary>
|
||||
public static class ArpRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Sendet eine Anfrage über das ARP-Protokoll, um eine IP-Adresse in die Physikalische Adresse aufzulösen. Falls sich die physikalische Adresse bereits im Cache des Hosts befindet, wird diese zurückgegeben.
|
||||
/// </summary>
|
||||
/// <param name="destination">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <returns>Eine <see cref="T:System.Net.ArpRequestResult">ArpRequestResult</see>-Instanz, welche die Ergebnisse der Anfrage enthält.</returns>
|
||||
public static ArpRequestResult Send(IPAddress destination)
|
||||
{
|
||||
if (destination == null)
|
||||
throw new ArgumentNullException(nameof(destination));
|
||||
|
||||
int destIp = BitConverter.ToInt32(destination.GetAddressBytes(), 0);
|
||||
|
||||
var addr = new byte[6];
|
||||
var len = addr.Length;
|
||||
|
||||
var res = NativeMethods.SendARP(destIp, 0, addr, ref len);
|
||||
|
||||
if (res == 0)
|
||||
return new ArpRequestResult(new PhysicalAddress(addr));
|
||||
return new ArpRequestResult(new Win32Exception(res));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sendet eine Anfrage über das ARP-Protokoll, um eine IP-Adresse in die Physikalische Adresse aufzulösen. Falls sich die physikalische Adresse bereits im Cache des Hosts befindet, wird diese zurückgegeben.
|
||||
/// </summary>
|
||||
/// <param name="destination">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <returns>Ein asynchroner Task, welcher einen ARP-Request sendet.</returns>
|
||||
public static Task<ArpRequestResult> SendAsync(IPAddress destination) => Task.Run(() => Send(destination));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
// TODO: rethink the whole exception thing
|
||||
|
||||
/// <summary>
|
||||
/// Enthält die Rückgabewerte der ArpRequest.Send-Funktion.
|
||||
/// </summary>
|
||||
public class ArpRequestResult
|
||||
{
|
||||
/// <summary>Falls Fehler bei der Protokollanfrage auftreten, werden diese in dieser Eigenschaft abgelegt. Andernfalls null.</summary>
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>Die aufgelöste physikalische Adresse.</summary>
|
||||
public PhysicalAddress Address { get; }
|
||||
|
||||
/// <summary>Erstellt eine neue ArpRequestResult-Instanz</summary>
|
||||
/// <param name="address">Die physikalische Adresse</param>
|
||||
public ArpRequestResult(PhysicalAddress address)
|
||||
{
|
||||
this.Exception = null;
|
||||
Address = address;
|
||||
}
|
||||
|
||||
/// <summary>Erstellt eine neue ArpRequestResult-Instanz</summary>
|
||||
/// <param name="exception">Der aufgetretene Fehler</param>
|
||||
public ArpRequestResult(Exception exception)
|
||||
{
|
||||
this.Exception = exception;
|
||||
Address = null;
|
||||
}
|
||||
|
||||
/// <summary>Konvertiert ARP-Rückgabewerte in eine Zeichenfolge.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (Address == null)
|
||||
sb.Append("no address");
|
||||
else
|
||||
{
|
||||
sb.Append("address: ");
|
||||
sb.Append(Address);
|
||||
}
|
||||
sb.Append(", ");
|
||||
if (Exception == null)
|
||||
sb.Append("no exception");
|
||||
else
|
||||
{
|
||||
sb.Append("exception: ");
|
||||
sb.Append(Exception.Message);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
//
|
||||
// To add a suppression to this file, right-click the message in the
|
||||
// Code Analysis results, point to "Suppress Message", and click
|
||||
// "In Suppression File".
|
||||
// You do not need to add suppressions to this file manually.
|
||||
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Int32)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte[],System.Int32)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte[],System.Int32,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte[])")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Byte[],System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWol(System.Net.IPAddress,System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Int32)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte[],System.Int32)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte[],System.Int32,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte[])")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Byte[],System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPAddressExtensions.#SendWolAsync(System.Net.IPAddress,System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWol(System.Net.IPEndPoint,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWol(System.Net.IPEndPoint,System.Byte[])")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWol(System.Net.IPEndPoint,System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWol(System.Net.IPEndPoint,System.Byte[],System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWol(System.Net.IPEndPoint,System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWolAsync(System.Net.IPEndPoint,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte,System.Byte)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWolAsync(System.Net.IPEndPoint,System.Byte[])")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWolAsync(System.Net.IPEndPoint,System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWolAsync(System.Net.IPEndPoint,System.Byte[],System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.IPEndPointExtensions.#SendWolAsync(System.Net.IPEndPoint,System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lan")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "System.Net.Topology.INetMask.#GetMaskBytes()")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#op_Equality(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#op_Inequality(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#op_BitwiseAnd(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "System.Net.Topology.SiblingOptions")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "type", Target = "System.Net.SendWol")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPEndPoint)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPEndPoint,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPEndPoint)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWol(System.Net.NetworkInformation.PhysicalAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#op_BitwiseOr(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#BitwiseOr(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPAddress)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPAddress,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wol", Scope = "member", Target = "System.Net.PhysicalAddressExtensions.#SendWolAsync(System.Net.NetworkInformation.PhysicalAddress,System.Net.IPEndPoint,System.Net.SecureOnPassword)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cidr", Scope = "member", Target = "System.Net.Topology.INetMask.#Cidr")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "m", Scope = "member", Target = "System.Net.Topology.NetMask.#.ctor(System.Byte,System.Byte,System.Byte,System.Byte)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n", Scope = "member", Target = "System.Net.Topology.NetMask.#BitwiseAnd(System.Net.Topology.NetMask,System.Net.Topology.NetMask)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "cidr", Scope = "member", Target = "System.Net.Topology.NetMask.#.ctor(System.Int32)")]
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "cidr", Scope = "member", Target = "System.Net.Topology.NetMask.#.ctor(System.Byte)")]
|
||||
@@ -1,166 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Provides extension methods for sending Wake On LAN signals (magic packets) to a specific <see cref="IPAddress"/>.</summary>
|
||||
public static class IPAddressExtensions
|
||||
{
|
||||
private const int DefaultWolPort = 7;
|
||||
#region Wol
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="port">The port to send the packet to.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, byte[] macAddress, int port) => target.SendWol(macAddress, port, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="port">The port to send the packet to.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, byte[] macAddress, int port, SecureOnPassword password)
|
||||
{
|
||||
Net.MagicPacket.Send(new IPEndPoint(target, port), macAddress, password);
|
||||
}
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, byte[] macAddress) => target.SendWol(macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
Net.MagicPacket.Send(new IPEndPoint(target, DefaultWolPort), macAddress, password);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="PhysicalAddress" /> <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, PhysicalAddress macAddress) => target.SendWol(macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="PhysicalAddress" /> <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void SendWol(this IPAddress target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
Net.MagicPacket.Send(new IPEndPoint(target, DefaultWolPort), macAddress, password);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region ARP
|
||||
|
||||
/// <summary>
|
||||
/// Sendet eine Anfrage über das ARP-Protokoll, um eine IP-Adresse in die Physikalische Adresse aufzulösen. Falls sich die physikalische Adresse bereits im Cache des Hosts befindet, wird diese zurückgegeben.
|
||||
/// </summary>
|
||||
/// <param name="destination">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <returns>Eine <see cref="T:System.Net.ArpRequestResult">ArpRequestResult</see>-Instanz, welche die Ergebnisse der Anfrage enthält.</returns>
|
||||
public static ArpRequestResult SendArpRequest(this IPAddress destination) => ArpRequest.Send(destination);
|
||||
|
||||
#endregion
|
||||
#region TAP
|
||||
|
||||
#region Wol
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="port">The port to send the packet to.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, byte[] macAddress, int port) => target.SendWolAsync(macAddress, port, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="port">The port to send the packet to.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, byte[] macAddress, int port, SecureOnPassword password)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(new IPEndPoint(target, port), macAddress, password);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, byte[] macAddress) => target.SendWolAsync(macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(new IPEndPoint(target, DefaultWolPort), macAddress, password);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="PhysicalAddress" /> <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, PhysicalAddress macAddress) => target.SendWolAsync(macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="PhysicalAddress" /> <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPAddress target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(new IPEndPoint(target, DefaultWolPort), macAddress, password);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ARP
|
||||
|
||||
/// <summary>Sends a request via ARP to resolve an IP address to aphysical address. If the physical address is already cached, it's cached value is returned.</summary>
|
||||
/// <param name="destination">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends an ARP request.</returns>
|
||||
public static Task<ArpRequestResult> SendArpRequestAsync(this IPAddress destination) => ArpRequest.SendAsync(destination);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Provides extension methods for sending Wake On LAN signals (magic packets) to a specific <see cref="IPEndPoint"/>.</summary>
|
||||
public static class IPEndPointExtensions
|
||||
{
|
||||
#region Wol
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array macAddress is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
public static void SendWol(this IPEndPoint target, byte[] macAddress)
|
||||
{
|
||||
Net.MagicPacket.Send(target, macAddress);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
public static void SendWol(this IPEndPoint target, PhysicalAddress macAddress)
|
||||
{
|
||||
Net.MagicPacket.Send(target, macAddress);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array macAddress is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
public static void SendWol(this IPEndPoint target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
Net.MagicPacket.Send(target, macAddress, password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sendet ein Wake-On-LAN-Signal an einen Client.
|
||||
/// </summary>
|
||||
/// <param name="target">Der Ziel-IPEndPoint.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
public static void SendWol(this IPEndPoint target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
Net.MagicPacket.Send(target, macAddress, password);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region TAP
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array macAddress is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPEndPoint target, byte[] macAddress)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(target, macAddress);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPEndPoint target, PhysicalAddress macAddress)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(target, macAddress);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array macAddress is not 6.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPEndPoint target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(target, macAddress, password);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this IPEndPoint target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
return Net.MagicPacket.SendAsync(target, macAddress, password);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#define GERMAN
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
internal static class Localization
|
||||
{
|
||||
public const string ArgumentExceptionInvalidMacAddressLength = "Invalid MAC address length.";
|
||||
public const string ArgumentExceptionInvalidPasswordLength = "Invalid password length.";
|
||||
|
||||
public const string ArgumentExceptionIsNotAValidMadAddress = " is not a valid MAC address.";
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Provides methods for sending Wake On LAN signals (magic packets).</summary>
|
||||
public static class MagicPacket
|
||||
{
|
||||
#region Wol
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <exception cref="ArgumentNullException">target is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array macAddress is not 6.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void Send(IPEndPoint target, byte[] macAddress) => Send(target, macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void Send(IPEndPoint target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
if (macAddress == null)
|
||||
throw new ArgumentNullException(nameof(macAddress));
|
||||
|
||||
byte[] passwordBuffer = password?.GetPasswordBytes();
|
||||
byte[] packet = GetWolPacket(macAddress, passwordBuffer);
|
||||
SendPacket(target, packet);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void Send(IPEndPoint target, PhysicalAddress macAddress) => Send(target, macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="SocketException">An error occurred when accessing the socket. See Remarks section of <see cref="UdpClient.Send(byte[], int, IPEndPoint)"/> for more information.</exception>
|
||||
public static void Send(IPEndPoint target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
if (macAddress == null)
|
||||
throw new ArgumentNullException(nameof(macAddress));
|
||||
|
||||
byte[] passwordBuffer = password?.GetPasswordBytes();
|
||||
byte[] packet = GetWolPacket(macAddress.GetAddressBytes(), passwordBuffer);
|
||||
SendPacket(target, packet);
|
||||
}
|
||||
|
||||
private static void SendPacket(IPEndPoint target, byte[] packet)
|
||||
{
|
||||
using (var cl = new UdpClient())
|
||||
cl.Send(packet, packet.Length, target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region TAP
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendAsync(IPEndPoint target, byte[] macAddress) => SendAsync(target, macAddress);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendAsync(IPEndPoint target, byte[] macAddress, SecureOnPassword password)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
if (macAddress == null)
|
||||
throw new ArgumentNullException(nameof(macAddress));
|
||||
|
||||
var passwordBuffer = password?.GetPasswordBytes();
|
||||
var packet = GetWolPacket(macAddress, passwordBuffer);
|
||||
return SendPacketAsync(target, packet);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendAsync(IPEndPoint target, PhysicalAddress macAddress) => SendAsync(target, macAddress, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a client.</summary>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="macAddress">The MAC address of the designated client.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="target"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendAsync(IPEndPoint target, PhysicalAddress macAddress, SecureOnPassword password)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
if (macAddress == null)
|
||||
throw new ArgumentNullException(nameof(macAddress));
|
||||
|
||||
var passwordBuffer = password?.GetPasswordBytes();
|
||||
var p = GetWolPacket(macAddress.GetAddressBytes(), passwordBuffer);
|
||||
return SendPacketAsync(target, p);
|
||||
}
|
||||
|
||||
private static Task SendPacketAsync(IPEndPoint target, byte[] packet)
|
||||
{
|
||||
var cl = new UdpClient();
|
||||
return cl.SendAsync(packet, packet.Length, target).ContinueWith((Task t) => cl.Close());
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region internal
|
||||
|
||||
/// <exception cref="ArgumentNullException"><paramref name="macAddress"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="macAddress"/> is not 6.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array <paramref name="password"/> is not 0 or 6.</exception>
|
||||
private static byte[] GetWolPacket(byte[] macAddress, byte[] password)
|
||||
{
|
||||
if (macAddress == null)
|
||||
throw new ArgumentNullException(nameof(macAddress));
|
||||
if (macAddress.Length != 6)
|
||||
throw new ArgumentException(Localization.ArgumentExceptionInvalidMacAddressLength);
|
||||
|
||||
password = password ?? new byte[0];
|
||||
if (password.Length != 0 && password.Length != 6)
|
||||
throw new ArgumentException(Localization.ArgumentExceptionInvalidPasswordLength);
|
||||
|
||||
var packet = new byte[17 * 6 + password.Length];
|
||||
|
||||
int offset, i;
|
||||
for (offset = 0; offset < 6; ++offset)
|
||||
packet[offset] = 0xFF;
|
||||
|
||||
for (offset = 6; offset < 17 * 6; offset += 6)
|
||||
for (i = 0; i < 6; ++i)
|
||||
packet[i + offset] = macAddress[i];
|
||||
|
||||
if (password.Length > 0)
|
||||
{
|
||||
for (offset = 16 * 6 + 6; offset < (17 * 6 + password.Length); offset += 6)
|
||||
for (i = 0; i < 6; ++i)
|
||||
packet[i + offset] = password[i];
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
internal static class NativeMethods
|
||||
{
|
||||
private const string IphlpApi = "iphlpapi.dll";
|
||||
|
||||
[DllImport(IphlpApi, ExactSpelling = true)]
|
||||
[SecurityCritical]
|
||||
internal static extern int SendARP(int destinationIp, int sourceIp, byte[] macAddress, ref int physicalAddrLength);
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Provides extension methods for sending Wake On LAN signals (magic packets) using a specific <see cref="PhysicalAddress"/>.</summary>
|
||||
public static class PhysicalAddressExtensions
|
||||
{
|
||||
#region Wol
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to the broadcast IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static void SendWol(this PhysicalAddress address) => address.SendWol(IPAddress.Broadcast, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static void SendWol(this PhysicalAddress address, IPAddress target) => address.SendWol(target, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static void SendWol(this PhysicalAddress address, IPAddress target, SecureOnPassword password)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
target.SendWol(address.GetAddressBytes(), password);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP end point with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static void SendWol(this PhysicalAddress address, IPEndPoint target) => address.SendWol(target, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP end point with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static void SendWol(this PhysicalAddress address, IPEndPoint target, SecureOnPassword password)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
target.SendWol(address.GetAddressBytes(), password);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region TAP
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to the broadcast IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
public static Task SendWolAsync(this PhysicalAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
return IPAddress.Broadcast.SendWolAsync(address.GetAddressBytes());
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this PhysicalAddress address, IPAddress target) => address.SendWolAsync(target, null);
|
||||
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP address with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPAddress"/>.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this PhysicalAddress address, IPAddress target, SecureOnPassword password)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
return target.SendWolAsync(address.GetAddressBytes(), password);
|
||||
}
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP end point with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this PhysicalAddress address, IPEndPoint target) => address.SendWolAsync(target, null);
|
||||
|
||||
/// <summary>Sends a Wake On LAN signal (magic packet) to a specific IP end point with the physical address.</summary>
|
||||
/// <param name="address">The instance of the physical address that should be used in the magic packet.</param>
|
||||
/// <param name="target">Destination <see cref="IPEndPoint"/>.</param>
|
||||
/// <param name="password">The SecureOn password of the client.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>An asynchronous <see cref="Task"/> which sends a Wake On LAN signal (magic packet) to a client.</returns>
|
||||
public static Task SendWolAsync(this PhysicalAddress address, IPEndPoint target, SecureOnPassword password)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
return target.SendWolAsync(address.GetAddressBytes(), password);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region common
|
||||
|
||||
/// <summary>Gets the type (<see cref="PhysicalAddressAdministrator"/>) of the <see cref="PhysicalAddress" />.</summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>The <see cref="PhysicalAddressType" /> of the physical addess (MAC address).</returns>
|
||||
public static PhysicalAddressType GetAddressType(this PhysicalAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
var bytes = address.GetAddressBytes();
|
||||
if (bytes == null || bytes.Length < 1)
|
||||
throw new ArgumentException($"Invalid {nameof(address)}.");
|
||||
return (bytes[0] & 0x1) == 0 ? PhysicalAddressType.Unicast : PhysicalAddressType.Multicast;
|
||||
}
|
||||
|
||||
/// <summary>Gets the administrator (<see cref="PhysicalAddressAdministrator"/>) of the <see cref="PhysicalAddress" />.</summary>
|
||||
/// <param name="address">The address.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="address"/> is null.</exception>
|
||||
/// <returns>The <see cref="PhysicalAddressAdministrator" /> of the physical addess (MAC address).</returns>
|
||||
public static PhysicalAddressAdministrator GetAddressAdministrator(this PhysicalAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
|
||||
var bytes = address.GetAddressBytes();
|
||||
if (bytes == null || bytes.Length < 1)
|
||||
throw new ArgumentException($"Invalid {nameof(address)}.");
|
||||
return (bytes[0] & 0x2) == 0 ? PhysicalAddressAdministrator.Global : PhysicalAddressAdministrator.Local;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// Der Administrator der physikalischen Adresse gibt an, ob die Adresse globally unique oder local administrated ist.
|
||||
/// </summary>
|
||||
public enum PhysicalAddressAdministrator
|
||||
{
|
||||
/// <summary>
|
||||
/// Die Adresse ist global einzigartig (nach der OUI).
|
||||
/// </summary>
|
||||
Global,
|
||||
|
||||
/// <summary>
|
||||
/// Die Adresse ist lokal administriert.
|
||||
/// </summary>
|
||||
Local
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Der Typ der physikalischen Adresse gibt an, ob es um eine UNicast oder Multicast-Adresse handelt.
|
||||
/// </summary>
|
||||
public enum PhysicalAddressType
|
||||
{
|
||||
/// <summary>
|
||||
/// Bezeichnet eine Unicast-Adresse.
|
||||
/// </summary>
|
||||
Unicast,
|
||||
|
||||
/// <summary>
|
||||
/// Bezeichnet eine Multicast-Adresse
|
||||
/// </summary>
|
||||
Multicast
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
|
||||
[assembly: InternalsVisibleTo("WakeOnLan.Tests")]
|
||||
@@ -1,95 +0,0 @@
|
||||
namespace System.Net
|
||||
{
|
||||
/// <summary>Provides a SecureOn password.</summary>
|
||||
[Serializable]
|
||||
public sealed class SecureOnPassword
|
||||
{
|
||||
private readonly byte[] _password;
|
||||
|
||||
/// <summary>Initializes a new instance of <see cref="SecureOnPassword"/> with the given password.</summary>
|
||||
/// <param name="password">The password as <see cref="Byte"/> array.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The length of the <see cref="T:System.Byte" /> array password is not 6.</exception>
|
||||
public SecureOnPassword(byte[] password)
|
||||
{
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
if (password.Length != 6)
|
||||
throw new ArgumentException(Localization.ArgumentExceptionInvalidPasswordLength);
|
||||
_password = password;
|
||||
}
|
||||
|
||||
/// <summary>Gets the buffer of the password.</summary>
|
||||
public byte[] GetPasswordBytes()
|
||||
{
|
||||
if (_password == null)
|
||||
return null;
|
||||
var buffer = new byte[_password.Length];
|
||||
Array.Copy(_password, buffer, 0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of <see cref="SecureOnPassword"/> with the given password.</summary>
|
||||
/// <param name="password">The password as <see cref="String"/>.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <remarks>Uses <see cref="Text.Encoding.Default" /> as encoding.</remarks>
|
||||
public SecureOnPassword(string password)
|
||||
: this(password, Text.Encoding.Default)
|
||||
{ }
|
||||
|
||||
|
||||
/// <summary>Initializes a new instance of <see cref="SecureOnPassword"/> with the given password.</summary>
|
||||
/// <param name="password">The password as <see cref="String"/>.</param>
|
||||
/// <param name="encoding">The <see cref="Text.Encoding"/> instance to use for the password.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="encoding"/> is null.</exception>
|
||||
/// <exception cref="ArgumentException">The <see cref="Byte"/> array wich is created using the password has more elements than 6.</exception>
|
||||
public SecureOnPassword(string password, Text.Encoding encoding)
|
||||
{
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
if (encoding == null)
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
_password = new byte[6];
|
||||
|
||||
var bytes = encoding.GetBytes(password);
|
||||
if (bytes.Length > 6)
|
||||
throw new ArgumentException(Localization.ArgumentExceptionInvalidPasswordLength);
|
||||
|
||||
_password = new byte[6];
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
_password[i] = bytes[i];
|
||||
if (bytes.Length < 6)
|
||||
{
|
||||
for (int i = bytes.Length - 1; i < 6; i++)
|
||||
_password[i] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Converts the <see cref="SecureOnPassword"/> to dash notation.</summary>
|
||||
/// <returns>A <see cref="String"/> representing the <see cref="SecureOnPassword"/> as dash notation.</returns>
|
||||
public override string ToString() => ToString("X2");
|
||||
|
||||
/// <summary>Converts the <see cref="SecureOnPassword"/> to dash notation.</summary>
|
||||
/// <returns>A <see cref="String"/> representing the <see cref="SecureOnPassword"/> as dash notation.</returns>
|
||||
private string ToString(string format)
|
||||
{
|
||||
var f = new string[6];
|
||||
for (int i = 0; i < f.Length; i++)
|
||||
f[i] = _password[i].ToString(format);
|
||||
return string.Join("-", f);
|
||||
}
|
||||
|
||||
/// <summary>Converts the <see cref="SecureOnPassword"/> to dash notation.</summary>
|
||||
/// <returns>A <see cref="String"/> representing the <see cref="SecureOnPassword"/> as dash notation.</returns>
|
||||
public string ToString(IFormatProvider format)
|
||||
{
|
||||
var f = new string[6];
|
||||
for (int i = 0; i < f.Length; i++)
|
||||
f[i] = _password[i].ToString(format);
|
||||
return string.Join("-", f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
internal static class ByteArrayExtensions
|
||||
{
|
||||
internal static IEnumerable<bool> ToBitStream(this byte[] bytes, bool fromLeft)
|
||||
{
|
||||
if (bytes == null)
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
|
||||
if (fromLeft)
|
||||
{
|
||||
for (int i = 0; i < bytes.Length; ++i)
|
||||
{
|
||||
byte tmp = bytes[i].ReverseBits();
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
yield return (tmp & 1) == 1;
|
||||
tmp >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = bytes.Length - 1; i >= 0; --i)
|
||||
{
|
||||
byte tmp = bytes[i];
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
yield return (tmp & 1) == 1;
|
||||
tmp >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountFromSide(byte[] bits, bool value, bool fromleft)
|
||||
{
|
||||
int counter = 0;
|
||||
var str = bits.ToBitStream(fromleft);
|
||||
foreach (var bit in str)
|
||||
{
|
||||
if (bit == value)
|
||||
++counter;
|
||||
else return counter;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
internal static int CountFromLeft(this byte[] bits, bool value)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
return CountFromSide(bits, value, true);
|
||||
}
|
||||
|
||||
internal static int CountFromRight(this byte[] bits, bool value)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
return CountFromSide(bits, value, false);
|
||||
}
|
||||
|
||||
internal static string ToBinaryString(this byte[] bits, char separator)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
|
||||
const int radix = 2;
|
||||
const int padding = 8;
|
||||
const char paddingChar = '0';
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(Convert.ToString(bits[0], radix).PadLeft(padding, paddingChar)).Append(separator);
|
||||
sb.Append(Convert.ToString(bits[1], radix).PadLeft(padding, paddingChar)).Append(separator);
|
||||
sb.Append(Convert.ToString(bits[2], radix).PadLeft(padding, paddingChar)).Append(separator);
|
||||
sb.Append(Convert.ToString(bits[3], radix).PadLeft(padding, paddingChar));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal static string ToBinaryString(this byte[] bits)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
|
||||
const int radix = 2;
|
||||
const int padding = 8;
|
||||
const char paddingChar = '0';
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(Convert.ToString(bits[0], radix).PadLeft(padding, paddingChar));
|
||||
sb.Append(Convert.ToString(bits[1], radix).PadLeft(padding, paddingChar));
|
||||
sb.Append(Convert.ToString(bits[2], radix).PadLeft(padding, paddingChar));
|
||||
sb.Append(Convert.ToString(bits[3], radix).PadLeft(padding, paddingChar));
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal static bool RepresentsValidNetMask(this byte[] bits)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
|
||||
int fromLeft = bits.CountFromLeft(true);
|
||||
int fromRight = bits.CountFromRight(false);
|
||||
|
||||
// Sum of all counted indexes schloud be equal the whole length
|
||||
return (fromLeft + fromRight) == (8 * NetMask.MaskLength);
|
||||
}
|
||||
|
||||
internal static byte[] And(this byte[] b1, byte[] b2)
|
||||
{
|
||||
if (b1 == null)
|
||||
throw new ArgumentNullException(nameof(b1));
|
||||
if (b2 == null)
|
||||
throw new ArgumentNullException(nameof(b2));
|
||||
|
||||
if (b1.Length == 1 && b2.Length == 1)
|
||||
{
|
||||
int ib1 = b1[0];
|
||||
int ib2 = b2[0];
|
||||
return new[] { (byte)(ib1 & ib2) };
|
||||
}
|
||||
if (b1.Length == 2 && b2.Length == 2)
|
||||
{
|
||||
var sb1 = BitConverter.ToInt16(b1, 0);
|
||||
var sb2 = BitConverter.ToInt16(b2, 0);
|
||||
return BitConverter.GetBytes((short)(sb1 & sb2));
|
||||
}
|
||||
if (b1.Length == 4 && b2.Length == 4)
|
||||
{
|
||||
var ib1 = BitConverter.ToInt32(b1, 0);
|
||||
var ib2 = BitConverter.ToInt32(b2, 0);
|
||||
return BitConverter.GetBytes(ib1 & ib2);
|
||||
}
|
||||
if (b1.Length != b2.Length)
|
||||
{
|
||||
// Or maybe throw exception?
|
||||
|
||||
int maxIndex = Math.Max(b1.Length, b2.Length);
|
||||
byte[] biggerArray = b1.Length > b2.Length ? b1 : b2;
|
||||
byte[] smallerArray = b1.Length <= b2.Length ? b1 : b2;
|
||||
|
||||
var paddedArray = new byte[maxIndex];
|
||||
|
||||
Buffer.BlockCopy(smallerArray, 0, paddedArray, 0, smallerArray.Length);
|
||||
|
||||
Debug.Assert(biggerArray.Length == paddedArray.Length);
|
||||
|
||||
return And(biggerArray, paddedArray);
|
||||
}
|
||||
|
||||
var targetIndex = b1.Length;
|
||||
var andedArray = new byte[targetIndex];
|
||||
Buffer.BlockCopy(b1, 0, andedArray, 0, andedArray.Length);
|
||||
|
||||
for (int i = 0; i < targetIndex; ++i)
|
||||
andedArray[i] &= b2[i];
|
||||
|
||||
return andedArray;
|
||||
}
|
||||
|
||||
internal static byte[] Or(this byte[] b1, byte[] b2)
|
||||
{
|
||||
if (b1 == null)
|
||||
throw new ArgumentNullException(nameof(b1));
|
||||
if (b2 == null)
|
||||
throw new ArgumentNullException(nameof(b2));
|
||||
|
||||
if (b1.Length == 1 && b2.Length == 1)
|
||||
{
|
||||
int ib1 = b1[0];
|
||||
int ib2 = b2[0];
|
||||
return new[] { (byte)(ib1 | ib2) };
|
||||
}
|
||||
if (b1.Length == 2 && b2.Length == 2)
|
||||
{
|
||||
var sb1 = BitConverter.ToInt16(b1, 0);
|
||||
var sb2 = BitConverter.ToInt16(b2, 0);
|
||||
return BitConverter.GetBytes((short)(sb1 | sb2));
|
||||
}
|
||||
if (b1.Length == 4 && b2.Length == 4)
|
||||
{
|
||||
var ib1 = BitConverter.ToInt32(b1, 0);
|
||||
var ib2 = BitConverter.ToInt32(b2, 0);
|
||||
return BitConverter.GetBytes(ib1 | ib2);
|
||||
}
|
||||
if (b1.Length != b2.Length)
|
||||
{
|
||||
// Or maybe throw exception?
|
||||
|
||||
int maxIndex = Math.Max(b1.Length, b2.Length);
|
||||
byte[] biggerArray = b1.Length > b2.Length ? b1 : b2;
|
||||
byte[] smallerArray = b1.Length <= b2.Length ? b1 : b2;
|
||||
|
||||
var paddedArray = new byte[maxIndex];
|
||||
|
||||
Buffer.BlockCopy(smallerArray, 0, paddedArray, 0, smallerArray.Length);
|
||||
|
||||
Debug.Assert(biggerArray.Length == paddedArray.Length);
|
||||
|
||||
return Or(biggerArray, paddedArray);
|
||||
}
|
||||
|
||||
var targetIndex = b1.Length;
|
||||
var oredArray = new byte[targetIndex];
|
||||
Buffer.BlockCopy(b1, 0, oredArray, 0, oredArray.Length);
|
||||
|
||||
for (int i = 0; i < targetIndex; ++i)
|
||||
oredArray[i] |= b2[i];
|
||||
|
||||
return oredArray;
|
||||
}
|
||||
|
||||
internal static byte[] Xor(this byte[] b1, byte[] b2)
|
||||
{
|
||||
if (b1 == null)
|
||||
throw new ArgumentNullException(nameof(b1));
|
||||
if (b2 == null)
|
||||
throw new ArgumentNullException(nameof(b2));
|
||||
|
||||
// TODO: Testing
|
||||
|
||||
if (b1.Length == 1 && b2.Length == 1)
|
||||
{
|
||||
int ib1 = b1[0];
|
||||
int ib2 = b2[0];
|
||||
return new[] { (byte)(ib1 ^ ib2) };
|
||||
}
|
||||
if (b1.Length == 2 && b2.Length == 2)
|
||||
{
|
||||
var sb1 = BitConverter.ToInt16(b1, 0);
|
||||
var sb2 = BitConverter.ToInt16(b2, 0);
|
||||
return BitConverter.GetBytes((short)(sb1 ^ sb2));
|
||||
}
|
||||
if (b1.Length == 4 && b2.Length == 4)
|
||||
{
|
||||
var ib1 = BitConverter.ToInt32(b1, 0);
|
||||
var ib2 = BitConverter.ToInt32(b2, 0);
|
||||
return BitConverter.GetBytes(ib1 ^ ib2);
|
||||
}
|
||||
if (b1.Length != b2.Length)
|
||||
{
|
||||
// Or maybe throw exception?
|
||||
|
||||
int maxIndex = Math.Max(b1.Length, b2.Length);
|
||||
byte[] biggerArray = b1.Length > b2.Length ? b1 : b2;
|
||||
byte[] smallerArray = b1.Length <= b2.Length ? b1 : b2;
|
||||
|
||||
var paddedArray = new byte[maxIndex];
|
||||
|
||||
Buffer.BlockCopy(smallerArray, 0, paddedArray, 0, smallerArray.Length);
|
||||
|
||||
Debug.Assert(biggerArray.Length == paddedArray.Length);
|
||||
|
||||
return Xor(biggerArray, paddedArray);
|
||||
}
|
||||
|
||||
var targetIndex = b1.Length;
|
||||
var xoredArray = new byte[targetIndex];
|
||||
Buffer.BlockCopy(b1, 0, xoredArray, 0, xoredArray.Length);
|
||||
|
||||
for (int i = 0; i < targetIndex; ++i)
|
||||
xoredArray[i] ^= b2[i];
|
||||
|
||||
return xoredArray;
|
||||
}
|
||||
|
||||
internal static byte[] Not(this byte[] bits)
|
||||
{
|
||||
if (bits == null)
|
||||
throw new ArgumentNullException(nameof(bits));
|
||||
|
||||
if (bits.Length == 4)
|
||||
{
|
||||
var i = BitConverter.ToInt32(bits, 0);
|
||||
return BitConverter.GetBytes(~i);
|
||||
}
|
||||
var newBytes = new byte[bits.Length];
|
||||
for (int i = 0; i < newBytes.Length; ++i)
|
||||
newBytes[i] = unchecked((byte)(~bits[i]));
|
||||
return newBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
internal static class ByteExtensions
|
||||
{
|
||||
public static byte[] BitReverseTable = {
|
||||
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
|
||||
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
|
||||
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
|
||||
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
|
||||
0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
|
||||
0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
|
||||
0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
|
||||
0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
|
||||
0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
|
||||
0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
|
||||
0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
|
||||
0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
|
||||
0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
|
||||
0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
|
||||
0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
|
||||
0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
|
||||
0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
|
||||
0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
|
||||
0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
|
||||
0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
|
||||
0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
|
||||
0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
|
||||
0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
|
||||
0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
|
||||
0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
|
||||
0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
|
||||
0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
|
||||
0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
|
||||
0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
|
||||
0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
|
||||
0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
|
||||
0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
|
||||
};
|
||||
|
||||
public static byte ReverseBits(this byte toReverse) => BitReverseTable[toReverse];
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
/// <summary>Provides an interface for IP net masks.</summary>
|
||||
public interface INetMask
|
||||
{
|
||||
/// <summary>Gets the length of the net mask in bits.</summary>
|
||||
int AddressLength { get; }
|
||||
|
||||
/// <summary>Gets the amount of set bits from the left side (used in CIDR-Notation of net masks).</summary>
|
||||
int Cidr { get; }
|
||||
|
||||
/// <summary>Gets the bits of the net mask instance as an BitArray object instance.</summary>
|
||||
/// <returns>The bits of the net mask instance as an BitArray object instance</returns>
|
||||
byte[] GetMaskBytes();
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
/// <summary>Provides extension methods for the <see cref="T:System.Net.IPAddress"/>.</summary>
|
||||
public static class IPAddressExtensions
|
||||
{
|
||||
private const string OnlyIPv4Supported = "Only IPv4 is currently supported";
|
||||
|
||||
/// <summary>Enumerates through the siblings of an <see cref="T:System.Net.IPAddress"/> in a network. Compliant to RFC 950 (2^n-2).</summary>
|
||||
/// <param name="address">The address</param>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
public static IEnumerable<IPAddress> GetSiblings(this IPAddress address, NetMask mask)
|
||||
{
|
||||
return GetSiblings(address, mask, SiblingOptions.ExcludeUnusable);
|
||||
}
|
||||
|
||||
/// <summary>Enumerates through the siblings of an <see cref="T:System.Net.IPAddress"/> in a network.</summary>
|
||||
/// <param name="address">The address</param>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <param name="options">Options which addresses to include an which not</param>
|
||||
public static IEnumerable<IPAddress> GetSiblings(this IPAddress address, NetMask mask, SiblingOptions options)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException(nameof(mask));
|
||||
if (address.AddressFamily != Sockets.AddressFamily.InterNetwork)
|
||||
throw new NotSupportedException(OnlyIPv4Supported);
|
||||
|
||||
bool includeSelf = BitHelper.IsOptionSet(options, SiblingOptions.IncludeSelf);
|
||||
bool includeBroadcast = BitHelper.IsOptionSet(options, SiblingOptions.IncludeBroadcast);
|
||||
bool includeNetworkIdentifier = BitHelper.IsOptionSet(options, SiblingOptions.IncludeNetworkIdentifier);
|
||||
|
||||
bool alreadyReturnedSelf = false;
|
||||
|
||||
var netPrefix = address.GetNetworkPrefix(mask);
|
||||
|
||||
if (includeNetworkIdentifier)
|
||||
{
|
||||
netPrefix = address.GetNetworkPrefix(mask);
|
||||
if (netPrefix.Equals(address))
|
||||
alreadyReturnedSelf = true;
|
||||
yield return netPrefix;
|
||||
}
|
||||
|
||||
var selfAddressBytes = address.GetAddressBytes();
|
||||
|
||||
var netPrefixBytes = netPrefix.GetAddressBytes();
|
||||
|
||||
int cidr = mask.Cidr;
|
||||
uint maxHosts = 0xFFFFFFFF;
|
||||
|
||||
if (cidr > 0)
|
||||
maxHosts = (uint)(1 << (8 * NetMask.MaskLength - cidr)) - 1;
|
||||
|
||||
var hostBytes = new byte[NetMask.MaskLength];
|
||||
for (int hostPart = 1; hostPart < maxHosts; ++hostPart)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
hostBytes[0] = (byte)(hostPart >> 24);
|
||||
hostBytes[1] = (byte)(hostPart >> 16);
|
||||
hostBytes[2] = (byte)(hostPart >> 8);
|
||||
hostBytes[3] = (byte)(hostPart >> 0);
|
||||
}
|
||||
|
||||
Debug.WriteLine("HostPart: " + hostPart.ToString("X2").PadLeft(8, '0') + " (" + BitConverter.ToString(hostBytes) + ")");
|
||||
|
||||
var nextIpBytes = netPrefixBytes.Or(hostBytes);
|
||||
var nextIp = new IPAddress(nextIpBytes);
|
||||
|
||||
if (!alreadyReturnedSelf)
|
||||
{
|
||||
if (includeSelf)
|
||||
{
|
||||
if (nextIpBytes[0] == selfAddressBytes[0]
|
||||
&& nextIpBytes[1] == selfAddressBytes[1]
|
||||
&& nextIpBytes[2] == selfAddressBytes[2]
|
||||
&& nextIpBytes[3] == selfAddressBytes[3])
|
||||
alreadyReturnedSelf = true;
|
||||
yield return nextIp;
|
||||
}
|
||||
else if (nextIpBytes[0] != selfAddressBytes[0]
|
||||
|| nextIpBytes[1] != selfAddressBytes[1]
|
||||
|| nextIpBytes[2] != selfAddressBytes[2]
|
||||
|| nextIpBytes[3] != selfAddressBytes[3])
|
||||
yield return nextIp;
|
||||
}
|
||||
else
|
||||
yield return nextIp;
|
||||
}
|
||||
|
||||
if (includeBroadcast)
|
||||
{
|
||||
var broadcastAddress = address.GetBroadcastAddress(mask);
|
||||
if (!address.Equals(broadcastAddress) || (address.Equals(broadcastAddress) && !alreadyReturnedSelf))
|
||||
yield return broadcastAddress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the network prefix of an <see cref="T:System.Net.IPAddress"/>.</summary>
|
||||
/// <param name="address">The address</param>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <returns>The network prefix of an <see cref="T:System.Net.IPAddress"/></returns>
|
||||
public static IPAddress GetNetworkPrefix(this IPAddress address, NetMask mask)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException(nameof(mask));
|
||||
|
||||
if (address.AddressFamily != Sockets.AddressFamily.InterNetwork)
|
||||
throw new NotSupportedException(OnlyIPv4Supported);
|
||||
|
||||
return mask & address;
|
||||
}
|
||||
|
||||
/// <summary>Gets the broadcast address of an <see cref="T:System.Net.IPAddress"/>.</summary>
|
||||
/// <param name="address">The address</param>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <returns>The broadcast address of an <see cref="T:System.Net.IPAddress"/></returns>
|
||||
public static IPAddress GetBroadcastAddress(this IPAddress address, NetMask mask)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException("address");
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException("mask");
|
||||
|
||||
if (address.AddressFamily != Sockets.AddressFamily.InterNetwork)
|
||||
throw new NotSupportedException(OnlyIPv4Supported);
|
||||
|
||||
// TODO: Test
|
||||
|
||||
var ipBytes = address.GetAddressBytes();
|
||||
var notMaskBytes = mask.GetMaskBytes().Not();
|
||||
|
||||
var broadcastAddressBytes = notMaskBytes.Or(ipBytes);
|
||||
return new IPAddress(broadcastAddressBytes);
|
||||
}
|
||||
|
||||
/// <summary>Gets the host identifier (rest) an <see cref="T:System.Net.IPAddress"/>.</summary>
|
||||
/// <param name="address">The address</param>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <returns>The host identifier (rest) an <see cref="T:System.Net.IPAddress"/></returns>
|
||||
public static IPAddress GetHostIdentifier(this IPAddress address, NetMask mask)
|
||||
{
|
||||
if (address == null)
|
||||
throw new ArgumentNullException(nameof(address));
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException(nameof(mask));
|
||||
if (address.AddressFamily != Sockets.AddressFamily.InterNetwork)
|
||||
throw new NotSupportedException(OnlyIPv4Supported);
|
||||
|
||||
var maskBits = mask.GetMaskBytes();
|
||||
var ipBits = address.GetAddressBytes();
|
||||
|
||||
// ~Mask & IP
|
||||
var retVal = maskBits.Not().And(ipBits);
|
||||
var bytes = new byte[NetMask.MaskLength];
|
||||
Buffer.BlockCopy(retVal, 0, bytes, 0, bytes.Length);
|
||||
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
// TODO: Use Span<T> when available (instead of byte arrays)
|
||||
/// <summary>Represents an IPv4 net mask.</summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct NetMask : INetMask, IEquatable<NetMask>
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
private readonly uint _mask;
|
||||
|
||||
#region byte-wise fields
|
||||
|
||||
// We are assuming here, that the byte-ordering is like on a normal x86 system
|
||||
|
||||
[FieldOffset(3)]
|
||||
private readonly byte _maskB0;
|
||||
[FieldOffset(2)]
|
||||
private readonly byte _maskB1;
|
||||
[FieldOffset(1)]
|
||||
private readonly byte _maskB2;
|
||||
[FieldOffset(0)]
|
||||
private readonly byte _maskB3;
|
||||
|
||||
#endregion
|
||||
|
||||
internal const int MaskLength = 4;
|
||||
|
||||
/// <summary>Represents an empty IPv4 NetMask (all bits set to 0).</summary>
|
||||
public static NetMask Empty { get; } = new NetMask();
|
||||
|
||||
/// <summary>Gets the length of the net mask in bits.</summary>
|
||||
public int AddressLength => MaskLength * 8;
|
||||
|
||||
/// <summary>Gets the amount of set bits from the left side (used in CIDR-Notation of net masks).</summary>
|
||||
public int Cidr => GetCidr(_mask);
|
||||
|
||||
#region Ctors
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="T:System.Net.Topology.NetMask"/> cloning an existing instance of <see cref="T:System.Net.Topology.NetMask"/>.</summary>
|
||||
public NetMask(NetMask mask)
|
||||
{
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException(nameof(mask));
|
||||
_maskB0 = _maskB1 = _maskB2 = _maskB3 = 0;
|
||||
_mask = mask._mask;
|
||||
}
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="T:System.Net.Topology.NetMask"/> from an array of <see cref="System.Byte"/>.</summary>
|
||||
public NetMask(byte[] value)
|
||||
{
|
||||
_maskB0 = _maskB1 = _maskB2 = _maskB3 = 0;
|
||||
_mask = 0;
|
||||
if (value == null || value.Length == 0) // maybe throw ArgumentNullException?
|
||||
return;
|
||||
|
||||
if (value.Length != MaskLength)
|
||||
throw new ArgumentException("Invalid mask length.");
|
||||
|
||||
CheckMaskBytes(value); // check if passed mask are a valid mask. if not, throw Exception
|
||||
_maskB0 = value[0];
|
||||
_maskB1 = value[1];
|
||||
_maskB2 = value[2];
|
||||
_maskB3 = value[3];
|
||||
}
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="T:System.Net.Topology.NetMask"/> from a given <see cref="T:System.Net.IPAddress"/>.</summary>
|
||||
/// <param name="address">The IPv4 address.</param>
|
||||
public NetMask(IPAddress address)
|
||||
: this(address == null ? null : address.GetAddressBytes())
|
||||
{ }
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="T:System.Net.Topology.NetMask"/>.</summary>
|
||||
/// <param name="m0">The first byte.</param>
|
||||
/// <param name="m1">The second byte.</param>
|
||||
/// <param name="m2">The third byte.</param>
|
||||
/// <param name="m3">The fourth byte.</param>
|
||||
public NetMask(byte m0, byte m1, byte m2, byte m3)
|
||||
: this(new[] { m0, m1, m2, m3 })
|
||||
{ }
|
||||
|
||||
/// <summary>Creates a new instance of <see cref="T:System.Net.Topology.NetMask"/>.</summary>
|
||||
/// <param name="cidr">The mask represented by the CIDR notation integer.</param>
|
||||
public NetMask(byte cidr)
|
||||
{
|
||||
// maybe change parameter type interpretation to CIDR?
|
||||
if (cidr > MaskLength * 8)
|
||||
throw new ArgumentException("Invalid CIDR length");
|
||||
_maskB0 = _maskB1 = _maskB2 = _maskB3 = 0; // to keep init checks happy
|
||||
_mask = GetUIntFromCidrValue(cidr);
|
||||
}
|
||||
|
||||
private NetMask(uint ipv4Mask)
|
||||
{
|
||||
_maskB0 = _maskB1 = _maskB2 = _maskB3 = 0; // to keep init checks happy
|
||||
_mask = ipv4Mask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static void CheckMaskBytes(byte[] bytes)
|
||||
{
|
||||
if (!bytes.RepresentsValidNetMask())
|
||||
throw new ArgumentException("The passed mask do not represent a valid net mask.");
|
||||
}
|
||||
|
||||
/// <summary>Gets the bits of the net mask instance as an BitArray object instance.</summary>
|
||||
/// <returns>The bits of the net mask instance as an BitArray object instance.</returns>
|
||||
public byte[] GetMaskBytes() => new[] { _maskB0, _maskB1, _maskB2, _maskB3 };
|
||||
|
||||
private static int GetCidr(uint mask)
|
||||
{
|
||||
Debug.Assert(sizeof(uint) == MaskLength);
|
||||
return mask.CountOnesFromLeft();
|
||||
}
|
||||
|
||||
/// <summary>Extends the current <see cref="T:System.Net.Topology.NetMask"/> instance by a given value (CIDR-wise).</summary>
|
||||
/// <param name="mask">The mask to use as a reference.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <remarks>Because <see cref="T:System.Net.Topology.NetMask"/> is a reference type, this method is static. If it were not like this, you could change the value of <see cref="T:System.Net.Topology.NetMask"/>.Empty, for example.</remarks>
|
||||
public static NetMask Extend(NetMask mask, int value)
|
||||
{
|
||||
int currentCidr = mask.Cidr;
|
||||
if (currentCidr >= MaskLength * 8)
|
||||
return new NetMask(mask);
|
||||
if (currentCidr <= 0)
|
||||
currentCidr = 0;
|
||||
|
||||
int newLength = Math.Max(Math.Min(currentCidr + value, 32), 0);
|
||||
|
||||
var m = BytesFromCidrValue(newLength);
|
||||
|
||||
return new NetMask(m);
|
||||
}
|
||||
|
||||
/// <summary>Abbreviates the current <see cref="T:System.Net.Topology.NetMask"/> instance by a given value (CIDR-wise).</summary>
|
||||
/// <param name="mask">The mask to use as a reference.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <remarks>Because <see cref="T:System.Net.Topology.NetMask"/> is a reference type, this method is static. If it were not like this, you could change the value of <see cref="T:System.Net.Topology.NetMask"/>.Empty, for example.</remarks>
|
||||
public static NetMask Abbreviate(NetMask mask, int value)
|
||||
{
|
||||
int currentCidr = mask.Cidr;
|
||||
if (currentCidr < 1)
|
||||
return new NetMask(mask);
|
||||
if (currentCidr >= MaskLength * 8)
|
||||
currentCidr = MaskLength * 8;
|
||||
|
||||
int newLength = Math.Max(Math.Min(currentCidr - value, 32), 0);
|
||||
|
||||
var m = BytesFromCidrValue(newLength);
|
||||
return new NetMask(m);
|
||||
}
|
||||
|
||||
/// <summary>Returns a value indicating whether the given array of <see cref="T:System.Byte"/> represents a valid net mask.</summary>
|
||||
/// <returns>True if the given array of <see cref="T:System.Byte"/> represents a valid net mask, otherwise false.</returns>
|
||||
public static bool GetIsValidNetMask(byte[] mask) => mask.RepresentsValidNetMask();
|
||||
|
||||
// TODO: Testing(!)
|
||||
[Obsolete]
|
||||
private static byte[] BytesFromCidrValue(int cidr)
|
||||
{
|
||||
int target = MaskLength * 8 - cidr;
|
||||
int mask = 0;
|
||||
for (int i = 0; i < target; ++i)
|
||||
{
|
||||
mask >>= 1;
|
||||
mask |= unchecked((int)0x80000000);
|
||||
}
|
||||
var bytes = BitConverter.GetBytes(~mask);
|
||||
return new[] {
|
||||
bytes[0].ReverseBits(),
|
||||
bytes[1].ReverseBits(),
|
||||
bytes[2].ReverseBits(),
|
||||
bytes[3].ReverseBits()
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Testing(!)
|
||||
private static uint GetUIntFromCidrValue(int cidr) => UIntExtensions.CreateWithOnesFromLeft(cidr);
|
||||
|
||||
#region Operators
|
||||
|
||||
#region Equality
|
||||
|
||||
/// <summary>Returns a other indicating whether two instances of <see cref="T:System.Net.Topology.NetMask" /> are equal.</summary>
|
||||
/// <param name="n1">The first other to compare.</param>
|
||||
/// <param name="n2">The second other to compare.</param>
|
||||
/// <returns>true if <paramref name="n1" /> and <paramref name="n2" /> are equal; otherwise, false.</returns>
|
||||
public static bool operator ==(NetMask n1, NetMask n2)
|
||||
{
|
||||
if (ReferenceEquals(n1, n2))
|
||||
return true;
|
||||
if (((object)n1 == null) || ((object)n2 == null))
|
||||
return false;
|
||||
return n1.Equals(n2);
|
||||
}
|
||||
|
||||
/// <summary>Returns a other indicating whether two instances of <see cref="T:System.Net.Topology.NetMask" /> are not equal.</summary>
|
||||
/// <param name="n1">The first other to compare. </param>
|
||||
/// <param name="n2">The second other to compare. </param>
|
||||
/// <returns>true if <paramref name="n1" /> and <paramref name="n2" /> are not equal; otherwise, false.</returns>
|
||||
public static bool operator !=(NetMask n1, NetMask n2) => !(n1 == n2); // Problem solved
|
||||
|
||||
#endregion
|
||||
#region And
|
||||
|
||||
/// <summary>Bitwise combines a <see cref="T:System.Net.Topology.NetMask" /> instance and an <see cref="T:System.Net.IPAddress"/> the AND operation.</summary>
|
||||
/// <param name="mask">The net mask.</param>
|
||||
/// <param name="address">The IPAddress.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static IPAddress operator &(IPAddress address, NetMask mask) => mask & address;
|
||||
|
||||
/// <summary>Bitwise combines a <see cref="T:System.Net.Topology.NetMask" /> instance and an <see cref="T:System.Net.IPAddress"/> the AND operation.</summary>
|
||||
/// <param name="mask">The net mask.</param>
|
||||
/// <param name="address">The IPAddress.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static IPAddress BitwiseAnd(IPAddress address, NetMask mask) => mask & address;
|
||||
|
||||
/// <summary>Bitwise combines a <see cref="T:System.Net.Topology.NetMask" /> instance and an <see cref="T:System.Net.IPAddress"/> the AND operation.</summary>
|
||||
/// <param name="mask">The net mask.</param>
|
||||
/// <param name="address">The IPAddress.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static IPAddress operator &(NetMask mask, IPAddress address)
|
||||
{
|
||||
var ipBytes = address == null
|
||||
? new byte[MaskLength]
|
||||
: address.GetAddressBytes();
|
||||
var maskBytes = mask == null
|
||||
? new byte[MaskLength]
|
||||
: new byte[] { mask._maskB0, mask._maskB1, mask._maskB2, mask._maskB3 };
|
||||
|
||||
byte[] combinedBytes = maskBytes.And(ipBytes);
|
||||
return new IPAddress(combinedBytes);
|
||||
}
|
||||
|
||||
/// <summary>Bitwise combines a <see cref="T:System.Net.Topology.NetMask" /> instance and an <see cref="T:System.Net.IPAddress"/> the AND operation.</summary>
|
||||
/// <param name="mask">The net mask.</param>
|
||||
/// <param name="address">The IPAddress.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static IPAddress BitwiseAnd(NetMask mask, IPAddress address) => mask & address;
|
||||
|
||||
/// <summary>Bitwise combines the two instances of <see cref="T:System.Net.Topology.NetMask" /> using the AND operation.</summary>
|
||||
/// <param name="n1">The first other.</param>
|
||||
/// <param name="n2">The second other.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static NetMask operator &(NetMask n1, NetMask n2) => new NetMask(n1._mask & n2._mask);
|
||||
|
||||
/// <summary>Bitwise combines the two instances of <see cref="T:System.Net.Topology.NetMask" /> using the AND operation.</summary>
|
||||
/// <param name="n1">The first other.</param>
|
||||
/// <param name="n2">The second other.</param>
|
||||
/// <returns>The bitwised combination using the AND operation.</returns>
|
||||
public static NetMask BitwiseAnd(NetMask n1, NetMask n2) => n1 & n2;
|
||||
|
||||
#endregion
|
||||
#region Or
|
||||
|
||||
/// <summary>Bitwise combines the two instances of <see cref="T:System.Net.Topology.NetMask" /> using the OR operation.</summary>
|
||||
/// <param name="n1">The first other.</param>
|
||||
/// <param name="n2">The second other.</param>
|
||||
/// <returns>The bitwised combination using the OR operation.</returns>
|
||||
public static NetMask operator |(NetMask n1, NetMask n2) => new NetMask(n1._mask | n2._mask);
|
||||
|
||||
/// <summary>Bitwise combines the two instances of <see cref="T:System.Net.Topology.NetMask" /> using the OR operation.</summary>
|
||||
/// <param name="n1">The first other.</param>
|
||||
/// <param name="n2">The second other.</param>
|
||||
/// <returns>The bitwised combination using the OR operation.</returns>
|
||||
public static NetMask BitwiseOr(NetMask n1, NetMask n2) => n1 | n2;
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#region Common overrides
|
||||
|
||||
/// <summary>Converts the other of this instance to its equivalent string representation.</summary>
|
||||
/// <returns>A string that represents the other of this instance.</returns>
|
||||
/// <filterpriority>1</filterpriority>
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder(4 * 3 + 3 * 3 + 32 + 3 + 3); // 255.255.255.255 (11111111111111111111111111111111)
|
||||
|
||||
var arr = new byte[] { _maskB0, _maskB1, _maskB2, _maskB3 };
|
||||
var asString = arr.ToBinaryString('.');
|
||||
|
||||
sb.Append(arr[0]).Append('.').Append(arr[1]).Append('.').Append(arr[2]).Append('.').Append(arr[3]).Append(" (");
|
||||
sb.Append(asString).Append(')');
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Returns a value indicating whether this instance and a specified <see cref="T:System.Object" /> represent the same type and other.</summary>
|
||||
/// <returns>true if <paramref name="obj" /> is a <see cref="T:System.Net.Topology.NetMask" /> and equal to this instance; otherwise, false.</returns>
|
||||
/// <param name="obj">The object to compare with this instance. </param>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override bool Equals(object obj) => obj != null && obj is NetMask && Equals((NetMask)obj);
|
||||
|
||||
/// <summary>Returns a value indicating whether this instance and a specified <see cref="T:System.Net.Topology.NetMask" /> object represent the same other.</summary>
|
||||
/// <returns>true if <paramref name="other" /> is equal to this instance; otherwise, false.</returns>
|
||||
/// <param name="other">An object to compare to this instance.</param>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public bool Equals(NetMask other) => other._mask == _mask;
|
||||
|
||||
/// <summary>Returns the hash code for this instance.</summary>
|
||||
/// <returns>A 32-bit signed integer hash code.</returns>
|
||||
/// <filterpriority>2</filterpriority>
|
||||
public override int GetHashCode() => unchecked((int)_mask);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
/// <summary>Provides extension methods for the <see cref="T:System.Net.Topology.NetMask"/>.</summary>
|
||||
public static class NetMaskExtensions
|
||||
{
|
||||
/// <summary>Gets the number of siblings an <see cref="T:System.Net.IPAddress"/> can have in a given network. Compliant to RFC 950 (2^n-2).</summary>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <returns>The number of siblings an <see cref="T:System.Net.IPAddress"/> can have in the given network.</returns>
|
||||
public static int GetSiblingCount(this NetMask mask) => GetSiblingCount(mask, SiblingOptions.ExcludeUnusable);
|
||||
|
||||
/// <summary>Gets the number of siblings an <see cref="T:System.Net.IPAddress"/> can have in a given network.</summary>
|
||||
/// <param name="mask">The net mask of the network</param>
|
||||
/// <param name="options">Options which addresses to include an which not</param>
|
||||
/// <returns>The number of siblings an <see cref="T:System.Net.IPAddress"/> can have in the given network.</returns>
|
||||
public static int GetSiblingCount(this NetMask mask, SiblingOptions options)
|
||||
{
|
||||
if (mask == null)
|
||||
throw new ArgumentNullException(nameof(mask));
|
||||
|
||||
bool includeSelf = BitHelper.IsOptionSet(options, SiblingOptions.IncludeSelf);
|
||||
bool includeBroadcast = BitHelper.IsOptionSet(options, SiblingOptions.IncludeBroadcast);
|
||||
bool includeNetworkIdentifier = BitHelper.IsOptionSet(options, SiblingOptions.IncludeNetworkIdentifier);
|
||||
|
||||
var hostPartBits = mask.GetMaskBytes().CountFromRight(false);
|
||||
var total = 1 << hostPartBits;
|
||||
total -= includeSelf ? 0 : 1;
|
||||
total -= includeBroadcast ? 0 : 1;
|
||||
total -= includeNetworkIdentifier ? 0 : 1;
|
||||
|
||||
// TODO: Testing
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
/// <summary>Provides options for doing network sibling calculations using a net mask.</summary>
|
||||
[Flags]
|
||||
public enum SiblingOptions
|
||||
{
|
||||
/// <summary>Do not include the broadcast or net address neither the addess passed to the method.</summary>
|
||||
ExcludeAll = 0,
|
||||
/// <summary>Include the addess passed to the method.</summary>
|
||||
IncludeSelf = 1,
|
||||
/// <summary>Include the addess passed to the method. Compliant to RFC 950 (2^n-2).</summary>
|
||||
ExcludeUnusable = IncludeSelf,
|
||||
/// <summary>Include the broadcast address.</summary>
|
||||
IncludeBroadcast = 2,
|
||||
/// <summary>Include the net address.</summary>
|
||||
IncludeNetworkIdentifier = 4,
|
||||
/// <summary>Include all addresses possible. RFC 1878 (2^n).</summary>
|
||||
IncludeAll = IncludeSelf | IncludeBroadcast | IncludeNetworkIdentifier
|
||||
}
|
||||
|
||||
internal static class BitHelper
|
||||
{
|
||||
[Runtime.TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] // as if NGen will ever compile this assembly ;)
|
||||
[Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsOptionSet(SiblingOptions value, SiblingOptions testValue) => (value & testValue) == testValue;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
// TODO: The following method uses unsafe stuff. We don't want to pass the /unsafe flag to the compiler, so we use a simple, safe method instead.
|
||||
// As soon as .NET Standard with Span<T> and the new String.Create is available, we can make this faster and safe.
|
||||
/*
|
||||
internal static unsafe string Reverse(this string input)
|
||||
{
|
||||
int len = input.Length;
|
||||
|
||||
// Why allocate a char[] array on the heap when you won't use it
|
||||
// outside of this method? Use the stack.
|
||||
char* reversed = stackalloc char[len];
|
||||
|
||||
// Avoid bounds-checking performance penalties.
|
||||
fixed (char* str = input)
|
||||
{
|
||||
int i = 0;
|
||||
int j = i + len - 1;
|
||||
while (i < len)
|
||||
reversed[i++] = str[j--];
|
||||
}
|
||||
|
||||
// Need to use this overload for the System.String constructor
|
||||
// as providing just the char* pointer could result in garbage
|
||||
// at the end of the string (no guarantee of null terminator).
|
||||
return new string(reversed, 0, len);
|
||||
}
|
||||
*/
|
||||
internal static string Reverse(this string input)
|
||||
{
|
||||
char[] chars = input.ToCharArray();
|
||||
Array.Reverse(chars);
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace System.Net.Topology
|
||||
{
|
||||
internal static class UIntExtensions
|
||||
{
|
||||
// TODO: Tests
|
||||
public static int CountOnesFromLeft(this uint value)
|
||||
{
|
||||
var occurences = 0;
|
||||
for (uint i = 1u << 31; i >= 0; i >>= 1)
|
||||
{
|
||||
if ((i & value) != 0)
|
||||
++occurences;
|
||||
else
|
||||
break;
|
||||
}
|
||||
return occurences;
|
||||
}
|
||||
|
||||
// TODO: Tests
|
||||
public static uint CreateWithOnesFromLeft(int count)
|
||||
{
|
||||
if (count > 32)
|
||||
throw new ArgumentException("Cannot set more than 32 bits to one!");
|
||||
|
||||
var value = 0u;
|
||||
|
||||
var mask = 1u << 31;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
value |= mask;
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<Authors>Niklas Mollenhauer</Authors>
|
||||
<PackageVersion>2.0.2</PackageVersion>
|
||||
<Description>Library for Sending magic packets and performing IP address operations.</Description>
|
||||
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/nikeee/wake-on-lan</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/nikeee/wake-on-lan.git</RepositoryUrl>
|
||||
<PackageTags>WakeOnLan, magic packets</PackageTags>
|
||||
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||
<RootNamespace>System.Net</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.6.0" />
|
||||
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="4.3.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in new issue
Block a user