Compare commits

...
41 Commits
Author SHA1 Message Date
Vincent Maas 43f038e9ee add auto update, add LG expert actions and auto set current device 2022-12-04 10:13:53 +01:00
Maassoft cb09d5c540 Merge pull request #142 from Shraymonks/fix-readme-url
Fix lgtv.js url in readme
2022-11-22 20:01:32 +01:00
Raymond Ha d29f607ab0 Fix lgtv.js url in readme 2022-11-21 13:10:23 -08:00
Vincent Maas dc2f7f493c fixed several issues, add pre-launch/finalize steps to game presets 2022-11-13 15:06:24 +01:00
Vincent Maas 452206729b fix WOL issues, add Windows Service 2022-10-28 14:25:14 +02:00
Vincent Maas a8d41f67c9 fix screensaver issue, add eye comfort mode, improve elevated process and add priority/affinity settings to game launcher 2022-09-11 16:40:55 +02:00
Vincent Maas 84cb7e6446 add auto volume and total power on hours action 2022-07-24 15:14:44 +02:00
Vincent Maas 6c21245b98 fix typo 2022-07-16 15:16:09 +02:00
Vincent Maas a1121c165f fix some issues, add LG aspect ratio/zoom actions 2022-07-16 15:04:07 +02:00
Vincent Maas 5836f88ef0 restructured code (wip), add game launcher and quick access 2022-06-27 19:07:33 +02:00
Vincent Maas 41cf8ae3e2 add/improve some picture settings, allow enabling OLED Motion Pro for C2/G2, add NVIDIA resolution and driver settings 2022-05-21 11:30:28 +02:00
Vincent Maas c1ace0bdd5 add sorting of list views, improve refresh rate/color depth apply order, fix manually turned off tv's 2022-05-01 10:07:22 +02:00
Vincent Maas 66e682ba3d fixed corrupt json file issues, retrieving apps and added HDMI port selection and new power setting 2022-04-03 14:58:14 +02:00
Vincent Maas 0de3deebf7 fixed some nvidia/lg controller issues 2022-03-26 15:19:55 +01:00
Vincent Maas 0c1859f848 add spanish translations to restart detector 2022-02-27 15:23:46 +01:00
Vincent Maas b8910a3b64 fixed several issues, add some LG settings 2022-02-26 14:35:38 +01:00
Vincent Maas b2e7c529a0 fix/add various LG/NVIDIA issues 2022-02-05 11:53:49 +01:00
Vincent Maas 977060e927 fix dithering issues 2022-01-02 15:24:33 +01:00
Vincent Maas f609471fd2 add label to set device config call, add EzAdjust service menu expert button 2021-12-31 14:48:28 +01:00
Vincent Maas a1a94d27db add hdmi 1-4 icon actions 2021-12-29 11:47:47 +01:00
Vincent Maas e12faaba27 remove asp.net framework dependency 2021-12-27 17:50:10 +01:00
Vincent Maas 6f5f7c4dd3 add game bar and some other functions, upgrade to .NET 6 2021-12-27 14:37:14 +01:00
Vincent Maas e1800b8d50 add some LG actions, fix IP address edit box 2021-09-26 10:19:06 +02:00
Vincent Maas 7779533c1e Merge branch 'master' of https://github.com/Maassoft/ColorControl 2021-09-12 14:46:14 +02:00
Vincent Maas 74cefc00f7 add firmware downgrade functionality 2021-09-12 14:46:07 +02:00
Maassoft a8d33b6a30 Update readme (work in progress) 2021-09-05 20:07:07 +02:00
Vincent Maas f83869da04 add turn screen off/on actions 2021-09-04 14:25:48 +02:00
Vincent Maas 815710c081 fix shortcuts and refresh of apps 2021-08-30 19:17:34 +02:00
Vincent Maas 805c516371 fix F13-F24 keys 2021-08-29 16:14:17 +02:00
Vincent Maas b85d929890 fixed process monitor issue, improve shortcuts and Chrome fix 2021-08-29 15:28:18 +02:00
Vincent Maas dd9ce932fc made loading presets code more robust 2021-08-18 11:15:56 +02:00
Vincent Maas 3667243fc7 fixed some issues, added check for updates 2021-08-16 13:59:01 +02:00
Vincent Maas 92f4ab8ef5 add triggers, fixed some bugs 2021-08-13 15:11:33 +02:00
Vincent Maas 90ca095954 add nvidia/amd presets to steps in LG controller 2021-07-04 16:18:59 +02:00
Vincent Maas fa63d8dc4f fixed some issues 2021-06-20 13:59:12 +02:00
Vincent Maas fc134b146a extend eligible models for motion pro, fix bug when adding WOL action 2021-06-11 20:07:51 +02:00
Vincent Maas 3abf106496 extended expert-button, added new actions 2021-06-06 15:58:21 +02:00
Vincent Maas a17534abcb use modelName for detection if eligible for motion pro 2021-05-30 17:17:46 +02:00
Vincent Maas 4614886954 add LG expert button to test new way of changing settings 2021-05-30 10:31:35 +02:00
Vincent Maas 26a91dc7e9 manually add device, add device setting per preset, add remote control image 2021-05-02 17:38:38 +02:00
Vincent Maas cac5180c76 add name to NVIDIA/AMD presets, add CLI 2021-04-18 15:15:55 +02:00
207 changed files with 41985 additions and 5906 deletions

No files matched your search

+2 -1
View File
@@ -337,4 +337,5 @@ ASALocalRun/
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
healthchecksdb
*.clef
+15 -5
View File
@@ -1,9 +1,11 @@
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
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nspector", "Nspector\Nspector.csproj", "{76797290-48F5-4A82-86C3-FAD25DAE4500}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -15,12 +17,20 @@ 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
{A6005D08-70F0-44CB-A384-9219EBA84F21}.Release|x64.Build.0 = Release|x64
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|x64.ActiveCfg = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Debug|x64.Build.0 = Debug|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|Any CPU.Build.0 = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|x64.ActiveCfg = Release|Any CPU
{76797290-48F5-4A82-86C3-FAD25DAE4500}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+1
View File
@@ -0,0 +1 @@
{"Build":"0.3.261602\u002Befb3cc30a24e699e6e4337f263a2b9478f04f618","CurrentProject":"ColorControl.csproj","EntryPoints":["ColorControl.csproj"],"Properties":{}}
+317
View File
@@ -0,0 +1,317 @@
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Dependency Analysis",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA103",
"name": "ReferenceToBeDeleted",
"fullDescription": {
"text": "References that need to be deleted in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
},
{
"id": "UA106",
"name": "PackageToBeAdded",
"fullDescription": {
"text": "Packages that need to be added in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
},
{
"id": "UA108",
"name": "FrameworkReferenceToBeAdded",
"fullDescription": {
"text": "Framework Reference that need to be added in order to upgrade the project to chosen TFM"
},
"helpUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
]
}
},
"results": [
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Configuration needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.ServiceModel needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Web needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA103",
"message": {
"text": "Reference to System.Web.Extensions needs to be deleted."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.Configuration.ConfigurationManager, Version=5.0.0 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Primitives, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Http, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Duplex, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.NetTcp, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Security, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package System.ServiceModel.Federation, Version=4.8.1 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers, Version=0.3.261602 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA106",
"message": {
"text": "Package Microsoft.Windows.Compatibility, Version=6.0.0 needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
},
{
"ruleId": "UA108",
"message": {
"text": "Framework Reference to Microsoft.AspNetCore.App needs to be added."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/ColorControl.csproj"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "API Upgradability",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview"
}
},
"results": [],
"columnKind": "utf16CodeUnits"
},
{
"tool": {
"driver": {
"name": "Component Analysis",
"semanticVersion": "0.3.261602",
"informationUri": "https://docs.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview",
"rules": [
{
"id": "UA202",
"name": "Microsoft.DotNet.UpgradeAssistant.Extensions.Windows.WinformsDpiSettingUpdater",
"fullDescription": {
"text": "Winforms Source Updater"
},
"helpUri": "about:blank"
}
]
}
},
"results": [
{
"ruleId": "UA202",
"message": {
"text": "HighDpiMode needs to set in Main() instead of app.config or app.manifest - Application.SetHighDpiMode(HighDpiMode.<setting>). It is recommended to use SystemAware as the HighDpiMode option for better results."
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "file:///C:/Users/vinni/source/repos/ColorControl/ColorControl/Program.cs"
},
"region": {}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
}
]
}
-29
View File
@@ -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>
+19
View File
@@ -0,0 +1,19 @@
namespace ColorControl
{
public class AppContext
{
public static AppContext CurrentContext { get; private set; }
public Config Config { get; private set; }
public StartUpParams StartUpParams { get; private set; }
public AppContext(Config config, StartUpParams startUpParams)
{
Config = config;
StartUpParams = startUpParams;
CurrentContext = this;
}
}
}
+89 -272
View File
@@ -1,293 +1,110 @@
<?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>
<OutputType>WinExe</OutputType>
<RootNamespace>ColorControl</RootNamespace>
<AssemblyName>ColorControl</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>false</MapFileExtensions>
<UpdateUrl>https://www.maassoft.com/downloads/ColorControl/</UpdateUrl>
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>2.0.0.0</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>10.0</LangVersion>
<TargetFramework>net6.0-windows10.0.20348.0</TargetFramework>
<OutputType>WinExe</OutputType>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>false</MapFileExtensions>
<UpdateUrl>https://www.maassoft.com/downloads/ColorControl/</UpdateUrl>
<ProductName>ColorControl</ProductName>
<PublisherName>Maassoft</PublisherName>
<Company>Maassoft</Company>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>8.2.0.0</ApplicationVersion>
<Version>8.2.0.0</Version>
<UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>false</BootstrapperEnabled>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>false</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<ApplicationIcon>pngbarn.ico</ApplicationIcon>
<SupportedOSPlatformVersion>10.0.20348.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>
<OutputPath>bin\x64\Debug\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ManifestCertificateThumbprint>482C987E8C69872608C9D44751E004C5784E8540</ManifestCertificateThumbprint>
</PropertyGroup>
<PropertyGroup>
<ManifestKeyFile>ColorControl_TemporaryKey.pfx</ManifestKeyFile>
</PropertyGroup>
<PropertyGroup>
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<TargetZone>LocalIntranet</TargetZone>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<SignManifests>true</SignManifests>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>ColorControl_TemporaryKey.pfx</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>pngbarn.ico</ApplicationIcon>
<OutputPath>bin\x64\Release\</OutputPath>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</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.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Native.0.1.0\lib\net35\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.9\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="NStandard, Version=0.6.12.5, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NStandard.0.6.12.5\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.0.7.6, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\NWin32.1.0.7.6\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" />
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Update="Services\Common\QuickAccessForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="services\lg\RemoteControlPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<EmbeddedResource Include="Resources\LG_register.json" />
<EmbeddedResource Include="Resources\RC_coords.txt" />
<EmbeddedResource Include="Resources\LG_remote.jpg" />
<Content Include="LgPresets.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Include="ADL.cs" />
<Compile Include="ADLWrapper.cs" />
<Compile Include="AmdDisplayInfo.cs" />
<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="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="RemoteControlForm.resx">
<DependentUpon>RemoteControlForm.cs</DependentUpon>
</EmbeddedResource>
<None Include="ColorControl_TemporaryKey.pfx" />
<Content Include="LgPresets.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="lgtv\README.md" />
<None Include="packages.config" />
<None Include="Properties\app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Content Include="pngbarn.ico" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<Folder Include="bin\x64\Debug\" />
</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>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<Content Include="pngbarn.ico" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<Folder Include="bin\x64\Debug\" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="NStandard" Version="0.15.2" />
<PackageReference Include="NvAPIWrapper.Net" Version="0.8.1.101" />
<PackageReference Include="NWin32" Version="1.2.3" />
<PackageReference Include="TaskScheduler" Version="2.10.1" />
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.9" />
</ItemGroup>
<ItemGroup>
<COMReference Include="IWshRuntimeLibrary">
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
<ProjectReference Include="..\Nspector\Nspector.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
-54
View File
@@ -1,54 +0,0 @@
using NvAPIWrapper.Display;
using NvAPIWrapper.Native.Display;
using System;
using System.Collections.Generic;
using System.Web.Script.Serialization;
namespace ColorControl
{
public class ColorDataConverter : JavaScriptConverter
{
public override IEnumerable<Type> SupportedTypes
{
get { return new List<Type>() { typeof(ColorData) }; }
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Dictionary<string, object> result = new Dictionary<string, object>();
if (obj == null) return result;
return result;
}
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var format = ColorDataFormat.RGB;
var dynamicRange = ColorDataDynamicRange.VESA;
var colorDepth = ColorDataDepth.BPC8;
var colorimetry = ColorDataColorimetry.Auto;
var selectionPolicy = ColorDataSelectionPolicy.User;
object value;
if (dictionary.TryGetValue("ColorFormat", out value))
{
format = (ColorDataFormat)Enum.ToObject(typeof(ColorDataFormat), value);
}
if (dictionary.TryGetValue("ColorDepth", out value))
{
colorDepth = (ColorDataDepth)Enum.ToObject(typeof(ColorDataDepth), value);
}
if (dictionary.TryGetValue("Colorimetry", out value))
{
colorimetry = (ColorDataColorimetry)Enum.ToObject(typeof(ColorDataColorimetry), value);
}
if (dictionary.TryGetValue("DynamicRange", out value))
{
dynamicRange = (ColorDataDynamicRange)Enum.ToObject(typeof(ColorDataDynamicRange), value);
}
if (dictionary.TryGetValue("SelectionPolicy", out value))
{
selectionPolicy = (ColorDataSelectionPolicy)Enum.ToObject(typeof(ColorDataSelectionPolicy), value);
}
return new ColorData(format, dynamicRange: dynamicRange, colorimetry: colorimetry, colorDepth: colorDepth, colorSelectionPolicy: selectionPolicy, desktopColorDepth: ColorDataDesktopDepth.Default);
}
}
}
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace ColorControl
namespace ColorControl.Common
{
/// <summary>
@@ -18,7 +18,7 @@ namespace ColorControl
/// <param name="environmentStackTrace">Environment stack trace, for pulling additional stack frames.</param>
public static string ToLogString(this Exception exception, string environmentStackTrace = "")
{
List<string> environmentStackTraceLines = ExceptionExtensions.GetUserStackTraceLines(environmentStackTrace);
List<string> environmentStackTraceLines = GetUserStackTraceLines(environmentStackTrace);
if (environmentStackTraceLines.Any())
{
environmentStackTraceLines.RemoveAt(0);
@@ -29,7 +29,14 @@ namespace ColorControl
string fullStackTrace = String.Join(Environment.NewLine, stackTraceLines);
string logMessage = exception.Message + Environment.NewLine + fullStackTrace;
string logMessage = exception.Message + Environment.NewLine;
if (exception.InnerException != null)
{
logMessage += $"Inner exception: {exception.InnerException.Message}{Environment.NewLine}";
}
logMessage += fullStackTrace;
return logMessage;
}
@@ -51,7 +58,7 @@ namespace ColorControl
List<string> outputList = new List<string>();
Regex regex = new Regex(@"([^\)]*\)) in (.*):([A-z]*) (\d)*$");
List<string> stackTraceLines = ExceptionExtensions.GetStackTraceLines(fullStackTrace);
List<string> stackTraceLines = GetStackTraceLines(fullStackTrace);
foreach (string stackTraceLine in stackTraceLines)
{
if (!regex.IsMatch(stackTraceLine))
+83
View File
@@ -0,0 +1,83 @@
using ColorControl.Svc;
using Newtonsoft.Json;
using System;
using System.IO.Pipes;
using System.Security.Principal;
namespace ColorControl.Common
{
public static class PipeUtils
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static string SendMessage(string message)
{
var pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut, PipeOptions.None, TokenImpersonationLevel.None);
try
{
pipeClient.Connect(500);
try
{
var ss = new StreamString(pipeClient);
ss.WriteString(message);
var result = ss.ReadString();
return result;
}
finally
{
pipeClient.Close();
}
}
catch (Exception ex)
{
Logger.Error(ex, "PipeUtils.SendMessage");
return null;
}
}
public static SvcResultMessage SendMessage(SvcMessage message)
{
var messageJson = JsonConvert.SerializeObject(message);
var resultJson = SendMessage(messageJson);
if (resultJson == null)
{
return null;
}
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
return resultMessage;
}
public static SvcResultMessage SendMessage(SvcMessageType messageType)
{
var message = new SvcMessage { MessageType = messageType };
return SendMessage(message);
}
public static bool? SendRpcMessage(string command)
{
var message = new SvcMessage { MessageType = SvcMessageType.ExecuteRpc, Data = command };
var messageJson = JsonConvert.SerializeObject(message);
var resultJson = SendMessage(messageJson);
if (resultJson == null)
{
return null;
}
var resultMessage = JsonConvert.DeserializeObject<SvcResultMessage>(resultJson);
return resultMessage.Result;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace ColorControl.Common
{
public static class ProcessExtensions
{
private static string FindIndexedProcessName(int pid, string processName, IEnumerable<Process> processes = 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++)
{
processIndexedName = index == 0 ? processName : processName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexedName);
if ((int)processId.NextValue() == pid)
{
return processIndexedName;
}
}
return processIndexedName;
}
private static Process FindPidFromIndexedProcessName(string indexedProcessName, IEnumerable<Process> processes = null)
{
var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName);
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, IEnumerable<Process> processes = null)
{
var indexedProcessName = FindIndexedProcessName(process.Id, process.ProcessName, processes);
if (indexedProcessName == null)
{
return null;
}
return FindPidFromIndexedProcessName(indexedProcessName, processes);
}
}
}
+51
View File
@@ -0,0 +1,51 @@
using System;
using System.IO;
using System.Text;
namespace ColorControl.Common
{
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;
public StreamString(Stream ioStream)
{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}
public string ReadString()
{
var lenBytes = new byte[4];
var read = ioStream.Read(lenBytes, 0, lenBytes.Length);
if (read < lenBytes.Length)
{
return null;
}
var len = BitConverter.ToInt32(lenBytes);
var inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);
return streamEncoding.GetString(inBuffer);
}
public int WriteString(string outString)
{
var outBuffer = streamEncoding.GetBytes(outString);
var len = outBuffer.Length;
var lenBytes = BitConverter.GetBytes(len);
ioStream.Write(lenBytes, 0, lenBytes.Length);
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();
return outBuffer.Length + lenBytes.Length;
}
}
}
File diff suppressed because it is too large. Load diff
+256
View File
@@ -0,0 +1,256 @@
using NLog;
using System;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using Vanara.PInvoke;
using static Vanara.PInvoke.IpHlpApi;
namespace ColorControl.Common
{
class WOL
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private const string ArgumentExceptionInvalidMacAddressLength = "Invalid MAC address length.";
private const string ArgumentExceptionInvalidPasswordLength = "Invalid password length.";
private const int DefaultWolPort = 9;
public static bool WakeFunction(string macAddress, string ipAddress = null)
{
return WakeFunctionToAllNics(macAddress, ipAddress);
}
public static bool WakeFunctionCheckAdmin(string macAddress, string ipAddress = null)
{
if (Utils.IsAdministrator())
{
return WakeFunction(macAddress, ipAddress);
}
var result = PipeUtils.SendRpcMessage($"{StartUpParams.SendWakeOnLanParam} {macAddress} {ipAddress}");
// Execute local WOL anyway because pipe failed
if (!result.HasValue)
{
result = WakeFunction(macAddress, ipAddress);
}
return result.Value;
}
public static bool WakeFunctionToAllNics(string macAddress, string ipAddressString)
{
var result = false;
Logger.Debug($"Sending WOL packet to MAC-address {macAddress}");
try
{
var physicalAddress = PhysicalAddress.Parse(macAddress);
var addressBytes = physicalAddress.GetAddressBytes();
var data = GetWolPacket(addressBytes);
var ipAddress = ipAddressString != null ? IPAddress.Parse(ipAddressString) : null;
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || ni.Name.Contains("VirtualBox") || ni.Name.Contains("vEthernet") || ni.Description.Contains("Virtual"))
{
continue;
}
Logger.Debug($"Found network: {ni.Name} ({ni.Description}), type: {ni.NetworkInterfaceType}");
try
{
if (ni.SupportsMulticast && ni.GetIPProperties().GetIPv4Properties() == null)
{
continue;
}
foreach (var uip in ni.GetIPProperties().UnicastAddresses)
{
if (uip.Address.ToString().StartsWith("169.254") || uip.Address.AddressFamily != AddressFamily.InterNetwork)
{
continue;
}
Logger.Debug($"Broadcast WOL in network: {ni.Name} ({ni.Description}), local address: {uip.Address}, destination IP-address: {ipAddressString ?? "broadcast"}");
if (ipAddress != null)
{
var localEP = new IPEndPoint(uip.Address, 0);
SendWolToIpAddress(uip.Address, ipAddress, data, physicalAddress);
}
BroadcastWol(uip.Address, IPAddress.Broadcast, data);
var parts = uip.Address.ToString().Split(".").ToList();
parts[3] = "0";
var broadcastAddress = IPAddress.Parse(string.Join(".", parts));
BroadcastWol(uip.Address, broadcastAddress, data);
result = true;
}
}
catch (Exception ex)
{
Logger.Error($"WakeFunctionToAllNics: while sending to specific network: {ni.Name} ({ni.Description}): {ex.ToLogString()}");
}
}
}
catch (Exception ex)
{
Logger.Error($"WakeFunctionToAllNics: {ex.ToLogString()}");
}
return result;
}
/// <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 = null)
{
if (macAddress == null)
throw new ArgumentNullException(nameof(macAddress));
if (macAddress.Length != 6)
throw new ArgumentException(ArgumentExceptionInvalidMacAddressLength);
password = password ?? new byte[0];
if (password.Length != 0 && password.Length != 6)
throw new ArgumentException(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;
}
private static void BroadcastWol(IPAddress localAddress, IPAddress broadCastAddress, byte[] data)
{
//var localEP = new IPEndPoint(localAddress, 0);
var udpc = new UdpClient();
udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
//udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
var target = new IPEndPoint(broadCastAddress, DefaultWolPort);
udpc.Send(data, data.Length, target);
}
private static void SendWolToIpAddress(IPAddress localAddress, IPAddress remoteAddress, byte[] data, PhysicalAddress physicalAddress)
{
//var localEP = new IPEndPoint(localAddress, 0);
var netLuid = CreateTransientLocalNetEntry(remoteAddress, physicalAddress);
try
{
var udpc = new UdpClient();
udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
var target = new IPEndPoint(remoteAddress, DefaultWolPort);
udpc.Send(data, data.Length, target);
}
finally
{
DeleteNetEntry(netLuid, remoteAddress);
}
}
private static NET_LUID CreateTransientLocalNetEntry(IPAddress ipAddress, PhysicalAddress macAddress)
{
var ipBytes = ipAddress.GetAddressBytes();
var address = new Ws2_32.SOCKADDR_INET
{
si_family = Ws2_32.ADDRESS_FAMILY.AF_INET,
Ipv4 = new Ws2_32.SOCKADDR_IN(new Ws2_32.IN_ADDR(ipBytes), DefaultWolPort),
};
var net = new NET_LUID();
var addr = new Ws2_32.SOCKADDR_INET();
MIB_IPFORWARD_ROW2 forwardRow;
var result = GetBestRoute2(net, 0, addr, address, 0, out forwardRow, out _);
if (result != Win32Error.ERROR_SUCCESS)
{
Logger.Debug($"Cannot find best route: {result}");
var luidDummy = new NET_LUID();
return luidDummy;
}
var luid = forwardRow.InterfaceLuid;
var macAddressBytes = macAddress.GetAddressBytes();
var bytes = new byte[32];
macAddressBytes.CopyTo(bytes, 0);
var row = new MIB_IPNET_ROW2
{
Address = address,
InterfaceLuid = luid,
PhysicalAddress = bytes,
PhysicalAddressLength = 6
};
result = CreateIpNetEntry2(ref row);
if (result != Win32Error.ERROR_SUCCESS)
{
Logger.Debug($"Cannot create ip net entry: {result}");
}
return luid;
}
private static void DeleteNetEntry(NET_LUID luid, IPAddress ipAddress)
{
if (luid.Value == 0)
{
return;
}
var ipBytes = ipAddress.GetAddressBytes();
var address = new Ws2_32.SOCKADDR_INET
{
si_family = Ws2_32.ADDRESS_FAMILY.AF_INET,
Ipv4 = new Ws2_32.SOCKADDR_IN(new Ws2_32.IN_ADDR(ipBytes), DefaultWolPort)
};
var row = new MIB_IPNET_ROW2
{
Address = address,
InterfaceLuid = luid,
};
var result = DeleteIpNetEntry2(ref row);
if (result != Win32Error.ERROR_SUCCESS)
{
Logger.Debug($"Cannot delete ip net entry: {result}");
}
}
}
}
+43 -9
View File
@@ -1,23 +1,47 @@
namespace ColorControl
using System.Windows.Forms;
namespace ColorControl
{
class Config
public enum ElevationMethod
{
None = 0,
RunAsAdmin = 1,
UseService = 2,
UseElevatedProcess = 3
}
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 bool AutoInstallUpdates { 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 ListViewSortState GamePresetsSortState { get; set; }
public string NvQuickAccessShortcut { get; set; }
public string AmdQuickAccessShortcut { get; set; }
public string LgQuickAccessShortcut { get; set; }
public string GameQuickAccessShortcut { get; set; }
public bool UseDedicatedElevatedProcess { get; set; }
public ElevationMethod ElevationMethod { get; set; }
public bool ElevationMethodAsked { get; set; }
public Config()
{
@@ -26,6 +50,16 @@
FormWidth = 900;
FormHeight = 600;
MinimizeToTray = true;
CheckForUpdates = true;
FixChromeFonts = false;
UseGdiScaling = true;
UseDedicatedElevatedProcess = false;
ElevationMethod = ElevationMethod.None;
ElevationMethodAsked = false;
LgPresetsSortState = new ListViewSortState();
NvPresetsSortState = new ListViewSortState();
AmdPresetsSortState = new ListViewSortState();
GamePresetsSortState = new ListViewSortState();
}
}
}
+46
View File
@@ -0,0 +1,46 @@
namespace ColorControl.Forms
{
partial class ElevatedForm
{
/// <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.SuspendLayout();
//
// ElevatedForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(251, 124);
this.Name = "ElevatedForm";
this.Text = "ElevatedForm";
this.ResumeLayout(false);
}
#endregion
}
}
+100
View File
@@ -0,0 +1,100 @@
using ColorControl.Native;
using NWin32;
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl.Forms
{
public partial class ElevatedForm : Form
{
public ElevatedForm()
{
File.Delete("d:\\Msg.txt");
InitializeComponent();
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
Load += ElevatedForm_Load;
Shown += ElevatedForm_Shown;
var filterStatus = new WinApi.CHANGEFILTERSTRUCT();
filterStatus.size = (uint)Marshal.SizeOf(filterStatus);
filterStatus.info = 0;
var result = WinApi.ChangeWindowMessageFilterEx(Handle, NativeConstants.WM_COPYDATA, WinApi.ChangeWindowMessageFilterExAction.Allow, ref filterStatus);
//File.AppendAllLines("d:\\Msg.txt", new[] { "ChangeWindowMessageFilterEx result: " + result });
Task.Run(() => CheckMutexAsync());
}
private void ElevatedForm_Shown(object sender, EventArgs e)
{
Hide();
}
protected void ElevatedForm_Load(object sender, EventArgs e)
{
Size = new Size(0, 0);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == NativeConstants.WM_COPYDATA)
{
var container = (WinApi.COPYDATASTRUCT)m.GetLParam(typeof(WinApi.COPYDATASTRUCT));
var message = Marshal.PtrToStringAnsi(container.lpData);
//File.AppendAllLines("d:\\Msg.txt", new[] { "Message received: " + message });
HandleMessage(message);
//Utils.StartProcess("notepad.exe");
//BackColor = Color.Red;
//Text = "AHA!";
//MessageForms.WarningOk("Received!");
}
base.WndProc(ref m);
}
private void HandleMessage(string message)
{
var args = message.Split(' ');
var startUpParams = StartUpParams.Parse(args);
try
{
Program.HandleStartupParams(startUpParams, null);
}
catch (Exception)
{
}
}
private void CheckMutexAsync()
{
var mutex = new Mutex(false, Program.MutexId, out var created);
File.AppendAllLines("d:\\Msg.txt", new[] { "Mutex created: " + created });
try
{
mutex.WaitOne();
File.AppendAllLines("d:\\Msg.txt", new[] { "Mutex released?" });
}
catch (AbandonedMutexException)
{
File.AppendAllLines("d:\\Msg.txt", new[] { "Mutex abandoned" });
}
BeginInvoke(() => Application.Exit());
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<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>
</root>
+487
View File
@@ -0,0 +1,487 @@
using ColorControl.Common;
using ColorControl.Services.Common;
using NStandard;
using NWin32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace ColorControl.Forms
{
static class FormUtils
{
public enum UserNotificationState : int
{
QUNS_NOT_PRESENT = 1,
QUNS_BUSY,
QUNS_RUNNING_D3D_FULL_SCREEN,
QUNS_PRESENTATION_MODE,
QUNS_ACCEPTS_NOTIFICATIONS,
QUNS_QUIET_TIME,
QUNS_APP
}
public static UserNotificationState[] NotificationsDisabledStates = new[] {
UserNotificationState.QUNS_BUSY,
UserNotificationState.QUNS_RUNNING_D3D_FULL_SCREEN,
UserNotificationState.QUNS_PRESENTATION_MODE,
};
[DllImport("shell32.dll")]
public static extern int SHQueryUserNotificationState(out UserNotificationState pquns);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private static UserNotificationState LastNotificationState;
public static void SetNotifyIconText(NotifyIcon ni, string text)
{
text = text.Substring(0, Math.Min(text.Length, 127));
Type t = typeof(NotifyIcon);
BindingFlags hidden = BindingFlags.NonPublic | BindingFlags.Instance;
t.GetField("text", hidden).SetValue(ni, text);
if ((bool)t.GetField("added", hidden).GetValue(ni))
t.GetMethod("UpdateIcon", hidden).Invoke(ni, new object[] { true });
}
public static ToolStripMenuItem BuildDropDownMenuEx(ToolStripItemCollection items, string parentName, string name, Type enumType, EventHandler clickEvent, object tag = null, int min = 0, int max = 0, bool noSubItems = false)
{
var subMenuName = $"{parentName}_{name}";
var subMenuItems = items.Find(subMenuName, false);
if (subMenuItems.Length > 0)
{
return subMenuItems[0] as ToolStripMenuItem;
}
ToolStripMenuItem subMenuItem;
subMenuItem = (ToolStripMenuItem)items.Add(name);
subMenuItem.Name = subMenuName;
if (noSubItems)
{
subMenuItem.Tag = tag;
subMenuItem.Click += clickEvent;
return subMenuItem;
}
if (enumType != null)
{
foreach (var enumValue in Enum.GetValues(enumType))
{
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 (max > min)
{
List<int> range;
if (max - min <= 20)
{
range = Enumerable.Range(min, (max - min) + 1).ToList();
}
else
{
range = new List<int>();
for (var i = min < 0 ? -10 : 0; i <= 10; i++)
{
range.Add(i * (max / 10));
}
}
foreach (var value in range)
{
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)
{
PropertyInfo property = null;
var subMenuItems = mnuParent.DropDownItems.Find("miColorSettings_" + name, false);
ToolStripMenuItem subMenuItem;
if (subMenuItems.Length == 0)
{
subMenuItem = (ToolStripMenuItem)mnuParent.DropDownItems.Add(name);
subMenuItem.Name = "miColorSettings_" + name;
if (colorData != null)
{
property = colorData.GetType().GetDeclaredProperty(propertyName);
subMenuItem.Tag = property;
}
foreach (var enumValue in Enum.GetValues(enumType))
{
var item = subMenuItem.DropDownItems.Add(enumValue.ToString());
item.Tag = enumValue;
item.Click += clickEvent;
}
}
else
{
subMenuItem = (ToolStripMenuItem)subMenuItems[0];
property = (PropertyInfo)subMenuItem.Tag;
}
if (colorData == null)
{
return;
}
var value = property.GetValue(colorData);
foreach (var item in subMenuItem.DropDownItems)
{
if (item is ToolStripMenuItem)
{
var menuItem = (ToolStripMenuItem)item;
if (menuItem.Tag != null)
{
menuItem.Checked = menuItem.Tag.Equals(value);
}
}
}
}
public static void BuildComboBox<T>(ComboBox comboBox, params T[] skip) where T : IConvertible
{
if (comboBox.Items.Count == 0)
{
foreach (var enumValue in Enum.GetValues(typeof(T)))
{
if (skip.Contains((T)enumValue))
{
continue;
}
var item = new EnumComboBoxItem
{
Name = ((T)enumValue).GetDescription(),
Id = (int)enumValue
};
comboBox.Items.Add(item);
}
}
}
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++)
{
var item = comboBox.Items[i] as EnumComboBoxItem;
if (item.Id == value)
{
index = i;
break;
}
}
comboBox.SelectedIndex = index;
return index;
}
public static T GetComboBoxEnumItem<T>(ComboBox comboBox) where T : IConvertible
{
if (comboBox.SelectedIndex == -1)
{
return (T)Enum.Parse(typeof(T), "None");
}
var item = comboBox.SelectedItem as EnumComboBoxItem;
return (T)Enum.Parse(typeof(T), item.Id.ToString());
}
public static void InitListView(ListView listView, IEnumerable<string> columns)
{
foreach (var name in columns)
{
var columnName = name;
var parts = name.Split('|');
var width = 120;
if (parts.Length > 1)
{
width = int.Parse(parts[1]);
columnName = parts[0];
}
var header = listView.Columns.Add(columnName);
header.Width = width == 120 ? -2 : width;
}
}
public static void AddOrUpdateListItem<T>(ListView listView, List<T> presets, Config config, T preset = null, ListViewItem specItem = null) where T : PresetBase
{
ListViewItem item = null;
if (preset == null)
{
item = listView.SelectedItems[0];
preset = (T)item.Tag;
}
else
{
item = specItem;
}
if (preset.id == 0)
{
preset.id = preset.GetHashCode();
}
var values = preset.GetDisplayValues(config);
if (item == null)
{
item = listView.Items.Add(values[0]);
item.Tag = preset;
item.Checked = preset.ShowInQuickAccess;
for (var i = 1; i < values.Count; i++)
{
item.SubItems.Add(values[i]);
}
if (!presets.Any(x => x.id == preset.id))
{
presets.Add(preset);
}
}
else
{
item.Text = values[0];
item.Checked = preset.ShowInQuickAccess;
for (var i = 1; i < values.Count; i++)
{
if (item.SubItems.Count == i)
{
item.SubItems.Add(values[i]);
}
else
{
item.SubItems[i].Text = values[i];
}
}
}
if (listView.ListViewItemSorter is ListViewColumnSorter sorter && sorter?.Order != SortOrder.None)
{
listView.Sort();
}
}
public static bool IsForegroundFullScreenAndDisabledNotifications(Screen screen = null)
{
return IsNotificationDisabled() && IsForegroundFullScreen(screen);
}
public static bool IsNotificationDisabled()
{
var result = SHQueryUserNotificationState(out var state);
if (result == NativeConstants.ERROR_SUCCESS && state != LastNotificationState)
{
Logger.Debug($"Detected NotificationState change from {LastNotificationState} to {state}");
LastNotificationState = state;
}
return NotificationsDisabledStates.Contains(state);
//return state != UserNotificationState.QUNS_ACCEPTS_NOTIFICATIONS && state != UserNotificationState.QUNS_QUIET_TIME;
}
public static bool IsForegroundFullScreen(Screen screen = null)
{
if (screen == null)
{
screen = Screen.PrimaryScreen;
}
var hWnd = NativeMethods.GetForegroundWindow();
NativeMethods.GetWindowRect(hWnd, out var rect);
/* in case you want the process name:
uint procId = 0;
GetWindowThreadProcessId(hWnd, out procId);
var proc = System.Diagnostics.Process.GetProcessById((int)procId);
Console.WriteLine(proc.ProcessName);
*/
return screen.Bounds.Width <= (rect.right - rect.left) && screen.Bounds.Height <= (rect.bottom - rect.top);
}
public static (uint, bool) GetForegroundProcessIdAndIfFullScreen(Screen screen = null)
{
if (screen == null)
{
screen = Screen.PrimaryScreen;
}
var hWnd = NativeMethods.GetForegroundWindow();
if (hWnd == IntPtr.Zero)
{
return (0, false);
}
NativeMethods.GetWindowRect(hWnd, out var rect);
GetWindowThreadProcessId(hWnd, out var processId);
var isFullScreen = screen.Bounds.Width <= (rect.right - rect.left) && screen.Bounds.Height <= (rect.bottom - rect.top);
return (processId, isFullScreen);
}
public static Process GetForegroundFullScreenProcess(Screen screen = null)
{
var (processId, isFullScreen) = GetForegroundProcessIdAndIfFullScreen(screen);
if (!isFullScreen)
{
return null;
}
return Process.GetProcessById((int)processId);
}
public static void EnableControls(Control parent, bool enable = true, IEnumerable<Control> excludedControls = null)
{
for (var i = 0; i < parent.Controls.Count; i++)
{
var control = parent.Controls[i];
if (!excludedControls?.Contains(control) ?? true)
{
control.Enabled = enable;
}
}
}
public static void AddStepToTextBox(TextBox textBox, string step)
{
var text = textBox.Text;
if (string.IsNullOrWhiteSpace(text))
{
text = step;
}
else
{
var pos = textBox.SelectionStart;
while (pos < text.Length && text.CharAt(pos) != ',')
{
pos++;
}
if (pos == text.Length)
{
text += ", " + step;
}
else
{
text = text.Substring(0, pos + 1) + " " + step + ", " + text.Substring(pos + 1).Trim();
}
}
textBox.Text = text.Trim();
}
public static T GetSelectedItemTag<T>(this ListView listView)
{
if (listView.SelectedItems.Count > 0)
{
var item = listView.SelectedItems[0];
return (T)item.Tag;
}
else
{
return default(T);
}
}
public static void FireEvent(this Control ctrl, string eventName, params object[] eventParams)
{
//EventSelectedIndexChanged
var propertyInfo = ctrl.GetType().GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
var eventHandlerList = propertyInfo.GetValue(ctrl) as EventHandlerList;
var headField = typeof(EventHandlerList).GetField("_head", BindingFlags.NonPublic | BindingFlags.Instance);
var assembly = typeof(EventHandlerList).Assembly;
var listEntryType = assembly.GetTypes().FirstOrDefault(t => t.Name == "ListEntry");
var nextField = listEntryType.GetField("_next", BindingFlags.NonPublic | BindingFlags.Instance);
var handlerField = listEntryType.GetField("_handler", BindingFlags.NonPublic | BindingFlags.Instance);
var entry = headField.GetValue(eventHandlerList);
Delegate foundHandler = null;
while (entry != null)
{
var handler = handlerField.GetValue(entry) as Delegate;
if (handler.Method.Name.Contains(eventName))
{
foundHandler = handler;
break;
}
entry = nextField.GetValue(entry);
}
if (foundHandler == null)
{
return;
}
var invocationList = foundHandler.GetInvocationList();
foreach (var item in invocationList)
{
item.Method.Invoke(item.Target, eventParams);
}
}
}
public class EnumComboBoxItem
{
public string Name;
public int Id;
public override string ToString()
{
return Name;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
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
var subItemTextX = listviewX.SubItems.Count > ColumnToSort ? listviewX.SubItems[ColumnToSort].Text : string.Empty;
var subItemTextY = listviewY.SubItems.Count > ColumnToSort ? listviewY.SubItems[ColumnToSort].Text : string.Empty;
compareResult = ObjectCompare.Compare(subItemTextX, subItemTextY);
}
// 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;
}
}
}
}
+298
View File
@@ -0,0 +1,298 @@
using ColorControl.Common;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl.Forms
{
class MessageForms
{
public enum FieldType
{
Text,
Numeric,
DropDown,
Flags,
Shortcut,
}
public class FieldDefinition
{
public string Label { get; set; }
public FieldType FieldType { get; set; } = FieldType.Text;
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; }
}
public class MessageForm : Form
{
public void edtShortcut_KeyDown(object sender, KeyEventArgs e)
{
((TextBox)sender).Text = Utils.FormatKeyboardShortcut(e);
}
public void edtShortcut_KeyUp(object sender, KeyEventArgs e)
{
Utils.HandleKeyboardShortcutUp(e);
}
}
public static Form MainForm;
public static void WarningOk(string text)
{
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
public static void ErrorOk(string text)
{
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
public static void InfoOk(string text, string title = null, string url = null)
{
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)
{
return MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Information);
}
public static List<FieldDefinition> ShowDialog(string caption, IEnumerable<string> labels, Func<IEnumerable<FieldDefinition>, string> validateFunc = null)
{
var fieldDefinitions = labels.Select(l => new FieldDefinition
{
Label = l
}).ToList();
return ShowDialog(caption, fieldDefinitions, validateFunc);
}
public static List<FieldDefinition> ShowDialog(string caption, IEnumerable<FieldDefinition> fields, Func<IEnumerable<FieldDefinition>, string> validateFunc = null)
{
var values = new List<FieldDefinition>();
var prompt = new MessageForm()
{
Width = 500,
Height = 90 + (fields.Count() * 55),
FormBorderStyle = FormBorderStyle.FixedDialog,
Text = caption,
StartPosition = FormStartPosition.CenterScreen
};
var top = 10;
var boxes = new List<Control>();
var counter = 1;
foreach (var field in fields)
{
var label = field.Label;
var textLabel = new Label() { Left = 40, Top = top, Text = label, AutoSize = true };
top += 25;
prompt.Controls.Add(textLabel);
Control control;
switch (field.FieldType)
{
case FieldType.Text:
var textBox = new MaskedTextBox() { Left = 40, Top = top, Width = 400 };
if (label.Contains("Ip-address"))
{
//textBox.Mask = "990.990.990.990";
//textBox.ValidatingType = typeof(System.Net.IPAddress);
textBox.Culture = CultureInfo.InvariantCulture;
}
else if (label.Contains("MAC-address"))
{
textBox.Mask = "AA-AA-AA-AA-AA-AA";
textBox.Culture = CultureInfo.InvariantCulture;
}
control = textBox;
break;
case FieldType.Shortcut:
var shortcutTextBox = new TextBox()
{
Left = 40,
Top = top,
Width = 200,
Name = $"edtShortcut_{counter}",
ReadOnly = true
};
shortcutTextBox.KeyDown += prompt.edtShortcut_KeyDown;
shortcutTextBox.KeyUp += prompt.edtShortcut_KeyUp;
if (field.Value != null)
{
shortcutTextBox.Text = field.Value.ToString();
}
control = shortcutTextBox;
break;
case FieldType.Numeric:
var numericEdit = new NumericUpDown() { Left = 40, Top = top, Width = 400 };
if (field.MinValue != field.MaxValue)
{
numericEdit.Minimum = field.MinValue;
numericEdit.Maximum = field.MaxValue;
}
if (field.Value != null)
{
numericEdit.Text = field.Value.ToString();
}
control = numericEdit;
break;
case FieldType.DropDown:
var comboBox = new ComboBox
{
Left = 40,
Top = top,
Width = 400,
DropDownStyle = ComboBoxStyle.DropDownList
};
if (field.Values != null && field.Values.Any())
{
comboBox.Items.AddRange(field.Values.ToArray());
comboBox.SelectedIndex = field.Value is string strValue ? comboBox.Items.IndexOf(strValue) : 0;
}
control = comboBox;
break;
case FieldType.Flags:
var checkedListBox = new CheckedListBox
{
Left = 40,
Top = top,
Width = 400,
Height = 100,
MultiColumn = true
};
top += 80;
prompt.Height += 80;
if (field.Values != null && field.Values.Any())
{
var compoundValue = field.Value is uint ? (int)(uint)field.Value : (int)field.Value;
var enumValue = 1;
foreach (var value in field.Values)
{
var isChecked = (compoundValue & enumValue) == enumValue;
checkedListBox.Items.Add(value, isChecked);
enumValue *= 2;
}
}
control = checkedListBox;
break;
default:
continue;
}
top += 30;
prompt.Controls.Add(control);
boxes.Add(control);
control.Tag = field;
counter++;
}
var confirmation = new Button() { Text = "OK", Left = 365, Width = 75, Top = prompt.ClientRectangle.Height - 30 };
confirmation.Click += (sender, e) =>
{
foreach (var box in boxes)
{
var field = (FieldDefinition)box.Tag;
switch (field.FieldType)
{
case FieldType.Flags:
var checkedListBox = (CheckedListBox)box;
var compoundValue = 0;
var enumValue = 1;
for (var i = 0; i < checkedListBox.Items.Count; i++)
{
compoundValue += checkedListBox.GetItemChecked(i) ? enumValue : 0;
enumValue *= 2;
}
field.Value = compoundValue;
break;
default:
field.Value = box.Text.Trim();
break;
}
}
if (validateFunc != null)
{
var message = validateFunc(fields);
if (!string.IsNullOrEmpty(message))
{
WarningOk(message);
return;
}
}
values.AddRange(fields);
prompt.Close();
};
prompt.Controls.Add(confirmation);
prompt.AcceptButton = confirmation;
prompt.ShowDialog(MainForm);
return values;
}
public static Form ShowProgress(string caption)
{
var prompt = new Form()
{
Width = 300,
Height = 100,
FormBorderStyle = FormBorderStyle.FixedToolWindow,
Text = caption,
StartPosition = FormStartPosition.CenterScreen,
ControlBox = false
};
var textLabel = new Label() { Left = 20, Top = 25, Text = caption, AutoSize = true };
prompt.Controls.Add(textLabel);
prompt.Show(MainForm);
return prompt;
}
}
}
@@ -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 "{}";
}
-70
View File
@@ -1,70 +0,0 @@
using LgTv;
using System.Collections.Generic;
using System.Linq;
namespace ColorControl
{
class LgPreset : PresetBase
{
public string name { get; set; }
public string appId { get; set; }
public List<string> steps { get; set; }
public static List<LgApp> LgApps { get; set; }
public LgPreset()
{
steps = new List<string>();
}
public LgPreset(LgPreset preset) : this()
{
id = GetNewId();
name = preset.name + " (copy)";
appId = preset.appId;
foreach (var step in preset.steps)
{
steps.Add(step);
}
}
public LgPreset Clone()
{
var preset = new LgPreset(this);
return preset;
}
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "App|200", "Steps|400", "Shortcut" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
var app = appId;
if (LgApps != null)
{
var item = LgApps.FirstOrDefault(x => x.appId.Equals(appId));
if (item != null)
{
app = item.title + " (" + appId + ")";
}
}
values.Add(app);
values.Add(GetStepsDisplay());
values.Add(shortcut);
return values;
}
public string GetStepsDisplay()
{
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
}
}
}
-770
View File
@@ -1,770 +0,0 @@
using LgTv;
using Microsoft.Win32;
using Newtonsoft.Json;
using NWin32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using System.Windows.Forms;
namespace ColorControl
{
class LgService
{
public static string LgListAppsJson = "listApps.json";
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
//public string FriendlyScreenName { get; private set; }
public List<PnpDev> Devices { get; private set; }
public PnpDev SelectedDevice
{
get { return _selectedDevice; }
set
{
_selectedDevice = value;
Config.PreferredMacAddress = _selectedDevice != null ? _selectedDevice.MacAddress : null;
}
}
public LgServiceConfig Config { get; private set; }
private List<LgPreset> _lgPresets;
private string _lgPresetsFilename;
private LgTvApi _lgTvApi;
private string _dataDir;
private string _configFilename;
private bool _allowPowerOn;
private bool _justWokeUp;
private PnpDev _selectedDevice;
private string _rcButtonsFilename;
private List<LgPreset> _remoteControlButtons;
private List<LgApp> _lgApps = new List<LgApp>();
private JavaScriptSerializer _JsonSerializer = new JavaScriptSerializer();
private JavaScriptSerializer _JsonDeserializer = new JavaScriptSerializer();
private Dictionary<string, Func<bool>> _invokableActions = new Dictionary<string, Func<bool>>();
private bool _poweredOffByScreenSaver;
private int _poweredOffByScreenSaverProcessId;
private Task _monitorTask;
private int _monitorTaskCounter;
public LgService(string dataDir, bool allowPowerOn)
{
_dataDir = dataDir;
_allowPowerOn = allowPowerOn;
_invokableActions.Add("WOL", new Func<bool>(WakeSelectedDevice));
LgPreset.LgApps = _lgApps;
LoadConfig();
LoadPresets();
LoadRemoteControlButtons();
//foreach (var screen in Screen.AllScreens)
//{
// var name = screen.DeviceFriendlyName();
// if (name.Contains("LG"))
// {
// FriendlyScreenName = name;
// }
//}
var _ = RefreshDevices(afterStartUp: true);
}
~LgService()
{
GlobalSave();
}
public void GlobalSave()
{
SaveConfig();
SavePresets();
SaveRemoteControlButtons();
}
public List<LgPreset> GetPresets()
{
return _lgPresets;
}
public LgPreset CreateNewPreset()
{
var preset = new LgPreset();
var name = "New preset";
string fullname;
var number = 1;
do
{
fullname = $"{name} ({number})";
number++;
} while (_lgPresets.Any(x => x.name.Equals(fullname)));
preset.name = fullname;
return preset;
}
private void LoadPresets()
{
_lgPresetsFilename = Path.Combine(_dataDir, "LgPresets.json");
var toCopy = Path.Combine(Directory.GetCurrentDirectory(), "LgPresets.json");
if (!File.Exists(_lgPresetsFilename) && File.Exists(toCopy))
{
try
{
File.Copy(toCopy, _lgPresetsFilename);
}
catch (Exception e)
{
Logger.Error($"Error while copying {toCopy} to {_lgPresetsFilename}: {e.Message}");
}
}
if (File.Exists(_lgPresetsFilename))
{
var json = File.ReadAllText(_lgPresetsFilename);
_lgPresets = _JsonDeserializer.Deserialize<List<LgPreset>>(json);
}
else
{
_lgPresets = new List<LgPreset>();
}
}
private void LoadConfig()
{
_configFilename = Path.Combine(_dataDir, "LgConfig.json");
if (File.Exists(_configFilename))
{
Config = JsonConvert.DeserializeObject<LgServiceConfig>(File.ReadAllText(_configFilename));
}
else
{
Config = new LgServiceConfig();
}
}
private void LoadRemoteControlButtons()
{
var defaultButtons = GenerateDefaultRemoteControlButtons();
_rcButtonsFilename = Path.Combine(_dataDir, "LgRemoteControlButtons.json");
//if (File.Exists(_rcButtonsFilename))
//{
// var json = File.ReadAllText(_rcButtonsFilename);
// _remoteControlButtons = _JsonDeserializer.Deserialize<List<LgPreset>>(json);
// var missingButtons = defaultButtons.Where(b => !_remoteControlButtons.Any(x => x.name.Equals(b.name)));
// _remoteControlButtons.AddRange(missingButtons);
//}
//else
{
_remoteControlButtons = defaultButtons;
}
}
public List<LgPreset> GetRemoteControlButtons()
{
return _remoteControlButtons;
}
public Dictionary<string, Func<bool>> GetInvokableActions()
{
return _invokableActions;
}
private List<LgPreset> GenerateDefaultRemoteControlButtons()
{
var list = new List<LgPreset>();
for (var i = 1; i < 11; i++)
{
var button = i < 10 ? i : 0;
list.Add(GeneratePreset(button.ToString()));
}
list.Add(GeneratePreset("Settings", appId: "com.palm.app.settings", key: Keys.S));
list.Add(GeneratePreset("Vol +", step: "VOLUMEUP", key: Keys.VolumeUp));
list.Add(GeneratePreset("Vol -", step: "VOLUMEDOWN", key: Keys.VolumeDown));
list.Add(GeneratePreset("Mute", step: "MUTE", key: Keys.VolumeMute));
list.Add(GeneratePreset("Up", step: "UP", key: Keys.Up));
list.Add(GeneratePreset("Down", step: "DOWN", key: Keys.Down));
list.Add(GeneratePreset("Left", step: "LEFT", key: Keys.Left));
list.Add(GeneratePreset("Right", step: "RIGHT", key: Keys.Right));
list.Add(GeneratePreset("Enter", step: "ENTER", key: Keys.Enter));
list.Add(GeneratePreset("Back", step: "BACK", key: Keys.Back));
list.Add(GeneratePreset("Exit", step: "EXIT", key: Keys.Escape));
return list;
}
private LgPreset GeneratePreset(string name, string step = null, string appId = null, Keys key = Keys.None)
{
var preset = new LgPreset();
var cvt = new KeysConverter();
var shortcut = key != Keys.None ? (string)cvt.ConvertTo(key, typeof(string)) : null;
preset.name = name;
preset.appId = appId;
preset.shortcut = shortcut;
if (appId == null)
{
preset.steps.Add(step == null ? name : step);
}
return preset;
}
private void SavePresets()
{
try
{
var json = _JsonSerializer.Serialize(_lgPresets);
File.WriteAllText(_lgPresetsFilename, json);
}
catch (Exception e)
{
Logger.Error(e.ToLogString());
}
}
private void SaveConfig()
{
var json = JsonConvert.SerializeObject(Config);
File.WriteAllText(_configFilename, json);
}
private void SaveRemoteControlButtons()
{
var json = JsonConvert.SerializeObject(_remoteControlButtons);
File.WriteAllText(_rcButtonsFilename, json);
}
public async Task RefreshDevices(bool connect = true, bool afterStartUp = false)
{
var devices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
SetDevices(devices);
if (afterStartUp && SelectedDevice == null && Config.PowerOnAfterStartup && !string.IsNullOrEmpty(Config.PreferredMacAddress))
{
Logger.Debug("No device has been found, trying to wake it first...");
WakeSelectedDevice(Config.PreferredMacAddress);
await Task.Delay(4000);
await RefreshDevices();
return;
}
if (connect && SelectedDevice != null)
{
if (Config.PowerOnAfterStartup && _allowPowerOn)
{
var _ = WakeAndConnectToSelectedDeviceWithRetries();
}
else
{
var _ = ConnectToSelectedDevice();
}
}
}
private void SetDevices(List<PnpDev> devices)
{
Devices = devices;
if (Devices?.Count > 0)
{
var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0];
SelectedDevice = preferredDevice;
}
}
public async Task<bool> ConnectToSelectedDevice(int retries = 3)
{
try
{
DisposeConnection();
_lgTvApi = await LgTvApi.CreateLgTvApi(SelectedDevice.IpAddress, retries);
//_lgTvApi.GetServiceList();
return _lgTvApi != null;
}
catch (Exception ex)
{
string logMessage = ex.ToLogString(Environment.StackTrace);
Logger.Error($"Error while connecting to {SelectedDevice.IpAddress}: {logMessage}");
return false;
}
}
private async Task<bool> Connected(bool reconnect = false)
{
if (SelectedDevice == null)
{
Logger.Debug("Cannot apply LG-preset: no device has been selected");
return false;
}
if (reconnect || !IsConnected() || !string.Equals(_lgTvApi.GetIpAddress(), SelectedDevice.IpAddress))
{
if (!await ConnectToSelectedDevice())
{
Logger.Debug("Cannot apply LG-preset: no connection could be made");
return false;
}
}
return true;
}
private bool IsConnected()
{
return !_lgTvApi?.ConnectionClosed ?? false;
}
public async Task<bool> ApplyPreset(LgPreset preset, bool reconnect = false)
{
var hasApp = !string.IsNullOrEmpty(preset.appId);
var hasWOL = preset.steps.Any(s => s.Equals("WOL", StringComparison.OrdinalIgnoreCase));
if (hasWOL)
{
var connected = await WakeAndConnectToSelectedDevice(0);
if (!connected)
{
return false;
}
}
for (var tries = 0; tries <= 1; tries++)
{
if (!await Connected(reconnect || tries == 1))
{
return false;
}
if (hasApp)
{
try
{
await _lgTvApi.LaunchApp(preset.appId);
}
catch (Exception ex)
{
string logMessage = ex.ToLogString(Environment.StackTrace);
Logger.Error("Error while launching app: " + logMessage);
if (tries == 0)
{
continue;
}
return false;
}
if (_justWokeUp)
{
_justWokeUp = false;
await Task.Delay(1000);
}
}
if (preset.steps.Any())
{
if (hasApp)
{
await Task.Delay(1500);
}
try
{
await ExecuteSteps(_lgTvApi, preset);
}
catch (Exception ex)
{
string logMessage = ex.ToLogString(Environment.StackTrace);
Logger.Error("Error while executing steps: " + logMessage);
if (tries == 0)
{
continue;
}
return false;
}
}
return true;
}
return true;
}
public async Task PowerOn()
{
var mouse = await _lgTvApi.GetMouse();
mouse.SendButton(ButtonType.POWER);
}
private async Task ExecuteSteps(LgTvApi api, LgPreset preset)
{
var mouse = await api.GetMouse();
foreach (var step in preset.steps)
{
var keySpec = step.Split(':');
var key = keySpec[0].ToUpper();
if (_invokableActions.ContainsKey(key))
{
continue;
}
if (keySpec.Length == 2)
{
SendKey(mouse, key, int.Parse(keySpec[1]));
}
else
{
SendKey(mouse, key);
}
}
}
private void SendKey(LgWebOsMouseService mouse, string key, int delay = 180)
{
if (key.Length == 1 && int.TryParse(key, out _))
{
key = "_" + key;
}
var button = (ButtonType)Enum.Parse(typeof(ButtonType), key);
mouse.SendButton(button);
Thread.Sleep(delay);
}
public async Task<LgWebOsMouseService> GetMouseAsync()
{
return await _lgTvApi.GetMouse();
}
public async Task RefreshApps(bool force = false)
{
if (SelectedDevice == null)
{
Logger.Debug("Cannot refresh apps: no device has been selected");
}
if (!await Connected(force))
{
Logger.Debug("Cannot refresh apps: no connection could be made");
}
_lgApps.Clear();
_lgApps.AddRange(await _lgTvApi.GetApps(force));
}
public List<LgApp> GetApps()
{
return _lgApps;
}
internal void DisposeConnection()
{
if (_lgTvApi != null)
{
_lgTvApi.Dispose();
_lgTvApi = null;
}
}
internal async Task<bool> PowerOff()
{
if (!await Connected(true))
{
return false;
}
await _lgTvApi.TurnOff();
return true;
}
internal bool WakeSelectedDevice()
{
return WakeSelectedDevice(SelectedDevice?.MacAddress ?? Config.PreferredMacAddress);
}
internal bool WakeSelectedDevice(string macAddress)
{
var result = false;
macAddress = string.IsNullOrEmpty(macAddress) ? SelectedDevice?.MacAddress : macAddress;
if (macAddress != null)
{
result = WOL.WakeFunction(macAddress, Config.UseOldNpcapWol);
_justWokeUp = true;
}
else
{
Logger.Debug("Cannot wake device: no device has been selected or there is no preferred MAC-address stored in the configuration");
}
return result;
}
internal void WakeAfterResume()
{
if (Config.PowerOnAfterResume)
{
var _ = WakeAndConnectToSelectedDeviceWithRetries();
}
}
private async Task<bool> WakeAndConnectToSelectedDeviceWithRetries(bool checkUserSession = true)
{
if (checkUserSession && !UserSessionInfo.UserLocalSession)
{
Logger.Debug($"WakeAndConnectToSelectedDeviceWithRetries: not waking because session info indicates no local session");
return false;
}
var wakeDelay = 0;
var maxRetries = Config.PowerOnRetries <= 1 ? 5 : Config.PowerOnRetries;
var result = false;
for (var retry = 0; retry < maxRetries && !result; retry++)
{
Logger.Debug($"WakeAndConnectToSelectedDeviceWithRetries: attempt {retry + 1} of {maxRetries}...");
var ms = DateTimeOffset.Now.ToUnixTimeMilliseconds();
result = await WakeAndConnectToSelectedDevice(retry == 0 ? wakeDelay : 0);
ms = DateTimeOffset.Now.ToUnixTimeMilliseconds() - ms;
if (!result)
{
var delay = 2000 - ms;
if (delay > 0)
{
await Task.Delay((int)delay);
}
}
}
return result;
}
private async Task<bool> WakeAndConnectToSelectedDevice(int wakeDelay = 5000, int connectDelay = 500)
{
try
{
//if (wakeDelay == 0)
//{
// if (await ConnectToSelectedDevice())
// {
// Logger.Debug("Already connected, no wake needed?");
// return;
// };
//}
await Task.Delay(wakeDelay);
var result = WakeSelectedDevice();
if (!result)
{
Logger.Debug("WOL failed");
return false;
}
Logger.Debug("WOL succeeded");
await Task.Delay(connectDelay);
result = await ConnectToSelectedDevice();
Logger.Debug("Connect succeeded: " + result);
return result;
}
catch (Exception e)
{
Logger.Error("WakeAndConnectToSelectedDevice: " + e.ToLogString());
return false;
}
}
public void InstallEventHandlers()
{
SystemEvents.PowerModeChanged -= PowerModeChanged;
SystemEvents.PowerModeChanged += PowerModeChanged;
//SystemEvents.SessionEnded -= SessionEnded;
//SystemEvents.SessionEnded += SessionEnded;
UserSessionInfo.UserSessionSwitch -= SessionSwitched;
UserSessionInfo.UserSessionSwitch += SessionSwitched;
MonitorProcesses();
}
private void SessionSwitched(bool toLocal)
{
if (toLocal)
{
if (_poweredOffByScreenSaver)
{
Logger.Debug("User switched to local and screen was powered off due to screensaver: waking up");
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
var _ = WakeAndConnectToSelectedDeviceWithRetries();
}
else
{
Logger.Debug("User switched to local but screen was not powered off by screensaver: NOT waking up");
}
}
}
private void PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
var powerOn = e.Mode == PowerModes.Resume;
Logger.Debug($"PowerModeChanged: {e.Mode}");
if (powerOn)
{
DisposeConnection();
WakeAfterResume();
return;
}
if (Config.PowerOffOnStandby)
{
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS | NativeConstants.ES_SYSTEM_REQUIRED | NativeConstants.ES_AWAYMODE_REQUIRED);
try
{
Logger.Debug("Powering off tv...");
var task = PowerOff();
Utils.WaitForTask(task);
Logger.Debug("Done powering off tv");
}
finally
{
NativeMethods.SetThreadExecutionState(NativeConstants.ES_CONTINUOUS);
}
}
}
private void SessionEnded(object sender, SessionEndedEventArgs e)
{
//if (e.Reason == SessionEndReasons.SystemShutdown)
//{
// Logger.Debug($"SessionEnded: {e.Reason}");
// if (Config.PowerOffOnShutdown)
// {
// PowerOff();
// }
//}
}
private void MonitorProcesses()
{
if (Config.PowerSwitchOnScreenSaver && _monitorTask == null)
{
_monitorTask = CheckProcesses();
}
else if (!Config.PowerSwitchOnScreenSaver && _monitorTask != null)
{
_monitorTask = null;
}
}
public async Task CheckProcesses()
{
var wasConnected = IsConnected();
_monitorTaskCounter++;
var validCounter = _monitorTaskCounter;
while (Config.PowerSwitchOnScreenSaver && validCounter == _monitorTaskCounter)
{
await Task.Delay(500);
if (_poweredOffByScreenSaver && !UserSessionInfo.UserLocalSession)
{
continue;
}
var processes = Process.GetProcesses();
if (_poweredOffByScreenSaver)
{
if (processes.Any(p => p.Id == _poweredOffByScreenSaverProcessId))
{
continue;
}
Logger.Debug("Screensaver stopped, waking");
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
var _ = WakeAndConnectToSelectedDeviceWithRetries();
continue;
}
if (!IsConnected())
{
if (wasConnected)
{
Logger.Debug("TV was connected, but not any longer");
wasConnected = false;
}
continue;
}
if (!wasConnected)
{
Logger.Debug("TV was not connected, but connection has now been established");
wasConnected = true;
}
foreach (var process in processes)
{
var name = process.ProcessName.ToLowerInvariant();
if (name.EndsWith(".scr"))
{
var parent = process.Parent();
if (parent?.ProcessName.Contains("winlogon") ?? false)
{
Logger.Debug($"Screensaver started: {process.ProcessName}, parent: {parent.ProcessName}");
try
{
Logger.Debug("Powering off tv because of screensaver");
_poweredOffByScreenSaver = await PowerOff();
_poweredOffByScreenSaverProcessId = process.Id;
}
catch (Exception e)
{
Logger.Error("CheckProcesses: can't power off: " + e.ToLogString());
}
if (!IsConnected())
{
_poweredOffByScreenSaver = false;
}
break;
}
else
{
Logger.Debug($"Screensaver started: {process.ProcessName}, but invalid parent: {parent?.ProcessName ?? "no parent"}");
}
}
}
}
}
}
}
-32
View File
@@ -1,32 +0,0 @@
namespace ColorControl
{
class LgServiceConfig
{
public bool PowerOnAfterStartup { get; set; }
public bool PowerOnAfterResume { get; set; }
public bool PowerOffOnShutdown { get; set; }
public bool PowerOffOnStandby { get; set; }
public bool PowerSwitchOnScreenSaver { 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 LgServiceConfig()
{
PowerOnDelayAfterResume = 5000;
DeviceSearchKey = "[LG]";
PowerOnRetries = 10;
}
}
}
+1984 -538
View File
File diff suppressed because it is too large. Load diff
+2633 -332
View File
File diff suppressed because it is too large. Load diff
+16 -61
View File
@@ -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">
@@ -123,9 +63,24 @@
<metadata name="mnuAmdPresets.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>278, 17</value>
</metadata>
<metadata name="mnuLgExpert.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>583, 17</value>
</metadata>
<metadata name="mnuLgButtons.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>147, 17</value>
</metadata>
<metadata name="mnuGameOptions.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>705, 16</value>
</metadata>
<metadata name="mnuGameActions.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>414, 15</value>
</metadata>
<metadata name="mnuGameAddStep.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>416, 50</value>
</metadata>
<metadata name="mnuNvSettings.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>15, 47</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
-34
View File
@@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
{
class MessageForms
{
public static Form MainForm;
public static void WarningOk(string text)
{
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
public static void ErrorOk(string text)
{
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
public static void InfoOk(string text)
{
MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
public static DialogResult QuestionYesNo(string text)
{
return MessageBox.Show(text, MainForm?.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Information);
}
}
}
+564
View File
@@ -0,0 +1,564 @@
using NWin32;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace ColorControl
{
// This class takes care of wrapping "Connecting and Configuring Displays(CCD) Win32 API"
internal static class CCD
{
public enum DisplayTopology
{
Internal,
External,
Extend,
Clone
}
public static DisplayTopology GetDisplayTopology()
{
uint numPathArrayElements;
uint numModeInfoArrayElements;
NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.DatabaseCurrent, out numPathArrayElements, out numModeInfoArrayElements);
var pathArray = new DisplayConfigPathInfo[numPathArrayElements];
var modeArray = new DisplayConfigModeInfo[numModeInfoArrayElements];
DisplayConfigTopologyId displayTopology;
NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref numPathArrayElements, pathArray, ref numModeInfoArrayElements, modeArray, out displayTopology);
switch (displayTopology)
{
case DisplayConfigTopologyId.External: return DisplayTopology.External;
case DisplayConfigTopologyId.Internal: return DisplayTopology.Internal;
case DisplayConfigTopologyId.Extend: return DisplayTopology.Extend;
}
return DisplayTopology.Clone;
}
public static void SetDisplayTopology(DisplayTopology displayTopology)
{
switch (displayTopology)
{
case DisplayTopology.External:
NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyExternal));
break;
case DisplayTopology.Internal:
NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyInternal));
break;
case DisplayTopology.Extend:
NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyExtend));
break;
case DisplayTopology.Clone:
NativeMethods.SetDisplayConfig(0, null, 0, null, (SdcFlags.Apply | SdcFlags.TopologyClone));
break;
}
}
public static void SetHDRState(bool enabled, string displayName = null)
{
uint pathCount, modeCount;
var err = NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.OnlyActivePaths, out pathCount, out modeCount);
if (err == NativeConstants.ERROR_SUCCESS)
{
var pathsArray = new DisplayConfigPathInfo[pathCount];
var modesArray = new DisplayConfigModeInfo[modeCount];
DisplayConfigTopologyId displayTopology;
err = NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref pathCount, pathsArray, ref modeCount, modesArray, out displayTopology);
if (err == NativeConstants.ERROR_SUCCESS)
{
foreach (var path in pathsArray)
{
// get display name
var info = new DISPLAYCONFIG_SOURCE_DEVICE_NAME();
info.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
info.header.size = Marshal.SizeOf<DISPLAYCONFIG_SOURCE_DEVICE_NAME>();
info.header.adapterId = path.sourceInfo.adapterId;
info.header.id = path.sourceInfo.id;
err = NativeMethods.DisplayConfigGetDeviceInfo(ref info);
if (err != NativeConstants.ERROR_SUCCESS)
{
break;
}
var deviceName = info.viewGdiDeviceName;
if (displayName != null && !EqualDisplayNames(deviceName, displayName))
{
continue;
}
var setpacket = new DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO();
setpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
setpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
setpacket.header.size = Marshal.SizeOf<DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO>(); ;
var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
requestpacket.header.size = Marshal.SizeOf<DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO>(); ;
for (int i = 0; i < modeCount; i++)
{
if (modesArray[i].infoType == DisplayConfigModeInfoType.Target)
{
setpacket.header.adapterId = modesArray[i].adapterId;
setpacket.header.id = modesArray[i].id;
requestpacket.header.adapterId = modesArray[i].adapterId;
requestpacket.header.id = modesArray[i].id;
if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS)
{
if (requestpacket.advancedColorSupported)
{
setpacket.enableAdvancedColor = enabled ? 1U : 0;
NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket);
}
}
}
}
}
}
}
if (err != NativeConstants.ERROR_SUCCESS)
{
throw new Win32Exception(err);
}
}
private static bool EqualDisplayNames(string displayName1, string displayName2)
{
displayName1 = displayName1.Replace("\\", string.Empty);
displayName2 = displayName2.Replace("\\", string.Empty);
return displayName1.Equals(displayName2, StringComparison.OrdinalIgnoreCase);
}
private enum DISPLAYCONFIG_MODE_INFO_TYPE
{
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3,
}
private enum DISPLAYCONFIG_DEVICE_INFO_TYPE
{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3,
DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4,
DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6,
DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7,
DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8,
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9,
DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10,
DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11,
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
public DISPLAYCONFIG_DEVICE_INFO_TYPE type;
public int size;
public LUID adapterId;
public uint id;
}
[StructLayout(LayoutKind.Sequential)]
struct LUID
{
uint LowPart;
uint HighPart;
}
[Flags]
enum DisplayConfigVideoOutputTechnology : uint
{
Other = 4294967295, // -1
Hd15 = 0,
Svideo = 1,
CompositeVideo = 2,
ComponentVideo = 3,
Dvi = 4,
Hdmi = 5,
Lvds = 6,
DJpn = 8,
Sdi = 9,
DisplayportExternal = 10,
DisplayportEmbedded = 11,
UdiExternal = 12,
UdiEmbedded = 13,
Sdtvdongle = 14,
Internal = 0x80000000,
ForceUint32 = 0xFFFFFFFF
}
#region SdcFlags enum
[Flags]
enum SdcFlags : uint
{
Zero = 0,
TopologyInternal = 0x00000001,
TopologyClone = 0x00000002,
TopologyExtend = 0x00000004,
TopologyExternal = 0x00000008,
TopologySupplied = 0x00000010,
UseSuppliedDisplayConfig = 0x00000020,
Validate = 0x00000040,
Apply = 0x00000080,
NoOptimization = 0x00000100,
SaveToDatabase = 0x00000200,
AllowChanges = 0x00000400,
PathPersistIfRequired = 0x00000800,
ForceModeEnumeration = 0x00001000,
AllowPathOrderChanges = 0x00002000,
UseDatabaseCurrent = TopologyInternal | TopologyClone | TopologyExtend | TopologyExternal
}
[Flags]
enum DisplayConfigFlags : uint
{
Zero = 0x0,
PathActive = 0x00000001
}
[Flags]
enum DisplayConfigSourceStatus
{
Zero = 0x0,
InUse = 0x00000001
}
[Flags]
enum DisplayConfigTargetStatus : uint
{
Zero = 0x0,
InUse = 0x00000001,
FORCIBLE = 0x00000002,
ForcedAvailabilityBoot = 0x00000004,
ForcedAvailabilityPath = 0x00000008,
ForcedAvailabilitySystem = 0x00000010,
}
[Flags]
enum DisplayConfigRotation : uint
{
Zero = 0x0,
Identity = 1,
Rotate90 = 2,
Rotate180 = 3,
Rotate270 = 4,
ForceUint32 = 0xFFFFFFFF
}
[Flags]
enum DisplayConfigPixelFormat : uint
{
Zero = 0x0,
Pixelformat8Bpp = 1,
Pixelformat16Bpp = 2,
Pixelformat24Bpp = 3,
Pixelformat32Bpp = 4,
PixelformatNongdi = 5,
PixelformatForceUint32 = 0xffffffff
}
[Flags]
enum DisplayConfigScaling : uint
{
Zero = 0x0,
Identity = 1,
Centered = 2,
Stretched = 3,
Aspectratiocenteredmax = 4,
Custom = 5,
Preferred = 128,
ForceUint32 = 0xFFFFFFFF
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigRational
{
uint numerator;
uint denominator;
}
[Flags]
enum DisplayConfigScanLineOrdering : uint
{
Unspecified = 0,
Progressive = 1,
Interlaced = 2,
InterlacedUpperfieldfirst = Interlaced,
InterlacedLowerfieldfirst = 3,
ForceUint32 = 0xFFFFFFFF
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigPathInfo
{
public DisplayConfigPathSourceInfo sourceInfo;
public DisplayConfigPathTargetInfo targetInfo;
public uint flags;
}
[Flags]
enum DisplayConfigModeInfoType : uint
{
Zero = 0,
Source = 1,
Target = 2,
ForceUint32 = 0xFFFFFFFF
}
[StructLayout(LayoutKind.Explicit)]
struct DisplayConfigModeInfo
{
[FieldOffset(0)]
public DisplayConfigModeInfoType infoType;
[FieldOffset(4)]
public uint id;
[FieldOffset(8)]
public LUID adapterId;
[FieldOffset(16)]
public DisplayConfigTargetMode targetMode;
[FieldOffset(16)]
public DisplayConfigSourceMode sourceMode;
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfig2DRegion
{
uint cx;
uint cy;
}
[Flags]
enum D3DmdtVideoSignalStandard : uint
{
Uninitialized = 0,
VesaDmt = 1,
VesaGtf = 2,
VesaCvt = 3,
Ibm = 4,
Apple = 5,
NtscM = 6,
NtscJ = 7,
Ntsc443 = 8,
PalB = 9,
PalB1 = 10,
PalG = 11,
PalH = 12,
PalI = 13,
PalD = 14,
PalN = 15,
PalNc = 16,
SecamB = 17,
SecamD = 18,
SecamG = 19,
SecamH = 20,
SecamK = 21,
SecamK1 = 22,
SecamL = 23,
SecamL1 = 24,
Eia861 = 25,
Eia861A = 26,
Eia861B = 27,
PalK = 28,
PalK1 = 29,
PalL = 30,
PalM = 31,
Other = 255
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigVideoSignalInfo
{
long pixelRate;
DisplayConfigRational hSyncFreq;
DisplayConfigRational vSyncFreq;
DisplayConfig2DRegion activeSize;
DisplayConfig2DRegion totalSize;
D3DmdtVideoSignalStandard videoStandard;
DisplayConfigScanLineOrdering ScanLineOrdering;
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigTargetMode
{
DisplayConfigVideoSignalInfo targetVideoSignalInfo;
}
[StructLayout(LayoutKind.Sequential)]
struct PointL
{
int x;
int y;
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigSourceMode
{
uint width;
uint height;
DisplayConfigPixelFormat pixelFormat;
PointL position;
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigPathSourceInfo
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
public DisplayConfigSourceStatus statusFlags;
}
[StructLayout(LayoutKind.Sequential)]
struct DisplayConfigPathTargetInfo
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
public DisplayConfigVideoOutputTechnology outputTechnology;
public DisplayConfigRotation rotation;
public DisplayConfigScaling scaling;
public DisplayConfigRational refreshRate;
public DisplayConfigScanLineOrdering scanLineOrdering;
public bool targetAvailable;
public DisplayConfigTargetStatus statusFlags;
}
[Flags]
enum QueryDisplayFlags : uint
{
Zero = 0x0,
AllPaths = 0x00000001,
OnlyActivePaths = 0x00000002,
DatabaseCurrent = 0x00000004
}
[Flags]
enum DisplayConfigTopologyId : uint
{
Zero = 0x0,
Internal = 0x00000001,
Clone = 0x00000002,
Extend = 0x00000004,
External = 0x00000008,
ForceUint32 = 0xFFFFFFFF
}
private enum DISPLAYCONFIG_COLOR_ENCODING
{
DISPLAYCONFIG_COLOR_ENCODING_RGB = 0,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR444 = 1,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR422 = 2,
DISPLAYCONFIG_COLOR_ENCODING_YCBCR420 = 3,
DISPLAYCONFIG_COLOR_ENCODING_INTENSITY = 4,
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public uint value;
public DISPLAYCONFIG_COLOR_ENCODING colorEncoding;
public int bitsPerColorChannel;
public bool advancedColorSupported => (value & 0x1) == 0x1;
public bool advancedColorEnabled => (value & 0x2) == 0x2;
public bool wideColorEnforced => (value & 0x4) == 0x4;
public bool advancedColorForceDisabled => (value & 0x8) == 0x8;
}
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public uint enableAdvancedColor;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS
{
public uint value;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_TARGET_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags;
public DisplayConfigVideoOutputTechnology outputTechnology;
public ushort edidManufactureId;
public ushort edidProductCodeId;
public uint connectorInstance;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string monitorFriendlyDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string monitorDevicePat;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string viewGdiDeviceName;
}
#endregion
static class NativeMethods
{
[DllImport("User32.dll")]
public static extern int GetDisplayConfigBufferSizes(QueryDisplayFlags flags, out uint numPathArrayElements, out uint numModeInfoArrayElements);
[DllImport("User32.dll")]
public static extern int SetDisplayConfig(uint numPathArrayElements, [In] DisplayConfigPathInfo[] pathArray, uint numModeInfoArrayElements, [In] DisplayConfigModeInfo[] modeInfoArray, SdcFlags flags);
[DllImport("User32.dll")]
public static extern int QueryDisplayConfig(QueryDisplayFlags flags, ref uint numPathArrayElements, [In, Out] DisplayConfigPathInfo[] pathInfoArray, ref uint modeInfoArrayElements, [In, Out] DisplayConfigModeInfo[] modeInfoArray, out DisplayConfigTopologyId id);
[DllImport("user32")]
public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO requestPacket);
[DllImport("user32")]
public static extern int DisplayConfigSetDeviceInfo(ref DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO setPacket);
[DllImport("user32")]
public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_TARGET_DEVICE_NAME requestPacket);
[DllImport("user32")]
public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket);
}
}
}
@@ -1,11 +1,14 @@
using Windows.Devices.Enumeration;
using Newtonsoft.Json;
using Windows.Devices.Enumeration;
using Windows.Devices.Enumeration.Pnp;
namespace ColorControl
{
class PnpDev
{
[JsonIgnore]
public DeviceInformation DevInfo { get; private set; }
[JsonIgnore]
public PnpObject PnpObject { get; private set; }
public string Name { get; private set; }
@@ -13,18 +16,27 @@ namespace ColorControl
public string MacAddress { get; private set; }
public PnpDev(DeviceInformation devInfo, PnpObject pnpObject, string name, string ipAddress, string macAddress)
[JsonConstructor]
public PnpDev(string name, string ipAddress, string macAddress)
{
DevInfo = devInfo;
PnpObject = pnpObject;
Name = name;
IpAddress = ipAddress;
MacAddress = macAddress;
}
public PnpDev(DeviceInformation devInfo, PnpObject pnpObject, string name, string ipAddress, string macAddress) : this(name, ipAddress, macAddress)
{
DevInfo = devInfo;
PnpObject = pnpObject;
}
public override string ToString()
{
return Name + " (" + IpAddress + ")";
return (IsCustom() ? "Custom: " : "Auto detect: ") + $"{Name}" + (!string.IsNullOrEmpty(IpAddress) ? $"({IpAddress})" : string.Empty);
}
public bool IsCustom()
{
return DevInfo == null;
}
}
}
@@ -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)
{
File renamed without changes.
+135
View File
@@ -0,0 +1,135 @@
using NWin32.NativeTypes;
using System;
using System.Runtime.InteropServices;
namespace ColorControl.Native
{
public static class WinApi
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData; // in C/C++ this is an UINT_PTR, not an UINT
public int cbData;
public IntPtr lpData;
}
[StructLayout(LayoutKind.Sequential)]
public struct CopyData : IDisposable
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, ref CopyData target,
SendMessageTimeoutFlags fuFlags, uint uTimeout, out UIntPtr lpdwResult);
[Flags]
enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0,
SMTO_BLOCK = 0x1,
SMTO_ABORTIFHUNG = 0x2,
SMTO_NOTIMEOUTIFNOTHUNG = 0x8
}
const uint WM_COPYDATA = 0x4A;
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
public void Dispose()
{
if (lpData != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(lpData);
lpData = IntPtr.Zero;
cbData = 0;
}
}
public string AsAnsiString
{
get { return Marshal.PtrToStringAnsi(lpData, cbData); }
}
public string AsUnicodeString
{
get { return Marshal.PtrToStringUni(lpData); }
}
public static CopyData CreateForString(int dwData, string value, bool Unicode = false)
{
var result = new CopyData();
result.dwData = (IntPtr)dwData;
result.lpData = Unicode ? Marshal.StringToCoTaskMemUni(value) : Marshal.StringToCoTaskMemAnsi(value);
result.cbData = value.Length + 2;
return result;
}
public static UIntPtr Send(IntPtr targetHandle, int dwData, string value, uint timeoutMs = 1000, bool Unicode = false)
{
var cds = CreateForString(dwData, value, Unicode);
UIntPtr result;
SendMessageTimeout(targetHandle, WM_COPYDATA, IntPtr.Zero, ref cds, SendMessageTimeoutFlags.SMTO_NORMAL, timeoutMs, out result);
cds.Dispose();
return result;
}
}
public enum MessageFilterInfo : uint
{
None = 0, AlreadyAllowed = 1, AlreadyDisAllowed = 2, AllowedHigher = 3
};
public enum ChangeWindowMessageFilterExAction : uint
{
Reset = 0, Allow = 1, DisAllow = 2
};
[StructLayout(LayoutKind.Sequential)]
public struct CHANGEFILTERSTRUCT
{
public uint size;
public MessageFilterInfo info;
}
public const int ENUM_CURRENT_SETTINGS = -1;
public const int ENUM_REGISTRY_SETTINGS = -2;
public static int PBT_POWERSETTINGCHANGE = 32787;
public delegate void PCREATE_PROCESS_NOTIFY_ROUTINE(IntPtr ParentId, IntPtr ProcessId, bool Create);
[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);
[DllImport("user32.dll")]
public static extern bool EnumDisplaySettingsA(string deviceName, int modeNum, out DEVMODEA devMode);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
public static extern bool AttachConsole(int processId);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FreeConsole();
[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("user32.dll", SetLastError = true)]
public static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, ChangeWindowMessageFilterExAction action, ref CHANGEFILTERSTRUCT changeInfo);
}
}
@@ -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;
}
}
}
}
-469
View File
@@ -1,469 +0,0 @@
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;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Windows.Graphics.Display;
namespace ColorControl
{
enum NvDitherBits
{
[Description("6-bit")]
Bits6 = 0,
[Description("8-bit")]
Bits8 = 1,
[Description("10-bit")]
Bits10 = 2
}
enum NvDitherMode
{
[Description("Spatial Dynamic")]
SpatialDynamic = 0,
[Description("Spatial Static")]
SpatialStatic = 1,
[Description("Spatial Dynamic 2x2")]
SpatialDynamic2x2 = 2,
[Description("Spatial Static 2x2")]
SpatialStatic2x2 = 3,
[Description("Temporal")]
Temporal = 4
}
class NvService : GraphicsService
{
[DllImport(@"nvapi64", EntryPoint = @"nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl,
PreserveSig = true)]
private static extern IntPtr NvAPI64_QueryInterface(uint interfaceId);
public delegate long NvAPI_Disp_SetDitherControl(
[In] PhysicalGPUHandle physicalGpu,
[In] uint OutputId,
[In] uint state,
[In] uint bits,
[In] uint mode
);
public delegate long NvAPI_Disp_GetDitherControl(
[In] PhysicalGPUHandle physicalGpu,
[In] uint OutputId,
[In] IntPtr ditherControl);
[StructLayout(LayoutKind.Sequential)]
public struct NV_GPU_DITHER_CONTROL_V1
{
public int version;
public uint state;
public uint bits;
public uint mode;
};
private List<NvPreset> _presets;
private Display _currentDisplay;
private NvPreset _lastAppliedPreset;
public NvService(string dataPath) : base(dataPath)
{
var converter = new ColorDataConverter();
_JsonDeserializer.RegisterConverters(new[] { converter });
LoadPresets();
}
public Display GetCurrentDisplay()
{
if (_currentDisplay == null)
{
_currentDisplay = Display.GetDisplays()[0];
}
return _currentDisplay;
}
public bool HasDisplaysAttached()
{
try
{
return DisplayDevice.GetGDIPrimaryDisplayDevice() != null;
}
catch (Exception)
{
return false;
}
}
public void GlobalSave()
{
SavePresets();
}
public List<NvPreset> GetPresets()
{
return _presets;
}
private void LoadPresets()
{
_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();
}
}
private void SavePresets()
{
try
{
var json = _JsonSerializer.Serialize(_presets);
File.WriteAllText(_presetsFilename, json);
}
catch (Exception e)
{
Logger.Error(e.ToLogString());
}
}
private void SetCurrentDisplay(NvPreset preset)
{
if (preset.primaryDisplay)
{
var displayId = DisplayDevice.GetGDIPrimaryDisplayDevice().DisplayId;
_currentDisplay = Display.GetDisplays().FirstOrDefault(x => x.DisplayDevice.DisplayId == displayId);
}
else
{
_currentDisplay = Display.GetDisplays().FirstOrDefault(x => x.Name.Equals(preset.displayName));
}
}
public bool ApplyPreset(NvPreset preset, Config config)
{
var result = true;
if (!HasDisplaysAttached())
{
return false;
}
SetCurrentDisplay(preset);
var hdrEnabled = IsHDREnabled();
var newHdrEnabled = preset.applyHDR && (preset.HDREnabled || (preset.toggleHDR && !hdrEnabled));
var applyHdr = preset.applyHDR && (preset.toggleHDR || preset.HDREnabled != hdrEnabled);
if (preset.applyColorData && (ColorDataDiffers(preset.colorData) || (!newHdrEnabled && preset.applyColorData && preset.colorData.Colorimetry != ColorDataColorimetry.Auto)))
{
var display = GetCurrentDisplay();
if (hdrEnabled)
{
SetHDRState(display, false);
applyHdr = false;
}
try
{
display.DisplayDevice.SetColorData(preset.colorData);
}
catch (Exception ex)
{
Logger.Error($"SetColorData threw an exception: {ex.Message}");
result = false;
}
if (hdrEnabled && newHdrEnabled)
{
SetHDRState(display, true);
applyHdr = false;
}
}
if (applyHdr)
{
var display = GetCurrentDisplay();
var colorData = preset.applyColorData ? preset.colorData : display.DisplayDevice.CurrentColorData;
SetHDRState(display, newHdrEnabled, colorData);
}
if (preset.applyRefreshRate)
{
if (!SetRefreshRate(preset.refreshRate))
{
result = false;
}
}
if (preset.applyDithering)
{
if (!SetDithering(preset.ditheringEnabled, preset: preset))
{
result = false;
}
}
_lastAppliedPreset = preset;
return result;
}
private ColorData GetCurrentColorData(Display display)
{
try
{
return display.DisplayDevice.CurrentColorData;
}
catch (Exception e)
{
Logger.Error("Error while reading current color data: " + e.Message);
return new ColorData();
}
}
private bool ColorDataDiffers(ColorData colorData)
{
var display = GetCurrentDisplay();
var currentColorData = GetCurrentColorData(display);
var settingRange = colorData.DynamicRange == ColorDataDynamicRange.Auto ? colorData.ColorFormat == ColorDataFormat.RGB ? ColorDataDynamicRange.VESA : ColorDataDynamicRange.CEA : colorData.DynamicRange;
var settingSpace = colorData.Colorimetry;
return colorData.ColorFormat != currentColorData.ColorFormat || colorData.ColorDepth != currentColorData.ColorDepth || settingRange != currentColorData.DynamicRange || settingSpace != currentColorData.Colorimetry;
}
private void SetHDRState(Display display, bool enabled, ColorData colorData = null)
{
var newMaster = new MasteringDisplayColorData();
var hdr = new HDRColorData(enabled ? ColorDataHDRMode.UHDA : ColorDataHDRMode.Off, newMaster, colorData?.ColorFormat, colorData?.DynamicRange, colorData?.ColorDepth);
display.DisplayDevice.SetHDRColorData(hdr);
// HDR will not always be disabled this way, then we can only disable it through the display settings
if (!enabled && IsHDREnabled())
{
ToggleHDR();
}
else if (enabled)
{
// Currectly there seems to be a bug that after enabling HDR via NVAPI, some settings are only applied upon opening the Display Settings...
////OpenDisplaySettings();
}
}
public bool SetDithering(bool enabled, uint bits = 1, uint mode = 4, NvPreset preset = null)
{
var result = true;
var ptr = NvAPI64_QueryInterface(0xDF0DFCDD);
if (ptr != IntPtr.Zero)
{
var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_SetDitherControl)) as NvAPI_Disp_SetDitherControl;
var displayDevice = GetCurrentDisplay().DisplayDevice;
var gpuHandle = displayDevice.PhysicalGPU.Handle;
var displayId = displayDevice.DisplayId;
if (preset != null)
{
bits = preset.ditheringBits;
mode = preset.ditheringMode;
}
var resultValue = delegateValue(gpuHandle, displayId, (uint)(enabled ? 1 : 2), bits, mode);
if (resultValue != 0)
{
Logger.Error($"Could not set dithering because NvAPI_Disp_SetDitherControl returned a non-zero return code: {resultValue}");
result = false;
}
}
else
{
Logger.Error($"Could not set dithering because the function NvAPI_Disp_SetDitherControl could not be found");
result = false;
}
return result;
}
public bool GetDithering()
{
// Requires elevation, too bad :(
//Utils.GetRegistryKeyValue(@"SYSTEM\CurrentControlSet\Services\nvlddmkm\State\DisplayDatabase", "DitherRegistryKey");
/*
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 gpuHandle = displayDevice.PhysicalGPU.Handle;
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);
if (result != 0)
{
Logger.Error($"Could not get dithering because NvAPI_Disp_GetDitherControl returned a non-zero return code: {result}");
}
return info.state == 1;
}
*/
return false;
}
public bool SetRefreshRate(uint refreshRate)
{
var display = GetCurrentDisplay();
var timing = display.DisplayDevice.CurrentTiming;
if (timing.Extra.RefreshRate == refreshRate)
{
return true;
}
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
return SetRefreshRateInternal(display.Name, refreshRate, portrait, timing.HorizontalVisible, timing.VerticalVisible);
}
public List<uint> GetAvailableRefreshRates(NvPreset preset = null)
{
if (preset != null)
{
SetCurrentDisplay(preset);
}
var display = GetCurrentDisplay();
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
var timing = display.DisplayDevice.CurrentTiming;
return GetAvailableRefreshRatesInternal(display.Name, portrait, timing.HorizontalVisible, timing.VerticalVisible);
}
public bool IsHDREnabled()
{
var displayDevice = GetCurrentDisplay().DisplayDevice;
var hdr = displayDevice.HDRColorData;
return hdr?.HDRMode == ColorDataHDRMode.UHDA;
}
public Display[] GetDisplays()
{
return Display.GetDisplays();
}
public List<NvDisplayInfo> GetDisplayInfos()
{
Display[] displays;
try
{
displays = GetDisplays();
}
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>();
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()
{
NVIDIA.Initialize();
_initialized = true;
}
protected override void Uninitialize()
{
if (_initialized)
{
NVIDIA.Unload();
}
}
}
}
-50
View File
@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
namespace ColorControl
{
internal abstract class PresetBase
{
public string shortcut { get; set; }
private int _id;
public int id
{
get
{
return _id;
}
set
{
if (value == 0 || ids.Contains(value))
{
value = GetNewId();
}
else
{
ids.Add(value);
}
_id = value;
}
}
public static List<int> ids = new List<int>();
public static int GetNewId()
{
int id;
do
{
id = new Random().Next();
}
while (ids.Contains(id));
ids.Add(id);
return id;
}
public abstract List<string> GetDisplayValues(Config config = null);
}
}
+315 -27
View File
@@ -1,66 +1,129 @@
using NWin32;
using ColorControl.Common;
using ColorControl.Forms;
using ColorControl.Services.AMD;
using ColorControl.Services.LG;
using ColorControl.Services.NVIDIA;
using ColorControl.Svc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using NLog;
using NWin32;
using System;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
{
static class Program
{
public const string TS_TASKNAME = "ColorControl";
public static string DataDir { get; private set; }
public static string ConfigFilename { get; private set; }
public static string LogFilename { get; private set; }
public static Config Config { get; private set; }
public static AppContext AppContext { get; private set; }
public static string MutexId { get; private set; }
public static bool IsRestarting { get; private set; }
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
DataDir = Utils.GetDataPath();
//Utils.UpdateFiles(@"H:\temp\ColorControl\ColorControl", @"C:\Users\vinni\source\repos\ColorControl\ColorControl\bin\Debug\net6.0-windows10.0.20348.0");
var runAsService = args.Contains("--service") || Process.GetCurrentProcess().Parent()?.ProcessName?.Equals("services", StringComparison.InvariantCultureIgnoreCase) == true;
InitLogger(runAsService);
Logger.Debug($"Using data path: {DataDir}");
Logger.Debug("Parent process: " + Process.GetCurrentProcess().Parent()?.ProcessName);
if (runAsService)
{
Utils.WaitForTask(RunService(args));
return;
}
MutexId = $"Global\\{typeof(MainForm).GUID}";
var currentDomain = AppDomain.CurrentDomain;
// Handler for unhandled exceptions.
currentDomain.UnhandledException += GlobalUnhandledExceptionHandler;
// Handler for exceptions in threads behind forms.
Application.ThreadException += GlobalThreadExceptionHandler;
var startUpParams = StartUpParams.Parse(args);
LoadConfig();
if (startUpParams.ActivateChromeFontFix || startUpParams.DeactivateChromeFontFix)
if (Config.UseGdiScaling)
{
Utils.InstallChromeFix(startUpParams.ActivateChromeFontFix);
return;
Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
}
string mutexId = $"Global\\{typeof(MainForm).GUID}";
bool mutexCreated;
var mutex = new Mutex(true, mutexId, out mutexCreated);
var startUpParams = StartUpParams.Parse(args);
AppContext = new AppContext(Config, startUpParams);
var existingProcess = Utils.GetProcessByName("ColorControl");
try
{
if (HandleStartupParams(startUpParams, existingProcess))
{
return;
}
}
finally
{
Utils.CloseConsole();
}
_mutex = new Mutex(true, MutexId, out var mutexCreated);
try
{
if (!mutexCreated)
{
var currentProcessId = Process.GetCurrentProcess().Id;
foreach (var process in Process.GetProcesses())
if (existingProcess != null && existingProcess.Threads.Count > 0)
{
if (process.ProcessName.Equals("ColorControl") && process.Id != currentProcessId)
{
if (process.Threads.Count > 0)
{
var thread = process.Threads[0];
var thread = existingProcess.Threads[0];
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
NativeMethods.EnumThreadWindows((uint)thread.Id, EnumThreadWindows, IntPtr.Zero);
}
return;
}
return;
}
MessageBox.Show("Only one instance of this program can be active.", "ColorControl");
}
else
{
_mutex.WaitOne();
try
{
if (Debugger.IsAttached)
{
Utils.StartService();
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm(startUpParams));
Application.Run(new MainForm(AppContext));
if (Debugger.IsAttached && !IsRestarting)
{
Utils.StopService();
}
}
catch (Exception ex)
{
@@ -72,11 +135,228 @@ namespace ColorControl
{
if (mutexCreated)
{
mutex.Dispose();
_mutex?.Close();
}
}
}
public static void Restart()
{
IsRestarting = true;
_mutex?.Close();
_mutex = null;
Thread.Sleep(1000);
Application.Restart();
Thread.Sleep(1000);
Environment.Exit(0);
}
private static void InitLogger(bool runAsService)
{
var config = new NLog.Config.LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : "";
LogFilename = Path.Combine(DataDir, $"LogFile{logFileAppendix}.txt");
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
//var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
// Rules for mapping loggers to targets
//config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
config.AddRule(LogLevel.Trace, LogLevel.Fatal, logfile);
// Apply config
LogManager.Configuration = config;
}
private static void LoadConfig()
{
ConfigFilename = Path.Combine(DataDir, "Settings.json");
try
{
if (File.Exists(ConfigFilename))
{
var data = File.ReadAllText(ConfigFilename);
Config = JsonConvert.DeserializeObject<Config>(data);
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new Config();
Utils.UseDedicatedElevatedProcess = Config.UseDedicatedElevatedProcess;
}
public static bool HandleStartupParams(StartUpParams startUpParams, Process existingProcess)
{
if (startUpParams.ActivateChromeFontFix || startUpParams.DeactivateChromeFontFix)
{
Utils.InstallChromeFix(startUpParams.ActivateChromeFontFix);
return true;
}
if (startUpParams.EnableAutoStart || startUpParams.DisableAutoStart)
{
Utils.RegisterTask(TS_TASKNAME, startUpParams.EnableAutoStart, startUpParams.AutoStartRunLevel);
return true;
}
if (startUpParams.SetProcessAffinity)
{
Utils.SetProcessAffinity(startUpParams.ProcessId, startUpParams.AffinityMask);
return true;
}
if (startUpParams.SetProcessPriority)
{
Utils.SetProcessPriority(startUpParams.ProcessId, startUpParams.PriorityClass);
return true;
}
if (startUpParams.StartElevated)
{
StartElevated();
return true;
}
if (startUpParams.SendWol)
{
return WOL.WakeFunction(startUpParams.WolMacAddress, startUpParams.WolIpAddress);
}
if (startUpParams.InstallService)
{
Utils.InstallService();
return true;
}
if (startUpParams.UninstallService)
{
Utils.UninstallService();
return true;
}
if (startUpParams.StartService)
{
Utils.StartService();
return true;
}
if (startUpParams.StopService)
{
Utils.StopService();
return true;
}
var useConsole = startUpParams.NoGui || existingProcess != null;
if (!useConsole)
{
return false;
}
startUpParams.NoGui = true;
var result = false;
if (startUpParams.ExecuteHelp)
{
Utils.OpenConsole();
Console.WriteLine("\nColorControl CLI");
Console.WriteLine("Syntax : ColorControl command options");
Console.WriteLine("Commands:");
Console.WriteLine("--nvpreset <preset name or id>: execute NVIDIA-preset");
Console.WriteLine("--amdpreset <preset name or id>: execute AMD-preset");
Console.WriteLine("--lgpreset <preset name> : execute LG-preset");
Console.WriteLine("--help : displays this help info");
Console.WriteLine("Options:");
Console.WriteLine("--nogui: starts command from the command line and will not open GUI (is forced when GUI is already running)");
result = true;
}
if (startUpParams.ExecuteLgPreset)
{
Utils.OpenConsole();
Console.WriteLine($"Executing LG-preset '{startUpParams.LgPresetName}'...");
var task = LgService.ExecutePresetAsync(startUpParams.LgPresetName);
var taskResult = Utils.WaitForTask(task);
if (taskResult)
{
Console.WriteLine("Done.");
}
result = true;
}
if (startUpParams.ExecuteNvidiaPreset)
{
Utils.OpenConsole();
Console.WriteLine($"Executing NVIDIA-preset '{startUpParams.NvidiaPresetIdOrName}'...");
var taskResult = NvService.ExecutePresetAsync(startUpParams.NvidiaPresetIdOrName);
if (taskResult)
{
Console.WriteLine("Done.");
}
result = true;
}
if (startUpParams.ExecuteAmdPreset)
{
Utils.OpenConsole();
Console.WriteLine($"Executing AMD-preset '{startUpParams.AmdPresetIdOrName}'...");
var taskResult = AmdService.ExecutePresetAsync(startUpParams.AmdPresetIdOrName);
if (taskResult)
{
Console.WriteLine("Done.");
}
result = true;
}
return result;
}
private static void StartElevated()
{
try
{
Application.Run(new ElevatedForm());
}
catch (Exception ex)
{
MessageBox.Show("Error while initializing elevated application: " + ex.ToLogString(Environment.StackTrace), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private static async Task RunService(string[] args)
{
Logger.Debug("RUNNING SERVICE");
using IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ColorControl Service";
})
.ConfigureServices(services =>
{
services.AddHostedService<ColorControlBackgroundService>();
})
.Build();
await host.RunAsync();
}
public static int EnumThreadWindows(IntPtr handle, IntPtr param)
{
NativeMethods.SendMessageW(handle, Utils.WM_BRINGTOFRONT, UIntPtr.Zero, IntPtr.Zero);
@@ -86,14 +366,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);
}
}
}
-36
View File
@@ -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("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
+72 -26
View File
@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ColorControl.Properties
{
namespace ColorControl.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -19,53 +19,99 @@ 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", "4.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
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ColorControl.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap LG_remote {
get {
object obj = ResourceManager.GetObject("LG_remote", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to Power;120;136
///1;106;228
///2;196;228
///3;286;228
///4;106;296
///5;196;296
///6;286;296
///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
///Channel -;298;622
///Home;78;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]&quot;;.
/// </summary>
internal static string RC_coords {
get {
return ResourceManager.GetString("RC_coords", resourceCulture);
}
}
}
}
+15 -5
View File
@@ -46,7 +46,7 @@
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
@@ -60,6 +60,7 @@
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
@@ -68,9 +69,10 @@
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<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">
@@ -85,9 +87,10 @@
<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" msdata:Ordinal="1" />
<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">
@@ -109,9 +112,16 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="LG_remote" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\LG_remote.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RC_coords" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\RC_coords.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
</root>
+9 -13
View File
@@ -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;
}
}
+77
View File
@@ -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=="
}
]
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

+37
View File
@@ -0,0 +1,37 @@
Power;120;136
1;106;228
2;196;228
3;286;228
4;106;296
5;196;296
6;286;296
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
Channel -;298;622
Home;78;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;1218
Pause;291;1218
-40
View File
@@ -1,40 +0,0 @@
using System.Web.Script.Serialization;
namespace ColorControl
{
class ServiceBase
{
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
protected string _dataPath;
protected string _presetsFilename;
protected JavaScriptSerializer _JsonSerializer;
protected JavaScriptSerializer _JsonDeserializer;
protected bool _initialized = false;
public ServiceBase(string dataPath)
{
_dataPath = dataPath;
_JsonSerializer = new JavaScriptSerializer();
_JsonDeserializer = new JavaScriptSerializer();
Initialize();
}
~ServiceBase()
{
Uninitialize();
}
protected virtual void Initialize()
{
}
protected virtual void Uninitialize()
{
}
}
}
File renamed without changes.
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
using System.Windows.Forms;
using ADL_CONTEXT_HANDLE = System.IntPtr;
namespace ColorControl
namespace ColorControl.Services.AMD
{
enum ADLPixelFormat
{
@@ -1,7 +1,7 @@
using ATI.ADL;
using System.Collections.Generic;
namespace ColorControl
namespace ColorControl.Services.AMD
{
class AmdDisplayInfo
{
@@ -1,8 +1,9 @@
using System;
using ColorControl.Services.Common;
using System;
using System.Collections.Generic;
using System.Text;
namespace ColorControl
namespace ColorControl.Services.AMD
{
class AmdPreset : PresetBase
{
@@ -19,7 +20,7 @@ namespace ColorControl
public bool primaryDisplay { get; set; }
public string displayName { get; set; }
public AmdPreset()
public AmdPreset() : base()
{
applyColorData = true;
applyDithering = false;
@@ -32,7 +33,7 @@ namespace ColorControl
primaryDisplay = true;
}
public AmdPreset(AmdPreset preset): this()
public AmdPreset(AmdPreset preset) : this()
{
id = GetNewId();
@@ -62,13 +63,15 @@ namespace ColorControl
public static string[] GetColumnNames()
{
//return new[] { "BPC", "Format", "Dynamic range", "Toggle HDR", "Shortcut" };
return new[] { "Display|140", "Color settings (BPC, format)|260", "Refresh rate|100", "Dithering", "HDR", "Shortcut", "Apply on startup" };
return new[] { "Name", "Display|140", "Color settings (BPC, format)|260", "Refresh rate|100", "Dithering", "HDR", "Shortcut", "Apply on startup" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
var display = string.Format("{0}", primaryDisplay ? "Primary" : displayName);
values.Add(display);
@@ -97,33 +100,41 @@ namespace ColorControl
return presets;
}
public string GetTextForMenuItem()
public override string GetTextForMenuItem()
{
var sb = new StringBuilder();
if (displayName != null)
if (!string.IsNullOrEmpty(name))
{
sb.AppendFormat("Display: {0} / ", displayName);
}
if (applyColorData)
{
var colorSettings = string.Format("Format: {0}, {1}", colorDepth, pixelFormat);
sb.Append(colorSettings);
sb.Append(name);
sb.Append(" / ");
}
if (applyRefreshRate)
else
{
sb.AppendFormat("{0}Hz", refreshRate);
sb.Append(" / ");
}
if (applyDithering)
{
sb.AppendFormat("Dithering: {0}", ditherState);
sb.Append(" / ");
}
if (applyHDR)
{
sb.AppendFormat("HDR: {0}", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled");
sb.Append(" / ");
if (displayName != null)
{
sb.AppendFormat("Display: {0} / ", displayName);
}
if (applyColorData)
{
var colorSettings = string.Format("Format: {0}, {1}", colorDepth, pixelFormat);
sb.Append(colorSettings);
sb.Append(" / ");
}
if (applyRefreshRate)
{
sb.AppendFormat("{0}Hz", refreshRate);
sb.Append(" / ");
}
if (applyDithering)
{
sb.AppendFormat("Dithering: {0}", ditherState);
sb.Append(" / ");
}
if (applyHDR)
{
sb.AppendFormat("HDR: {0}", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled");
sb.Append(" / ");
}
}
return sb.ToString(0, Math.Max(0, sb.Length - 3));
@@ -1,62 +1,56 @@
using ATI.ADL;
using ColorControl.Common;
using ColorControl.Services.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl
namespace ColorControl.Services.AMD
{
class AmdService : GraphicsService
class AmdService : GraphicsService<AmdPreset>
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public override string ServiceName => "AMD";
private ADLDisplayInfo _currentDisplay;
private List<AmdPreset> _presets;
private AmdPreset _lastAppliedPreset;
public AmdService(string dataPath) : base(dataPath)
public AmdService(string dataPath) : base(dataPath, "AmdPresets.json")
{
LoadPresets();
}
public List<AmdPreset> GetPresets()
public static bool ExecutePresetAsync(string idOrName)
{
return _presets;
try
{
var service = new AmdService(Program.DataDir);
var result = service.ApplyPreset(idOrName);
if (!result)
{
Console.WriteLine("Preset not found or error while executing.");
}
return result;
}
catch (Exception ex)
{
Console.WriteLine("Error executing preset: " + ex.ToLogString());
return false;
}
}
public AmdPreset GetLastAppliedPreset()
protected override List<AmdPreset> GetDefaultPresets()
{
return _lastAppliedPreset;
}
private void LoadPresets()
{
_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);
File.WriteAllText(_presetsFilename, json);
}
catch (Exception e)
{
Logger.Error(e.ToLogString());
}
Utils.WriteObject(_presetsFilename, _presets);
}
public void GlobalSave()
@@ -64,7 +58,7 @@ namespace ColorControl
SavePresets();
}
public bool HasDisplaysAttached()
public override bool HasDisplaysAttached(bool reinitialize = false)
{
try
{
@@ -99,7 +93,20 @@ namespace ColorControl
return _currentDisplay;
}
public bool ApplyPreset(AmdPreset preset, Config config)
public bool ApplyPreset(string idOrName)
{
var preset = GetPresetByIdOrName(idOrName);
if (preset != null)
{
return ApplyPreset(preset);
}
else
{
return false;
}
}
public override bool ApplyPreset(AmdPreset preset)
{
SetCurrentDisplay(preset);
@@ -131,9 +138,9 @@ namespace ColorControl
if (preset.applyRefreshRate)
{
if (!SetRefreshRate(preset.refreshRate))
{
result = false;
if (!SetRefreshRate(preset.refreshRate))
result = false;
}
}
@@ -161,6 +168,8 @@ namespace ColorControl
_lastAppliedPreset = preset;
PresetApplied();
return result;
}
@@ -203,7 +212,7 @@ namespace ColorControl
var portrait = vertical > horizontal;
return SetRefreshRateInternal(GetDisplayDeviceName(display), refreshRate, portrait, horizontal, vertical);
return SetRefreshRateInternal(GetDisplayDeviceName(display), (int)refreshRate, portrait, horizontal, vertical);
}
public List<uint> GetAvailableRefreshRates(AmdPreset preset = null)
@@ -295,6 +304,8 @@ namespace ColorControl
{
var values = new List<string>();
values.Add("Current settings");
var screen = GetScreenForDisplay(display);
var name = GetFullDisplayName(display);
@@ -1,4 +1,5 @@
using NWin32;
using ColorControl.Native;
using NWin32;
using NWin32.NativeTypes;
using System;
using System.Collections.Generic;
@@ -7,19 +8,14 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace ColorControl
namespace ColorControl.Services.Common
{
class GraphicsService : ServiceBase
abstract class GraphicsService<T> : ServiceBase<T> where T : PresetBase, new()
{
[DllImport("user32.dll")]
public static extern bool EnumDisplaySettingsA(string deviceName, int modeNum, out DEVMODEA devMode);
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
const int ENUM_CURRENT_SETTINGS = -1;
const int ENUM_REGISTRY_SETTINGS = -2;
public GraphicsService(string dataPath) : base(dataPath)
public GraphicsService(string dataPath, string presetsBaseFilename) : base(dataPath, presetsBaseFilename)
{
Initialize();
}
~GraphicsService()
@@ -27,6 +23,8 @@ namespace ColorControl
Uninitialize();
}
public abstract bool HasDisplaysAttached(bool reinitialize = false);
protected void ToggleHDR(int delay = 1000)
{
Process.Start("ms-settings:display");
@@ -54,7 +52,7 @@ namespace ColorControl
}
}
protected bool SetRefreshRateInternal(string displayName, uint refreshRate, bool portrait, int horizontal, int vertical)
protected bool SetRefreshRateInternal(string displayName, int refreshRate, bool portrait, int horizontal, int vertical, bool updateRegistry = false)
{
uint i = 0;
DEVMODEA devMode;
@@ -67,7 +65,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}");
@@ -103,11 +101,35 @@ namespace ColorControl
{
DEVMODEA devMode;
// NativeMethods defines modeNum as an 'uint' but we need to pass '-1'
if (EnumDisplaySettingsA(displayName, ENUM_CURRENT_SETTINGS, out devMode))
if (WinApi.EnumDisplaySettingsA(displayName, WinApi.ENUM_CURRENT_SETTINGS, out devMode))
{
return devMode.dmDisplayFrequency;
}
return 0;
}
protected List<DEVMODEA> GetAvailableResolutionsInternal(string displayName, bool portrait, uint refreshRate = 0)
{
var list = new List<DEVMODEA>();
uint i = 0;
DEVMODEA devMode;
while (NativeMethods.EnumDisplaySettingsA(displayName, i, out devMode))
{
if ((portrait ? devMode.dmPelsWidth < devMode.dmPelsHeight : devMode.dmPelsWidth > devMode.dmPelsHeight) &&
(refreshRate == 0 || devMode.dmDisplayFrequency == refreshRate) &&
!list.Any(m => m.dmPelsWidth == devMode.dmPelsWidth && m.dmPelsHeight == devMode.dmPelsHeight))
{
list.Add(devMode);
}
i++;
}
list.Sort((a, b) => (int)(a.dmPelsWidth - b.dmPelsWidth));
return list;
}
}
}
+211
View File
@@ -0,0 +1,211 @@
using ColorControl.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
namespace ColorControl.Services.Common
{
enum PresetTriggerType
{
[Description("None")]
None = 0,
[Description("Startup")]
Startup = 1,
[Description("Resume")]
Resume = 2,
[Description("Shutdown")]
Shutdown = 3,
[Description("Standby")]
Standby = 4,
[Description("Reserved5")]
Reserved5 = 5,
[Description("Process switch")]
ProcessSwitch = 6,
[Description("Screensaver start")]
ScreensaverStart = 7,
[Description("Screensaver stop")]
ScreensaverStop = 8,
}
[Flags]
enum PresetConditionType
{
[Description("None")]
None = 0,
[Description("SDR")]
SDR = 1,
[Description("HDR")]
HDR = 2,
[Description("Full Screen")]
FullScreen = 4,
[Description("Notifications Disabled")]
NotificationsDisabled = 8,
[Description("G-SYNC Disabled")]
GsyncDisabled = 16,
[Description("G-SYNC Enabled")]
GsyncEnabled = 32
}
class PresetTrigger
{
public PresetTriggerType Trigger { get; set; }
public List<string> IncludedProcesses { get; set; }
public List<string> ExcludedProcesses { get; set; }
public PresetConditionType Conditions { get; set; }
public PresetTrigger()
{
IncludedProcesses = new List<string>();
ExcludedProcesses = new List<string>();
}
public bool TriggerActive(PresetTriggerContext context)
{
var active = Conditions == PresetConditionType.None ||
(Conditions.HasFlag(PresetConditionType.SDR) ? !context.IsHDRActive : true) && (Conditions.HasFlag(PresetConditionType.HDR) ? context.IsHDRActive : true) &&
(Conditions.HasFlag(PresetConditionType.GsyncDisabled) ? !context.IsGsyncActive : true) && (Conditions.HasFlag(PresetConditionType.GsyncEnabled) ? context.IsGsyncActive : true);
var allProcesses = IncludedProcesses.Contains("*");
if (Trigger == PresetTriggerType.ProcessSwitch)
{
active = active && (allProcesses || (context.ChangedProcesses?.Any() ?? false));
if (active)
{
var included = allProcesses || context.ChangedProcesses.Any(cp => IncludedProcesses.Any(ip => cp.ProcessName.NormEquals(ip)));
var excluded = context.ChangedProcesses.Any(cp => ExcludedProcesses.Any(ep => cp.ProcessName.NormEquals(ep)));
var screenSizeCheck = (!Conditions.HasFlag(PresetConditionType.FullScreen) && !context.ForegroundProcessIsFullScreen) ||
(context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen &&
(allProcesses || IncludedProcesses.Any(ip => context.ForegroundProcess.ProcessName.NormEquals(ip))));
var notificationsDisabledCheck = !Conditions.HasFlag(PresetConditionType.NotificationsDisabled) || context.IsNotificationDisabled;
active = included && !excluded && screenSizeCheck && notificationsDisabledCheck;
}
}
return active;
}
public override string ToString()
{
if (Trigger == PresetTriggerType.None)
{
return string.Empty;
}
return $"On {Trigger.GetDescription()} of {DisplayProcesses(IncludedProcesses)}{(ExcludedProcesses.Any() ? ", excluding " + DisplayProcesses(ExcludedProcesses) : string.Empty)}{(Conditions > 0 ? ", only in " + string.Join(", ", Utils.GetDescriptions<PresetConditionType>((int)Conditions)) : string.Empty)}";
}
public static string DisplayProcesses(IEnumerable<string> processes)
{
return processes.Contains("*") ? "all processes" : string.Join(", ", processes);
}
}
class PresetTriggerContext
{
public bool IsHDRActive { get; set; }
public bool IsGsyncActive { get; set; }
public Process ForegroundProcess { get; set; }
public bool ForegroundProcessIsFullScreen { get; set; }
public List<Process> ChangedProcesses { get; set; }
public bool IsNotificationDisabled { get; set; }
}
internal abstract class PresetBase
{
public string name { get; set; }
public string shortcut { get; set; }
public List<PresetTrigger> Triggers { get; set; }
public string Description { get; set; }
public bool ShowInQuickAccess { get; set; }
private int _id;
protected PresetBase()
{
Triggers = new List<PresetTrigger>();
}
public int id
{
get
{
return _id;
}
set
{
if (value == 0 || ids.Contains(value))
{
value = GetNewId();
}
else
{
ids.Add(value);
}
_id = value;
}
}
public static List<int> ids = new List<int>();
public static int GetNewId()
{
int id;
do
{
id = new Random().Next();
}
while (ids.Contains(id));
ids.Add(id);
return id;
}
public abstract List<string> GetDisplayValues(Config config = null);
public virtual string GetTextForMenuItem()
{
return name;
}
public void UpdateTrigger(PresetTriggerType triggerType, PresetConditionType conditions, string includedProcesses, string excludedProcesses)
{
if (!Triggers.Any())
{
Triggers.Add(new PresetTrigger());
}
var trigger = Triggers.First();
trigger.Trigger = triggerType;
trigger.Conditions = conditions;
if (trigger.IncludedProcesses == null)
{
trigger.IncludedProcesses = new List<string>();
}
if (trigger.ExcludedProcesses == null)
{
trigger.ExcludedProcesses = new List<string>();
}
Utils.ParseWords(trigger.IncludedProcesses, includedProcesses);
Utils.ParseWords(trigger.ExcludedProcesses, excludedProcesses);
}
public PresetTrigger AddTrigger()
{
if (!Triggers.Any())
{
Triggers.Add(new PresetTrigger());
}
return Triggers.First();
}
}
}
+124
View File
@@ -0,0 +1,124 @@
using System.Windows.Forms;
namespace ColorControl.Services.Common
{
partial class QuickAccessForm<T> : Form where T : PresetBase, new()
{
/// <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.lblTitle = new System.Windows.Forms.Label();
this.tmrHide = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// flowPanel
//
this.flowPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
this.flowPanel.Location = new System.Drawing.Point(14, 33);
this.flowPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(270, 339);
this.flowPanel.TabIndex = 0;
this.flowPanel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.flowPanel_MouseMove);
//
// 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);
//
// lblTitle
//
this.lblTitle.AutoSize = true;
this.lblTitle.ForeColor = System.Drawing.Color.White;
this.lblTitle.Location = new System.Drawing.Point(13, 6);
this.lblTitle.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(77, 15);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "Quick Access";
//
// tmrHide
//
this.tmrHide.Interval = 5000;
this.tmrHide.Tick += new System.EventHandler(this.tmrHide_Tick);
//
// QuickAccessForm
//
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.lblTitle);
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 = "QuickAccessForm";
this.Opacity = 0.8D;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "RemoteControlForm";
this.TopMost = true;
this.Activated += new System.EventHandler(this.LgGameBar_Activated);
this.Deactivate += new System.EventHandler(this.LgGameBar_Deactivate);
this.Shown += new System.EventHandler(this.RemoteControlForm_Shown);
this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.RemoteControlForm_KeyUp);
this.Move += new System.EventHandler(this.LgGameBar_Move);
this.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.QuickAccessForm_PreviewKeyDown);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.FlowLayoutPanel flowPanel;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Timer tmrHide;
}
}
@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl.Services.Common
{
partial class QuickAccessForm<T> : Form where T : PresetBase, new()
{
private ServiceBase<T> _service;
private List<Button> _buttons = new List<Button>();
private bool _shown = false;
private static Dictionary<ServiceBase<T>, QuickAccessForm<T>> QuickAccessForms = new();
public static void ToggleQuickAccessForm(ServiceBase<T> service)
{
if (!QuickAccessForms.TryGetValue(service, out var form) || form.IsDisposed)
{
form = new QuickAccessForm<T>(service);
QuickAccessForms.Add(service, form);
}
if (!form.Visible)
{
form.Show();
form.Activate();
}
else
{
form.Hide();
}
}
public QuickAccessForm(ServiceBase<T> service)
{
_service = service;
InitializeComponent();
lblTitle.Text = $"Quick Access - {service.ServiceName}";
}
private void GenerateComponents()
{
var presets = _service.GetPresets().Where(p => p.ShowInQuickAccess);
flowPanel.Controls.Clear();
_buttons.Clear();
if (presets.Any())
{
foreach (var preset in presets)
{
CreateGroup(preset);
}
Height = Height - flowPanel.Height + (_buttons.Count() * 46) + 8;
return;
}
var panel = new Panel();
panel.Width = flowPanel.Width - 4;
panel.Height = 40;
var label = new Label();
label.Text = "No Quick Access presets configured";
label.Parent = panel;
label.AutoSize = true;
label.ForeColor = Color.White;
panel.Parent = flowPanel;
Height = Height - flowPanel.Height + 36;
}
private Button CreateGroup(PresetBase preset)
{
var panel = new Panel();
panel.Width = flowPanel.Width - 4;
panel.Height = 40;
var button = new Button();
button.Text = preset.name;
button.Tag = preset;
button.Font = new Font(button.Font, FontStyle.Bold);
button.Parent = panel;
button.Width = panel.Width - 4;
button.Height = panel.Height - 4;
var margin = button.Margin;
margin.Top = 0;
margin.Bottom = 0;
button.Margin = margin;
button.ForeColor = Color.White;
button.FlatStyle = FlatStyle.Flat;
button.FlatAppearance.BorderColor = Color.DarkGray;
button.FlatAppearance.MouseOverBackColor = Color.DimGray;
button.Click += Button_Click;
panel.Parent = flowPanel;
_buttons.Add(button);
return button;
}
private void Button_Click(object sender, EventArgs e)
{
var preset = (T)(sender as Button).Tag;
if (NWin32.NativeMethods.GetAsyncKeyState(NWin32.NativeConstants.VK_SHIFT) < 0)
{
preset.ShowInQuickAccess = false;
GenerateComponents();
return;
}
Hide();
var _ = _service.ApplyPreset(preset);
}
private void btnClose_Click(object sender, EventArgs e)
{
Hide();
}
private void HandleKey(Keys key)
{
if (key == Keys.Escape)
{
Hide();
return;
}
ResetAutoHide();
if (!_buttons.Any())
{
return;
}
if (!(ActiveControl is Button button))
{
_buttons.First().Focus();
return;
}
var diff = 0;
if (key == Keys.Up)
{
diff = -1;
}
else if (key == Keys.Down)
{
diff = 1;
}
if (diff == 0)
{
return;
}
var index = _buttons.IndexOf(button);
index += diff;
if (index == -1)
{
index = _buttons.Count - 1;
}
else if (index == _buttons.Count)
{
index = 0;
}
button = _buttons[index];
button.Focus();
}
private void RemoteControlForm_KeyUp(object sender, KeyEventArgs e)
{
HandleKey(e.KeyCode);
}
private void flowPanel_MouseMove(object sender, MouseEventArgs e)
{
ResetAutoHide();
}
private void RemoteControlForm_Shown(object sender, EventArgs e)
{
GenerateComponents();
ResetAutoHide();
_shown = true;
}
private void LgGameBar_Deactivate(object sender, EventArgs e)
{
Hide();
}
private void tmrHide_Tick(object sender, EventArgs e)
{
Hide();
}
private void LgGameBar_Activated(object sender, EventArgs e)
{
if (_shown)
{
GenerateComponents();
}
ResetAutoHide();
}
private void LgGameBar_Move(object sender, EventArgs e)
{
ResetAutoHide();
}
private void ResetAutoHide()
{
tmrHide.Enabled = false;
tmrHide.Enabled = true;
}
private void QuickAccessForm_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
{
HandleKey(e.KeyCode);
}
}
}
@@ -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>
+171
View File
@@ -0,0 +1,171 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ColorControl.Services.Common
{
abstract class ServiceBase<T> where T : PresetBase, new()
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public event EventHandler<T> AfterApplyPreset;
public abstract string ServiceName
{
get;
}
protected string _dataPath;
protected string _presetsBaseFilename;
protected string _presetsFilename;
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, string presetsBaseFilename)
{
_dataPath = dataPath;
_presetsBaseFilename = presetsBaseFilename;
_jsonConverters = new List<JsonConverter>();
Initialize();
}
~ServiceBase()
{
Uninitialize();
}
public List<T> GetPresets()
{
return _presets;
}
public T GetPresetByIdOrName(string idOrName)
{
var preset = _presets.FirstOrDefault(p => p.name != null && p.name.Equals(idOrName, StringComparison.OrdinalIgnoreCase));
if (preset == null && int.TryParse(idOrName, out var id))
{
preset = _presets.FirstOrDefault(p => p.id == id);
}
return preset;
}
public abstract bool ApplyPreset(T preset);
public T GetLastAppliedPreset()
{
return _lastAppliedPreset;
}
public virtual T CreateNewPreset()
{
var preset = new T();
var name = "New preset";
string fullname;
var number = 1;
do
{
fullname = $"{name} ({number})";
number++;
} while (_presets.Any(x => x.name.Equals(fullname)));
preset.name = fullname;
return preset;
}
protected virtual void Initialize()
{
}
protected virtual void Uninitialize()
{
}
protected void PresetApplied()
{
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);
}
public void ToggleQuickAccessForm()
{
QuickAccessForm<T>.ToggleQuickAccessForm(this);
}
}
}
@@ -0,0 +1,27 @@
namespace ColorControl.Services.Common
{
class ServiceManager
{
public static ServiceManager Instance
{
get
{
if (_instance == null)
{
_instance = new ServiceManager();
}
return _instance;
}
}
private static ServiceManager _instance { get; set; }
//private List<ServiceBase<PresetBase>> _services = new();
public void Initialize()
{
}
}
}
@@ -0,0 +1,89 @@
using ColorControl.Services.Common;
using NWin32;
using System.Collections.Generic;
using System.ComponentModel;
namespace ColorControl.Services.GameLauncher
{
public enum GameStepType
{
[Description("Pre-launch steps")]
PreLaunch = 0,
[Description("Post-launch steps")]
PostLaunch = 1,
[Description("Finalize steps")]
Finalize = 2,
}
public enum GamePriorityClass
{
Idle = NativeConstants.IDLE_PRIORITY_CLASS,
BelowNormal = NativeConstants.BELOW_NORMAL_PRIORITY_CLASS,
Normal = NativeConstants.NORMAL_PRIORITY_CLASS,
AboveNormal = NativeConstants.ABOVE_NORMAL_PRIORITY_CLASS,
High = NativeConstants.HIGH_PRIORITY_CLASS,
RealTime = NativeConstants.REALTIME_PRIORITY_CLASS
}
class GamePreset : PresetBase
{
public string Path { get; set; }
public string Parameters { get; set; }
public bool RunAsAdministrator { get; set; }
public List<string> PreLaunchSteps { get; set; }
public List<string> PostLaunchSteps { get; set; }
public List<string> FinalizeSteps { get; set; }
public uint ProcessAffinityMask { get; set; }
public uint ProcessPriorityClass { get; set; }
public GamePreset() : base()
{
PreLaunchSteps = new List<string>();
PostLaunchSteps = new List<string>();
FinalizeSteps = new List<string>();
}
public GamePreset(GamePreset preset) : this()
{
id = GetNewId();
name = preset.name;
Path = preset.Path;
Parameters = preset.Parameters;
RunAsAdministrator = preset.RunAsAdministrator;
PreLaunchSteps.AddRange(preset.PreLaunchSteps);
PostLaunchSteps.AddRange(preset.PostLaunchSteps);
FinalizeSteps.AddRange(preset.FinalizeSteps);
}
public GamePreset Clone()
{
var preset = new GamePreset(this);
preset.name += " (copy)";
return preset;
}
public static string[] GetColumnNames()
{
return new[] { "Name|160", "File/URI|400", "Parameters|200", "Pre-launch steps|300", "Post-launch steps|300", "Finalize steps|300" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
values.Add(Path);
values.Add(Parameters);
values.Add(string.Join(", ", PreLaunchSteps));
values.Add(string.Join(", ", PostLaunchSteps));
values.Add(string.Join(", ", FinalizeSteps));
return values;
}
}
}
@@ -0,0 +1,164 @@
using ColorControl.Common;
using ColorControl.Services.Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace ColorControl.Services.GameLauncher
{
class GameService : ServiceBase<GamePreset>
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public override string ServiceName => "Game";
private Func<string, string[], bool> _externalServiceHandler;
public GameService(string dataPath, Func<string, string[], bool> externalServiceHandler = null) : base(dataPath, "GamePresets.json")
{
LoadPresets();
_externalServiceHandler = externalServiceHandler;
}
public static bool ExecutePresetAsync(string idOrName)
{
try
{
var service = new GameService(Program.DataDir);
var result = service.ApplyPreset(idOrName);
if (!result)
{
Console.WriteLine("Preset not found or error while executing.");
}
return result;
}
catch (Exception ex)
{
Console.WriteLine("Error executing preset: " + ex.ToLogString());
return false;
}
}
protected override List<GamePreset> GetDefaultPresets()
{
return new List<GamePreset>();
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
public void GlobalSave()
{
SavePresets();
}
public bool ApplyPreset(string idOrName)
{
var preset = GetPresetByIdOrName(idOrName);
if (preset != null)
{
return ApplyPreset(preset, Program.AppContext);
}
else
{
return false;
}
}
public override bool ApplyPreset(GamePreset preset)
{
return ApplyPreset(preset, Program.AppContext);
}
public bool ApplyPreset(GamePreset preset, AppContext appContext)
{
var result = true;
ExecuteSteps(preset.PreLaunchSteps);
Process process = null;
if (!string.IsNullOrEmpty(preset.Path))
{
process = Utils.StartProcess(preset.Path, preset.Parameters, setWorkingDir: true, elevate: preset.RunAsAdministrator, affinityMask: preset.ProcessAffinityMask, priorityClass: preset.ProcessPriorityClass);
}
ExecuteSteps(preset.PostLaunchSteps);
if (process != null && preset.FinalizeSteps?.Any() == true)
{
var _ = ExecuteFinalizationStepsAsync(process, preset.FinalizeSteps);
}
_lastAppliedPreset = preset;
PresetApplied();
return result;
}
private async Task ExecuteFinalizationStepsAsync(Process process, List<string> finalizeSteps)
{
await process.WaitForExitAsync();
ExecuteSteps(finalizeSteps);
}
private void ExecuteSteps(List<string> steps)
{
if (steps?.Any() != true)
{
return;
}
foreach (var step in steps)
{
var keySpec = step.Split(':');
var delay = 0;
var key = step;
if (keySpec.Length == 2)
{
delay = Utils.ParseInt(keySpec[1]);
if (delay > 0)
{
key = keySpec[0];
}
}
var index = key.IndexOf("(");
string[] parameters = null;
if (index > -1)
{
var keyValue = key.Split('(');
key = keyValue[0];
parameters = keyValue[1].Substring(0, keyValue[1].Length - 1).Trim().Split(';');
}
var handled = false;
if (_externalServiceHandler != null && parameters != null)
{
handled = _externalServiceHandler(key, parameters);
}
if (!handled)
{
}
if (delay > 0)
{
Utils.WaitForTask(Task.Delay(delay));
}
}
}
}
}
File diff suppressed because it is too large. Load diff
+139
View File
@@ -0,0 +1,139 @@
namespace ColorControl.Services.LG
{
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;
}
}
+324
View File
@@ -0,0 +1,324 @@
using ColorControl.Common;
using NWin32;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl.Services.LG
{
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 = Utils.FirstCharUpperCase(settingName)
};
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;
}
}
}
+63
View File
@@ -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>
+117
View File
@@ -0,0 +1,117 @@
using ColorControl.Services.Common;
using LgTv;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
namespace ColorControl.Services.LG
{
class LgPreset : PresetBase
{
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; }
[JsonIgnore]
public int Y { get; set; }
public LgPreset() : base()
{
steps = new List<string>();
}
public LgPreset(LgPreset preset) : this()
{
id = GetNewId();
name = preset.name + " (copy)";
appId = preset.appId;
foreach (var step in preset.steps)
{
steps.Add(step);
}
}
public LgPreset(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);
return preset;
}
public static List<string> GetColumnNames()
{
return new List<string>() { "Name", "Device|120", "App|200", "Steps|400", "Shortcut", "Trigger" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
var deviceString = "Global";
if (!string.IsNullOrEmpty(DeviceMacAddress))
{
if (LgDevices != null)
{
var device = LgDevices.FirstOrDefault(d => !string.IsNullOrEmpty(d.MacAddress) && d.MacAddress.Equals(DeviceMacAddress));
if (device != null)
{
deviceString = device.Name;
}
else
{
deviceString = "Unknown: device not found";
}
}
else
{
deviceString = DeviceMacAddress;
}
}
values.Add(deviceString);
var app = appId;
if (LgApps != null)
{
var item = LgApps.FirstOrDefault(x => x.appId.Equals(appId));
if (item != null)
{
app = item.title + " (" + appId + ")";
}
}
values.Add(app);
values.Add(GetStepsDisplay());
values.Add(shortcut);
if (Triggers.Any())
{
values.Add(Triggers.First().ToString());
}
return values;
}
public string GetStepsDisplay()
{
return steps.Aggregate("", (a, b) => (string.IsNullOrEmpty(a) ? "" : a + ", ") + b);
}
}
}
+958
View File
@@ -0,0 +1,958 @@
using ColorControl.Common;
using ColorControl.Forms;
using ColorControl.Services.Common;
using ColorControl.Svc;
using LgTv;
using Microsoft.Win32;
using Newtonsoft.Json;
using NWin32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl.Services.LG
{
class LgService : ServiceBase<LgPreset>
{
internal enum PowerOnOffState
{
StartUp = 1,
Resume = 2,
ScreenSaver = 3,
ShutDown = 4,
StandBy = 5
}
internal enum WindowsPowerSetting
{
Off = 0,
On = 1,
Dimmed = 2
}
internal class ProcessMonitorContext
{
public List<LgDevice> Devices { get; set; }
public bool WasFullScreen { get; set; }
public Process[] StartedProcesses { get; set; }
public Process[] StoppedProcesses { get; set; }
public Process[] RunningProcesses { get; set; }
public bool IsNotificationDisabled { get; set; }
public Process ForegroundProcess { get; set; }
public bool ForegroundProcessIsFullScreen { get; set; }
public string LastFullScreenProcessName = string.Empty;
}
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
public static string LgListAppsJson = "listApps.json";
public override string ServiceName => "LG";
public List<LgDevice> Devices { get; private set; }
public LgDevice SelectedDevice
{
get { return _selectedDevice; }
set
{
_selectedDevice = value;
Config.PreferredMacAddress = _selectedDevice != null ? _selectedDevice.MacAddress : null;
SelectedDeviceChangedEvent?.Invoke(_selectedDevice, EventArgs.Empty);
}
}
public event EventHandler SelectedDeviceChangedEvent;
public LgServiceConfig Config { get; private set; }
public ProcessMonitorContext MonitorContext { get; private set; }
private string _configFilename;
private bool _allowPowerOn;
private LgDevice _selectedDevice;
private string _rcButtonsFilename;
private List<LgPreset> _remoteControlButtons;
private List<LgApp> _lgApps = new List<LgApp>();
private bool _poweredOffByScreenSaver;
private int _poweredOffByScreenSaverProcessId;
private Task _monitorTask;
private int _monitorTaskCounter;
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();
}
~LgService()
{
GlobalSave();
}
public static async Task<bool> ExecutePresetAsync(string presetName)
{
try
{
var lgService = new LgService(Program.DataDir, true);
await lgService.RefreshDevices(afterStartUp: true);
var result = await lgService.ApplyPreset(presetName);
if (!result)
{
Console.WriteLine("Preset not found or error while executing.");
}
return result;
}
catch (Exception ex)
{
Console.WriteLine("Error executing preset: " + ex.ToLogString());
return false;
}
}
public void GlobalSave()
{
SaveConfig();
SavePresets();
SaveRemoteControlButtons();
SendConfigToService();
}
protected override List<LgPreset> GetDefaultPresets()
{
List<LgPreset> presets = null;
var defaultPresetsFileName = Path.Combine(Directory.GetCurrentDirectory(), "LgPresets.json");
if (File.Exists(defaultPresetsFileName))
{
var json = File.ReadAllText(defaultPresetsFileName);
presets = JsonConvert.DeserializeObject<List<LgPreset>>(json);
}
return presets;
}
private void LoadConfig()
{
_configFilename = Path.Combine(_dataPath, "LgConfig.json");
try
{
if (File.Exists(_configFilename))
{
Config = JsonConvert.DeserializeObject<LgServiceConfig>(File.ReadAllText(_configFilename));
}
}
catch (Exception ex)
{
Logger.Error($"LoadConfig: {ex.Message}");
}
Config ??= new LgServiceConfig();
LgPreset.LgDevices = Config.Devices;
}
private void LoadRemoteControlButtons()
{
var defaultButtons = GenerateDefaultRemoteControlButtons();
_rcButtonsFilename = Path.Combine(_dataPath, "LgRemoteControlButtons.json");
//if (File.Exists(_rcButtonsFilename))
//{
// var json = File.ReadAllText(_rcButtonsFilename);
// _remoteControlButtons = _JsonDeserializer.Deserialize<List<LgPreset>>(json);
// var missingButtons = defaultButtons.Where(b => !_remoteControlButtons.Any(x => x.name.Equals(b.name)));
// _remoteControlButtons.AddRange(missingButtons);
//}
//else
{
_remoteControlButtons = defaultButtons;
}
}
public List<LgPreset> GetRemoteControlButtons()
{
return _remoteControlButtons;
}
private List<LgPreset> GenerateDefaultRemoteControlButtons()
{
var list = new List<LgPreset>();
for (var i = 1; i < 11; i++)
{
var button = i < 10 ? i : 0;
list.Add(GeneratePreset(button.ToString()));
}
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"));
list.Add(GeneratePreset("List", step: "LIST"));
list.Add(GeneratePreset("SAP", step: "SAP"));
list.Add(GeneratePreset("Vol +", step: "VOLUMEUP", key: Keys.VolumeUp));
list.Add(GeneratePreset("Vol -", step: "VOLUMEDOWN", key: Keys.VolumeDown));
list.Add(GeneratePreset("Mute", step: "MUTE", key: Keys.VolumeMute));
list.Add(GeneratePreset("Channel +", step: "CHANNELUP", key: Keys.MediaNextTrack));
list.Add(GeneratePreset("Channel -", step: "CHANNELDOWN", key: Keys.MediaPreviousTrack));
list.Add(GeneratePreset("Up", step: "UP", key: Keys.Up));
list.Add(GeneratePreset("Down", step: "DOWN", key: Keys.Down));
list.Add(GeneratePreset("Left", step: "LEFT", key: Keys.Left));
list.Add(GeneratePreset("Right", step: "RIGHT", key: Keys.Right));
list.Add(GeneratePreset("Enter", step: "ENTER", key: Keys.Enter));
list.Add(GeneratePreset("Back", step: "BACK", key: Keys.Back));
list.Add(GeneratePreset("Exit", step: "EXIT", key: Keys.Escape));
list.Add(GeneratePreset("Netflix", appId: "netflix"));
list.Add(GeneratePreset("Inputs", appId: "com.webos.app.homeconnect"));
list.Add(GeneratePreset("Amazon Prime", appId: "amazon"));
list.Add(GeneratePreset("Red", step: "RED"));
list.Add(GeneratePreset("Green", step: "GREEN"));
list.Add(GeneratePreset("Yellow", step: "YELLOW"));
list.Add(GeneratePreset("Blue", step: "BLUE"));
list.Add(GeneratePreset("Rakuten TV", appId: "ui30"));
list.Add(GeneratePreset("Play", step: "PLAY"));
list.Add(GeneratePreset("Pause", step: "PAUSE"));
return list;
}
private LgPreset GeneratePreset(string name, string step = null, string appId = null, Keys key = Keys.None)
{
var preset = new LgPreset();
var cvt = new KeysConverter();
var shortcut = key != Keys.None ? (string)cvt.ConvertTo(key, typeof(string)) : null;
preset.name = name;
preset.appId = appId;
preset.shortcut = shortcut;
if (appId == null)
{
preset.steps.Add(step == null ? name : step);
}
return preset;
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
private void SaveConfig()
{
Config.PowerOnAfterStartup = Devices?.Any(d => d.PowerOnAfterStartup) ?? false;
Utils.WriteObject(_configFilename, Config);
}
private void SaveRemoteControlButtons()
{
Utils.WriteObject(_rcButtonsFilename, _remoteControlButtons);
}
public async Task RefreshDevices(bool connect = true, bool afterStartUp = false)
{
Devices = Config.Devices;
var customIpAddresses = Devices.Where(d => d.IsCustom).Select(d => d.IpAddress);
var pnpDevices = await Utils.GetPnpDevices(Config.DeviceSearchKey);
var autoDevices = pnpDevices.Where(p => !customIpAddresses.Contains(p.IpAddress)).Select(d => new LgDevice(d.Name, d.IpAddress, d.MacAddress, false)).ToList();
var autoIpAddresses = pnpDevices.Select(d => d.IpAddress);
Devices.RemoveAll(d => !d.IsCustom && !autoIpAddresses.Contains(d.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())
{
var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0];
SelectedDevice = preferredDevice;
}
else
{
SelectedDevice = null;
}
if (afterStartUp && SelectedDevice == null && Config.PowerOnAfterStartup && !string.IsNullOrEmpty(Config.PreferredMacAddress))
{
Logger.Debug("No device has been found, trying to wake it first...");
var tempDevice = new LgDevice("Test", string.Empty, Config.PreferredMacAddress);
tempDevice.Wake();
await Task.Delay(4000);
await RefreshDevices();
return;
}
foreach (var device in Devices)
{
device.PowerStateChangedEvent += LgDevice_PowerStateChangedEvent;
}
if (connect && SelectedDevice != null)
{
if (_allowPowerOn)
{
WakeAfterStartupOrResume();
}
else
{
var _ = SelectedDevice.Connect();
}
}
}
public async Task<bool> ApplyPreset(string presetName)
{
var preset = _presets.FirstOrDefault(p => p.name.Equals(presetName, StringComparison.OrdinalIgnoreCase));
if (preset != null)
{
return await ApplyPreset(preset);
}
else
{
return false;
}
}
public override bool ApplyPreset(LgPreset preset)
{
var task = ApplyPreset(preset, false);
Utils.WaitForTask(task);
return task.Result;
}
public async Task<bool> ApplyPreset(LgPreset preset, bool reconnect = false)
{
var device = GetPresetDevice(preset);
if (device == null)
{
Logger.Debug("Cannot apply preset: no device has been selected");
return false;
}
var result = await device.ExecutePreset(preset, reconnect, Config);
_lastAppliedPreset = preset;
return result;
}
public LgDevice GetPresetDevice(LgPreset preset)
{
var device = string.IsNullOrEmpty(preset.DeviceMacAddress)
? SelectedDevice
: Devices.FirstOrDefault(d => d.MacAddress?.Equals(preset.DeviceMacAddress, StringComparison.OrdinalIgnoreCase) ?? false);
return device;
}
public async Task<LgWebOsMouseService> GetMouseAsync()
{
return await SelectedDevice?.GetMouseAsync();
}
public async Task RefreshApps(bool force = false)
{
if (SelectedDevice == null)
{
Logger.Debug("Cannot refresh apps: no device has been selected");
return;
}
var apps = await SelectedDevice.GetApps(force);
if (apps.Any())
{
_lgApps.Clear();
_lgApps.AddRange(apps);
}
}
public List<LgApp> GetApps()
{
return _lgApps;
}
internal async Task<bool> PowerOff()
{
return await SelectedDevice?.PowerOff();
}
internal bool WakeSelectedDevice()
{
return SelectedDevice?.Wake() ?? false;
}
internal void WakeAfterStartupOrResume(PowerOnOffState state = PowerOnOffState.StartUp, bool checkUserSession = true)
{
var wakeDevices = Devices.Where(d => state == PowerOnOffState.StartUp && d.PowerOnAfterStartup ||
state == PowerOnOffState.Resume && d.PowerOnAfterResume ||
state == PowerOnOffState.ScreenSaver && d.PowerOnAfterScreenSaver);
PowerOnDevices(wakeDevices, state, checkUserSession);
}
public void InstallEventHandlers()
{
SystemEvents.PowerModeChanged -= PowerModeChanged;
SystemEvents.PowerModeChanged += PowerModeChanged;
//SystemEvents.SessionEnded -= SessionEnded;
//SystemEvents.SessionEnded += SessionEnded;
UserSessionInfo.UserSessionSwitch -= SessionSwitched;
UserSessionInfo.UserSessionSwitch += SessionSwitched;
MonitorProcesses();
}
private void SessionSwitched(bool toLocal)
{
if (toLocal)
{
if (_poweredOffByScreenSaver)
{
Logger.Debug("User switched to local and screen was powered off due to screensaver: waking up");
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
WakeAfterStartupOrResume();
}
else
{
Logger.Debug("User switched to local but screen was not powered off by screensaver: NOT waking up");
}
}
}
private void PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
var powerOn = e.Mode == PowerModes.Resume;
Logger.Debug($"PowerModeChanged: {e.Mode}");
if (powerOn)
{
WakeAfterStartupOrResume(PowerOnOffState.Resume);
return;
}
var devices = Devices.Where(d => d.PowerOffOnStandby);
PowerOffDevices(devices, PowerOnOffState.StandBy);
}
private void SessionEnded(object sender, SessionEndedEventArgs e)
{
//if (e.Reason == SessionEndReasons.SystemShutdown)
//{
// Logger.Debug($"SessionEnded: {e.Reason}");
// if (Config.PowerOffOnShutdown)
// {
// PowerOff();
// }
//}
}
private void LgDevice_PowerStateChangedEvent(object sender, EventArgs e)
{
if (!(sender is LgDevice device))
{
return;
}
if (device.CurrentState == LgDevice.PowerState.Active)
{
if (Config.SetSelectedDeviceByPowerOn)
{
SelectedDevice = device;
}
}
}
private void MonitorProcesses()
{
var enableMonitoring = ShouldMonitorProcesses();
if (enableMonitoring && _monitorTask == null)
{
_monitorTask = CheckProcesses();
}
else if (!enableMonitoring && _monitorTask != null)
{
_monitorTask = null;
}
}
public bool ShouldMonitorProcesses()
{
return Devices.Any(d => d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver) || _presets.Any(p => p.Triggers.Any(t => t.Trigger != PresetTriggerType.None));
}
public async Task CheckProcesses()
{
var wasConnected = Devices.Any(d => (d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver) && d.IsConnected());
_monitorTaskCounter++;
var validCounter = _monitorTaskCounter;
var lastProcessId = 0;
await Task.Delay(2000);
MonitorContext ??= new ProcessMonitorContext();
//Process[] lastProcesses = null;
while (validCounter == _monitorTaskCounter)
{
await Task.Delay(_poweredOffByScreenSaver ? 500 : 1000);
try
{
var applicableDevices = Devices.Where(d => d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver || 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())
{
continue;
}
if (_poweredOffByScreenSaver && !UserSessionInfo.UserLocalSession)
{
continue;
}
var processes = Process.GetProcesses();
if (_poweredOffByScreenSaver)
{
if (processes.Any(p => p.Id == _poweredOffByScreenSaverProcessId))
{
continue;
}
Logger.Debug("Screensaver stopped, waking");
_poweredOffByScreenSaver = false;
_poweredOffByScreenSaverProcessId = 0;
lastProcessId = 0;
WakeAfterStartupOrResume(PowerOnOffState.ScreenSaver);
continue;
}
var connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList();
if (!connectedDevices.Any())
{
if (wasConnected)
{
Logger.Debug("Process monitor: TV(s) where connected, but not any longer");
wasConnected = false;
continue;
}
else
{
var devices = applicableDevices.ToList();
foreach (var device in devices)
{
Logger.Debug($"Process monitor: test connection with {device.Name}...");
var test = await device.TestConnection();
Logger.Debug("Process monitor: test connection result: " + test);
}
connectedDevices = applicableDevices.Where(d => d.IsConnected()).ToList();
if (!connectedDevices.Any())
{
continue;
}
}
}
if (!wasConnected)
{
Logger.Debug("Process monitor: TV(s) where not connected, but connection has now been established");
wasConnected = true;
}
if (connectedDevices.Any(d => d.PowerOffOnScreenSaver || d.PowerOnAfterScreenSaver))
{
lastProcessId = await HandleScreenSaverProcessAsync(lastProcessId, processes, connectedDevices);
}
MonitorContext.Devices = connectedDevices.Where(d => d.TriggersEnabled).ToList();
MonitorContext.RunningProcesses = processes;
await ExecuteProcessPresets(MonitorContext);
}
catch (Exception ex)
{
Logger.Error("CheckProcesses: " + ex.ToLogString());
}
}
}
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.Where(d => d.PowerOffOnScreenSaver))
{
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(true).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 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;
}
context.IsNotificationDisabled = FormUtils.IsNotificationDisabled();
var (processId, isFullScreen) = FormUtils.GetForegroundProcessIdAndIfFullScreen();
if (processId == Environment.ProcessId)
{
return;
}
if (processId > 0)
{
var process = context.RunningProcesses.FirstOrDefault(p => p.Id == processId);
if (process?.ProcessName?.Contains(".scr") ?? false)
{
return;
}
context.ForegroundProcess = process;
context.ForegroundProcessIsFullScreen = isFullScreen;
}
else
{
context.ForegroundProcess = null;
context.ForegroundProcessIsFullScreen = false;
}
var changedProcesses = new List<Process>();
if (context.ForegroundProcess != null)
{
changedProcesses.Add(context.ForegroundProcess);
}
if (context.ForegroundProcess != null && context.ForegroundProcessIsFullScreen)
{
if (!context.LastFullScreenProcessName.Equals(context.ForegroundProcess.ProcessName))
{
context.LastFullScreenProcessName = context.ForegroundProcess.ProcessName;
//Logger.Debug($"Foreground fullscreen app detected: {context.ForegroundProcess.ProcessName}");
}
presets = presets.Where(p => p.Triggers.Any(t => t.Conditions.HasFlag(PresetConditionType.FullScreen)));
context.WasFullScreen = true;
}
else if (context.WasFullScreen)
{
presets = presets.Where(p => p.Triggers.Any(t => !t.Conditions.HasFlag(PresetConditionType.FullScreen)));
context.WasFullScreen = false;
context.LastFullScreenProcessName = string.Empty;
}
var isHDRActive = context.Devices.Any(d => d.IsUsingHDRPictureMode());
var isGsyncActive = LgDevice.ExternalServiceHandler("GsyncEnabled", new[] { "" });
var triggerContext = new PresetTriggerContext
{
IsHDRActive = isHDRActive,
IsGsyncActive = isGsyncActive,
ForegroundProcess = context.ForegroundProcess,
ForegroundProcessIsFullScreen = context.ForegroundProcessIsFullScreen,
ChangedProcesses = changedProcesses,
IsNotificationDisabled = context.IsNotificationDisabled
};
var toApplyPresets = new List<LgPreset>();
foreach (var preset in presets)
{
if (preset.Triggers.Any(t => t.TriggerActive(triggerContext)))
{
toApplyPresets.Add(preset);
}
}
if (toApplyPresets.Any())
{
LgPreset toApplyPreset;
if (toApplyPresets.Count > 1)
{
toApplyPreset = toApplyPresets.FirstOrDefault(p => p.Triggers.Any(t => !t.IncludedProcesses.Contains("*")));
toApplyPreset = toApplyPreset != null ? toApplyPreset : toApplyPresets.First();
}
else
{
toApplyPreset = toApplyPresets.First();
}
if (_lastTriggeredPreset != toApplyPreset)
{
await ApplyPreset(toApplyPreset);
_lastTriggeredPreset = toApplyPreset;
}
}
}
internal void AddCustomDevice(LgDevice device)
{
Devices.Add(device);
}
internal void RemoveCustomDevice(LgDevice device)
{
Devices.Remove(device);
if (!string.IsNullOrEmpty(device.MacAddress))
{
var presets = _presets.Where(p => !string.IsNullOrEmpty(p.DeviceMacAddress) && p.DeviceMacAddress.Equals(device.MacAddress, StringComparison.OrdinalIgnoreCase)).ToList();
presets.ForEach(preset =>
{
preset.DeviceMacAddress = null;
});
}
if (SelectedDevice == device)
{
SelectedDevice = Devices.Any() ? Devices.First() : null;
}
}
public void PowerSettingChanged(WindowsPowerSetting setting)
{
if (setting == WindowsPowerSetting.Off)
{
var devices = Devices.Where(d => d.PowerOffByWindows).ToList();
PowerOffDevices(devices, PowerOnOffState.StandBy);
}
else if (setting == WindowsPowerSetting.On)
{
var devices = Devices.Where(d => d.PowerOnByWindows).ToList();
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);
}
}
private void SendConfigToService()
{
if (!Utils.IsServiceRunning())
{
return;
}
var json = JsonConvert.SerializeObject(Config);
var message = new SvcMessage
{
MessageType = SvcMessageType.SetLgConfig,
Data = json
};
PipeUtils.SendMessage(message);
}
}
}
@@ -0,0 +1,32 @@
using System.Collections.Generic;
namespace ColorControl.Services.LG
{
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 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 bool SetSelectedDeviceByPowerOn { get; set; }
public LgServiceConfig()
{
PowerOnDelayAfterResume = 5000;
ShutdownDelay = 1000;
DeviceSearchKey = "[LG]";
PowerOnRetries = 10;
Devices = new List<LgDevice>();
GameBarTop = 100;
GameBarLeft = 100;
}
}
}
@@ -1,5 +1,5 @@

namespace ColorControl
namespace ColorControl.Services.LG
{
partial class RemoteControlForm
{
@@ -29,21 +29,25 @@ namespace ColorControl
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(RemoteControlForm));
this.flowPanel = new System.Windows.Forms.FlowLayoutPanel();
this.btnClose = new System.Windows.Forms.Button();
this.lblUC = new System.Windows.Forms.Label();
this.chkDirectMode = new System.Windows.Forms.CheckBox();
this.pnlMouse = new System.Windows.Forms.Panel();
this.lblPos = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.label3 = 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(12, 16);
this.flowPanel.Location = new System.Drawing.Point(12, 185);
this.flowPanel.Name = "flowPanel";
this.flowPanel.Size = new System.Drawing.Size(787, 164);
this.flowPanel.Size = new System.Drawing.Size(817, 120);
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);
@@ -56,7 +60,7 @@ namespace ColorControl
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(796, 0);
this.btnClose.Location = new System.Drawing.Point(826, 0);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(14, 23);
this.btnClose.TabIndex = 1;
@@ -68,7 +72,7 @@ namespace ColorControl
//
this.lblUC.AutoSize = true;
this.lblUC.ForeColor = System.Drawing.Color.Maroon;
this.lblUC.Location = new System.Drawing.Point(130, 0);
this.lblUC.Location = new System.Drawing.Point(12, 0);
this.lblUC.Name = "lblUC";
this.lblUC.Size = new System.Drawing.Size(100, 13);
this.lblUC.TabIndex = 0;
@@ -78,8 +82,8 @@ namespace ColorControl
//
this.chkDirectMode.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.chkDirectMode.AutoSize = true;
this.chkDirectMode.ForeColor = System.Drawing.Color.DarkGray;
this.chkDirectMode.Location = new System.Drawing.Point(12, 643);
this.chkDirectMode.ForeColor = System.Drawing.Color.White;
this.chkDirectMode.Location = new System.Drawing.Point(12, 718);
this.chkDirectMode.Name = "chkDirectMode";
this.chkDirectMode.Size = new System.Drawing.Size(105, 17);
this.chkDirectMode.TabIndex = 2;
@@ -93,9 +97,9 @@ namespace ColorControl
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.pnlMouse.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.pnlMouse.Location = new System.Drawing.Point(12, 191);
this.pnlMouse.Location = new System.Drawing.Point(12, 311);
this.pnlMouse.Name = "pnlMouse";
this.pnlMouse.Size = new System.Drawing.Size(787, 446);
this.pnlMouse.Size = new System.Drawing.Size(817, 401);
this.pnlMouse.TabIndex = 3;
this.pnlMouse.MouseEnter += new System.EventHandler(this.pnlMouse_MouseEnter);
this.pnlMouse.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pnlMouse_MouseMove);
@@ -106,17 +110,53 @@ namespace ColorControl
this.lblPos.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.lblPos.AutoSize = true;
this.lblPos.ForeColor = System.Drawing.Color.Maroon;
this.lblPos.Location = new System.Drawing.Point(719, 643);
this.lblPos.Location = new System.Drawing.Point(749, 718);
this.lblPos.Name = "lblPos";
this.lblPos.Size = new System.Drawing.Size(0, 13);
this.lblPos.TabIndex = 1;
//
// label1
//
this.label1.AutoSize = true;
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(12, 26);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(538, 52);
this.label1.TabIndex = 4;
this.label1.Text = resources.GetString("label1.Text");
//
// label2
//
this.label2.AutoSize = true;
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(231, 91);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(216, 52);
this.label2.TabIndex = 5;
this.label2.Text = "Mouse:\r\n- left button: activate/open item\r\n- right button: go back\r\n- wheel: scro" +
"ll through menus (only vertically)";
//
// label3
//
this.label3.AutoSize = true;
this.label3.ForeColor = System.Drawing.Color.White;
this.label3.Location = new System.Drawing.Point(12, 91);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(180, 91);
this.label3.TabIndex = 6;
this.label3.Text = "Keyboard keys:\r\n- arrow keys: navigate the menu\r\n- backspace: go back\r\n- escape: " +
"exit\r\n- S: open picture settings\r\n- H: open Home\r\n- media keys: volume up/down/m" +
"ute";
//
// RemoteControlForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(811, 660);
this.ClientSize = new System.Drawing.Size(841, 735);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.lblPos);
this.Controls.Add(this.pnlMouse);
this.Controls.Add(this.chkDirectMode);
@@ -132,6 +172,7 @@ namespace ColorControl
this.Text = "RemoteControlForm";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.RemoteControlForm_FormClosed);
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);
@@ -149,5 +190,8 @@ namespace ColorControl
private System.Windows.Forms.CheckBox chkDirectMode;
private System.Windows.Forms.Panel pnlMouse;
private System.Windows.Forms.Label lblPos;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label3;
}
}
@@ -7,7 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl
namespace ColorControl.Services.LG
{
partial class RemoteControlForm : Form
{
@@ -116,24 +116,24 @@ namespace ColorControl
{
if (chkDirectMode.Checked)
{
MessageForms.InfoOk(
@"If you enable Direct Mode you will be able to control the TV directly via your keyboard and mouse.
Note that your mouse will be captured in a box and that due to pointer accelaration the control will not be perfect.
// MessageForms.InfoOk(
//@"If you enable Direct Mode you will be able to control the TV directly via your keyboard and mouse.
//Note that your mouse will be captured in a box and that due to pointer accelaration the control will not be perfect.
Keyboard keys:
- arrow keys: navigate the menu
- backspace: go back
- escape: exit
- S: open picture settings
- media keys: volume up/down/mute
//Keyboard keys:
//- arrow keys: navigate the menu
//- backspace: go back
//- escape: exit
//- S: open picture settings
//- media keys: volume up/down/mute
Mouse:
- left button: activate/open item
- right button: go back
- wheel: scroll through menus (only vertically)
//Mouse:
//- left button: activate/open item
//- right button: go back
//- wheel: scroll through menus (only vertically)
Press F1 to stop Direct Mode."
);
//Press F1 to stop Direct Mode."
// );
if (_mouseService == null)
{
@@ -216,6 +216,11 @@ Press F1 to stop Direct Mode."
}
}
private void RemoteControlForm_Shown(object sender, EventArgs e)
{
chkDirectMode.Checked = true;
}
private void pnlMouse_MouseMove(object sender, MouseEventArgs e)
{
HandleMouse(e);
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="label1.Text" xml:space="preserve">
<value>This form allows you to control the TV directly via your keyboard and mouse.
Note that your mouse will be captured in a box and that due to pointer acceleration the control will not be perfect.
Press F1 to stop Direct Mode.</value>
</data>
</root>
+81
View File
@@ -0,0 +1,81 @@
namespace ColorControl.Services.LG
{
partial class RemoteControlPanel
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pbRemote = new System.Windows.Forms.PictureBox();
this.btnRcDirect = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.pbRemote)).BeginInit();
this.SuspendLayout();
//
// pbRemote
//
this.pbRemote.Dock = System.Windows.Forms.DockStyle.Fill;
this.pbRemote.Image = global::ColorControl.Properties.Resources.LG_remote;
this.pbRemote.Location = new System.Drawing.Point(0, 0);
this.pbRemote.Name = "pbRemote";
this.pbRemote.Size = new System.Drawing.Size(129, 468);
this.pbRemote.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.pbRemote.TabIndex = 0;
this.pbRemote.TabStop = false;
this.pbRemote.Click += new System.EventHandler(this.pbRemote_Click);
this.pbRemote.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pbRemote_MouseMove);
//
// btnRcDirect
//
this.btnRcDirect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnRcDirect.Location = new System.Drawing.Point(71, 7);
this.btnRcDirect.Name = "btnRcDirect";
this.btnRcDirect.Size = new System.Drawing.Size(55, 23);
this.btnRcDirect.TabIndex = 3;
this.btnRcDirect.Text = "Direct";
this.btnRcDirect.UseVisualStyleBackColor = true;
this.btnRcDirect.Click += new System.EventHandler(this.button1_Click);
//
// RemoteControlPanel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.Controls.Add(this.btnRcDirect);
this.Controls.Add(this.pbRemote);
this.Name = "RemoteControlPanel";
this.Size = new System.Drawing.Size(129, 468);
this.Load += new System.EventHandler(this.RemoteControlPanel_Load);
((System.ComponentModel.ISupportInitialize)(this.pbRemote)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox pbRemote;
private System.Windows.Forms.Button btnRcDirect;
}
}
@@ -0,0 +1,188 @@
using ColorControl.Common;
using NLog;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace ColorControl.Services.LG
{
partial class RemoteControlPanel : UserControl
{
private static int Range = 35;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private LgService _lgService;
private List<LgPreset> _buttons;
private RemoteControlForm _remoteControlForm;
private Bitmap _backupImage;
private LgPreset _lastHoveredPreset;
public RemoteControlPanel(LgService lgService, List<LgPreset> buttons)
{
_lgService = lgService;
_buttons = buttons;
InitializeComponent();
_backupImage = new Bitmap(pbRemote.Image);
var txtCoords = Utils.GetResourceFile("RC_coords.txt");
foreach (var line in txtCoords.Split('\n'))
{
var columns = line.Split(';');
var name = columns[0];
var preset = _buttons.FirstOrDefault(p => p.name.Equals(name));
if (preset != null)
{
preset.X = int.Parse(columns[1]);
preset.Y = int.Parse(columns[2]);
}
}
}
private void RemoteControlPanel_Load(object sender, EventArgs e)
{
}
private void ButtonClick(object sender, EventArgs e)
{
if (e is MouseEventArgs)
{
var preset = (LgPreset)(sender as Button).Tag;
var _ = _lgService.ApplyPreset(preset);
}
}
private void pbRemote_Click(object sender, EventArgs e)
{
if (e is MouseEventArgs mouseArgs)
{
var (posX, posY) = GetCoordsInImage(mouseArgs.X, mouseArgs.Y);
var preset = _buttons.FirstOrDefault(p => posX >= (p.X - Range) && posX <= (p.X + Range) && posY >= (p.Y - Range) && posY <= (p.Y + Range));
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);
}
}
}
private (int, int) GetCoordsInImage(int curX, int curY)
{
var width = pbRemote.Width;
var aspect = 0.27D;
var currentAspect = (double)pbRemote.Width / pbRemote.Height;
if (currentAspect > aspect)
{
width = (int)(aspect * pbRemote.Height);
curX = curX - ((pbRemote.Width - width) / 2);
}
else
{
var height = (int)(pbRemote.Width / aspect);
curY = curY - ((pbRemote.Height - height) / 2);
}
var factor = 405D / width;
var posX = (int)(curX * factor);
var posY = (int)(curY * factor);
return (posX, posY);
}
private void btnRcDirect_Click(object sender, EventArgs e)
{
}
private void pbRemote_MouseMove(object sender, MouseEventArgs mouseArgs)
{
var (posX, posY) = GetCoordsInImage(mouseArgs.X, mouseArgs.Y);
var preset = _buttons.FirstOrDefault(p => posX >= (p.X - Range) && posX <= (p.X + Range) && posY >= (p.Y - Range) && posY <= (p.Y + Range));
var refresh = false;
if (_lastHoveredPreset != null && (preset == null || preset != _lastHoveredPreset))
{
var image = (Bitmap)pbRemote.Image;
for (var y = Math.Max(0, _lastHoveredPreset.Y - Range); y < _lastHoveredPreset.Y + Range; y++)
{
for (var x = Math.Max(0, _lastHoveredPreset.X - Range); x < _lastHoveredPreset.X + Range; x++)
{
image.SetPixel(x, y, _backupImage.GetPixel(x, y));
}
}
refresh = true;
}
if (preset != null && preset != _lastHoveredPreset)
{
var image = (Bitmap)pbRemote.Image;
var rec = new Rectangle(Math.Max(0, preset.X - Range), Math.Max(0, preset.Y - Range), Range * 2, Range * 2);
using (var g = Graphics.FromImage(image))
{
using (var cloud_brush = new SolidBrush(Color.FromArgb(128, Color.White)))
{
g.FillEllipse(cloud_brush, rec);
}
}
//for (var y = preset.Y - 30; y < preset.Y + 30; y++)
//{
// for (var x = preset.X - 30; x < preset.X + 30; x++)
// {
// var pixel = image.GetPixel(x, y);
// image.SetPixel(x, y, Color.FromArgb(150, 0, 0));
// }
//}
refresh = true;
}
if (refresh)
{
pbRemote.Refresh();
}
_lastHoveredPreset = preset;
}
private void button1_Click(object sender, EventArgs e)
{
if (_remoteControlForm == null || _remoteControlForm.IsDisposed)
{
var buttons = _lgService.GetRemoteControlButtons();
_remoteControlForm = new RemoteControlForm(_lgService, buttons);
_remoteControlForm.Show();
}
else
{
_remoteControlForm.Show();
}
}
}
}
@@ -0,0 +1,40 @@
using Newtonsoft.Json;
using NvAPIWrapper.Display;
using System;
using System.Collections.Generic;
namespace ColorControl.Services.NVIDIA
{
public class ColorDataConverter : JsonConverter<ColorData>
{
public override bool CanWrite { get { return false; } }
public override ColorData ReadJson(JsonReader reader, Type objectType, ColorData existingValue, bool hasExistingValue, JsonSerializer serializer)
{
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 void WriteJson(JsonWriter writer, ColorData value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
@@ -1,7 +1,7 @@
using NvAPIWrapper.Display;
using System.Collections.Generic;
namespace ColorControl
namespace ColorControl.Services.NVIDIA
{
class NvDisplayInfo
{
@@ -1,13 +1,19 @@
using NvAPIWrapper.Display;
using ColorControl.Common;
using ColorControl.Services.Common;
using nspector.Common;
using NvAPIWrapper.Display;
using NvAPIWrapper.Native.Display;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ColorControl
namespace ColorControl.Services.NVIDIA
{
class NvPreset : PresetBase
{
public static NvService NvService { get; set; }
public bool applyColorData { get; set; }
public ColorData colorData { get; set; }
public bool applyHDR { get; set; }
@@ -17,12 +23,17 @@ namespace ColorControl
public bool ditheringEnabled { get; set; }
public bool applyRefreshRate { get; set; }
public uint refreshRate { get; set; }
public bool applyResolution { get; set; }
public uint resolutionWidth { get; set; }
public uint resolutionHeight { get; set; }
public bool primaryDisplay { get; set; }
public string displayName { get; set; }
public uint ditheringBits { get; set; }
public uint ditheringMode { get; set; }
public bool applyDriverSettings { get; set; }
public Dictionary<uint, uint> driverSettings { get; set; }
public NvPreset()
public NvPreset() : base()
{
applyColorData = true;
applyDithering = false;
@@ -32,9 +43,14 @@ namespace ColorControl
toggleHDR = false;
applyRefreshRate = false;
refreshRate = 60;
applyResolution = false;
resolutionWidth = 0;
resolutionHeight = 0;
primaryDisplay = true;
ditheringBits = 1;
ditheringMode = 4;
applyDriverSettings = false;
driverSettings = new Dictionary<uint, uint>();
}
public NvPreset(ColorData colorData) : this()
@@ -43,7 +59,7 @@ namespace ColorControl
this.colorData = colorData;
}
public NvPreset(NvPreset preset): this()
public NvPreset(NvPreset preset) : this()
{
id = GetNewId();
@@ -51,7 +67,7 @@ namespace ColorControl
displayName = preset.displayName;
var colorData = preset.colorData;
this.colorData = new ColorData(colorData.ColorFormat, dynamicRange: colorData.DynamicRange, colorDepth: colorData.ColorDepth, colorSelectionPolicy: colorData.SelectionPolicy);
this.colorData = new ColorData(colorData.ColorFormat, dynamicRange: colorData.DynamicRange, colorDepth: colorData.ColorDepth, colorSelectionPolicy: ColorDataSelectionPolicy.User);
applyColorData = preset.applyColorData;
applyHDR = preset.applyHDR;
@@ -61,6 +77,11 @@ namespace ColorControl
ditheringEnabled = preset.ditheringEnabled;
applyRefreshRate = preset.applyRefreshRate;
refreshRate = preset.refreshRate;
applyResolution = preset.applyResolution;
resolutionWidth = preset.resolutionWidth;
resolutionHeight = preset.resolutionHeight;
applyDriverSettings = preset.applyDriverSettings;
driverSettings = new Dictionary<uint, uint>(preset.driverSettings);
}
public NvPreset Clone()
@@ -72,14 +93,15 @@ namespace ColorControl
public static string[] GetColumnNames()
{
//return new[] { "BPC", "Format", "Dynamic range", "Toggle HDR", "Shortcut" };
return new[] { "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Refresh rate|100", "Dithering", "HDR", "Shortcut", "Apply on startup" };
return new[] { "Name", "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Refresh rate|100", "Resolution|120", "Dithering", "HDR", "Driver settings|300", "Shortcut", "Apply on startup" };
}
public override List<string> GetDisplayValues(Config config = null)
{
var values = new List<string>();
values.Add(name);
var display = string.Format("{0}", primaryDisplay ? "Primary" : displayName);
values.Add(display);
@@ -88,15 +110,22 @@ namespace ColorControl
values.Add(colorSettings);
values.Add(string.Format("{0}: {1}Hz", applyRefreshRate ? "Included" : "Excluded", refreshRate));
if (applyResolution || resolutionWidth > 0)
{
values.Add(string.Format("{0}: {1}x{2}", applyResolution ? "Included" : "Excluded", resolutionWidth, resolutionHeight));
}
else
{
values.Add(string.Format("Excluded"));
}
var dithering = GetDitheringDescription();
values.Add(string.Format("{0}: {1}", applyDithering ? "Included" : "Excluded", dithering));
values.Add(string.Format("{0}: {1}", applyHDR ? "Included" : "Excluded", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled"));
//values.Add(colorData.ColorDepth.ToString());
//values.Add(colorData.ColorFormat.ToString());
//values.Add(colorData.DynamicRange.ToString());
//values.Add(toggleHDR.ToString());
values.Add(string.Format("{0}: {1}", applyDriverSettings ? "Included" : "Excluded", GetDriverSettingsDescription()));
values.Add(shortcut);
values.Add(string.Format("{0}", config?.NvPresetId_ApplyOnStartup == id ? "Yes" : string.Empty));
@@ -104,6 +133,27 @@ namespace ColorControl
return values;
}
public string GetDriverSettingsDescription()
{
if (driverSettings.Count == 0)
{
return "None";
}
var values = new List<string>();
foreach (var driverSetting in driverSettings)
{
var settingMeta = NvService.GetSettingMeta(driverSetting.Key);
var settingValue = settingMeta.DwordValues.FirstOrDefault(s => s.Value == driverSetting.Value);
values.Add($"{settingMeta.SettingName}: {settingValue?.ValueName ?? "Unknown"}");
}
return string.Join(", ", values);
}
public string GetDitheringDescription(string disabledText = "Disabled")
{
var dithering = ditheringEnabled ? string.Empty : disabledText;
@@ -139,34 +189,52 @@ namespace ColorControl
return presets;
}
public string GetTextForMenuItem()
public override string GetTextForMenuItem()
{
var sb = new StringBuilder();
if (displayName != null)
if (!string.IsNullOrEmpty(name))
{
sb.AppendFormat("Display: {0} / ", displayName);
}
if (applyColorData)
{
var colorSettings = string.Format("Format: {0}, {1}, {2}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange);
sb.Append(colorSettings);
sb.Append(name);
sb.Append(" / ");
}
if (applyRefreshRate)
else
{
sb.AppendFormat("{0}Hz", refreshRate);
sb.Append(" / ");
}
if (applyDithering)
{
var dithering = GetDitheringDescription("No");
sb.AppendFormat("Dithering: {0}", dithering);
sb.Append(" / ");
}
if (applyHDR)
{
sb.AppendFormat("HDR: {0}", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled");
sb.Append(" / ");
if (displayName != null)
{
sb.AppendFormat("Display: {0} / ", displayName);
}
if (applyColorData)
{
var colorSettings = string.Format("Format: {0}, {1}, {2}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange);
sb.Append(colorSettings);
sb.Append(" / ");
}
if (applyRefreshRate)
{
sb.AppendFormat("{0}Hz", refreshRate);
sb.Append(" / ");
}
if (applyResolution)
{
sb.AppendFormat("{0}x{1}", resolutionWidth, resolutionHeight);
sb.Append(" / ");
}
if (applyDithering)
{
var dithering = GetDitheringDescription("No");
sb.AppendFormat("Dithering: {0}", dithering);
sb.Append(" / ");
}
if (applyHDR)
{
sb.AppendFormat("HDR: {0}", toggleHDR ? "Toggle" : HDREnabled ? "Enabled" : "Disabled");
sb.Append(" / ");
}
if (applyDriverSettings)
{
sb.AppendFormat("DRS: {0}", GetDriverSettingsDescription());
sb.Append(" / ");
}
}
return sb.ToString(0, Math.Max(0, sb.Length - 3));
@@ -196,13 +264,27 @@ namespace ColorControl
{
dynamicRange = (ColorDataDynamicRange)Enum.ToObject(typeof(ColorDataDynamicRange), value);
}
if (dictionary.TryGetValue("SelectionPolicy", out value))
if (dictionary.TryGetValue("SelectionPolicy", out _))
{
selectionPolicy = (ColorDataSelectionPolicy)Enum.ToObject(typeof(ColorDataSelectionPolicy), value);
selectionPolicy =
colorDepth == ColorDataDepth.Default &&
format >= ColorDataFormat.Default &&
colorimetry >= ColorDataColorimetry.Default &&
dynamicRange == ColorDataDynamicRange.Auto ?
ColorDataSelectionPolicy.BestQuality : selectionPolicy;
}
return new ColorData(format, dynamicRange: dynamicRange, colorimetry: colorimetry, colorDepth: colorDepth, colorSelectionPolicy: selectionPolicy);
return new ColorData(format, dynamicRange: dynamicRange, colorimetry: colorimetry, colorDepth: colorDepth, colorSelectionPolicy: selectionPolicy, desktopColorDepth: ColorDataDesktopDepth.Default);
}
internal void UpdateDriverSetting(SettingItem settingItem, uint settingValue)
{
driverSettings[settingItem.SettingId] = settingValue;
}
internal void ResetDriverSetting(uint settingId)
{
driverSettings.Remove(settingId);
}
}
}
+753
View File
@@ -0,0 +1,753 @@
using ColorControl.Common;
using ColorControl.Services.Common;
using nspector.Common;
using nspector.Common.Meta;
using NvAPIWrapper.Display;
using NvAPIWrapper.Native.Display;
using NvAPIWrapper.Native.GPU.Structures;
using NWin32.NativeTypes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ColorControl.Services.NVIDIA
{
enum NvDitherState
{
[Description("Auto")]
Auto = 0,
[Description("Enabled")]
Enabled = 1,
[Description("Disabled")]
Disabled = 2
}
enum NvDitherBits
{
[Description("6-bit")]
Bits6 = 0,
[Description("8-bit")]
Bits8 = 1,
[Description("10-bit")]
Bits10 = 2
}
enum NvDitherMode
{
[Description("Spatial Dynamic")]
SpatialDynamic = 0,
[Description("Spatial Static")]
SpatialStatic = 1,
[Description("Spatial Dynamic 2x2")]
SpatialDynamic2x2 = 2,
[Description("Spatial Static 2x2")]
SpatialStatic2x2 = 3,
[Description("Temporal")]
Temporal = 4
}
class NvService : GraphicsService<NvPreset>
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
[DllImport(@"nvapi64", EntryPoint = @"nvapi_QueryInterface", CallingConvention = CallingConvention.Cdecl,
PreserveSig = true)]
private static extern IntPtr NvAPI64_QueryInterface(uint interfaceId);
public delegate int NvAPI_Disp_SetDitherControl(
[In] PhysicalGPUHandle physicalGpu,
[In] uint OutputId,
[In] uint state,
[In] uint bits,
[In] uint mode
);
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 uint version;
public int state;
public int bits;
public int mode;
public uint bitsCaps;
public uint modeCaps;
};
public override string ServiceName => "NVIDIA";
private Display _currentDisplay;
private string _baseProfileName = "";
private DrsSettingsService _drs;
private DrsSettingsMetaService _meta;
private List<SettingItem> _settings = new List<SettingItem>();
private Semaphore _semaphore = new Semaphore(1, 1);
public const uint DRS_GSYNC_APPLICATION_MODE = 294973784;
public const uint DRS_VSYNC_CONTROL = 11041231;
//public const uint DRS_ULTRA_LOW_LATENCY = 0x10835000;
public const uint DRS_PRERENDERED_FRAMES = 8102046;
public const uint DRS_FRAME_RATE_LIMITER_V3 = 277041154;
private static readonly List<uint> _driverSettingIds = new() { DRS_GSYNC_APPLICATION_MODE, DRS_VSYNC_CONTROL, DRS_PRERENDERED_FRAMES, DRS_FRAME_RATE_LIMITER_V3 };
public NvService(string dataPath) : base(dataPath, "NvPresets.json")
{
NvPreset.NvService = this;
AddJsonConverter(new ColorDataConverter());
LoadPresets();
}
public static bool ExecutePresetAsync(string idOrName)
{
try
{
var nvService = new NvService(Program.DataDir);
var result = nvService.ApplyPreset(idOrName);
if (!result)
{
Console.WriteLine("Preset not found or error while executing.");
}
return result;
}
catch (Exception ex)
{
Console.WriteLine("Error executing preset: " + ex.ToLogString());
return false;
}
}
public Display GetCurrentDisplay()
{
if (!HasDisplaysAttached())
{
_currentDisplay = null;
return null;
}
if (_currentDisplay == null)
{
_currentDisplay = Display.GetDisplays()[0];
}
return _currentDisplay;
}
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 reinitialize ? false : HasDisplaysAttached(true);
}
}
public void GlobalSave()
{
SavePresets();
}
protected override List<NvPreset> GetDefaultPresets()
{
return NvPreset.GetDefaultPresets();
}
private void SavePresets()
{
Utils.WriteObject(_presetsFilename, _presets);
}
private void SetCurrentDisplay(NvPreset preset)
{
if (preset.primaryDisplay)
{
var displayId = DisplayDevice.GetGDIPrimaryDisplayDevice().DisplayId;
_currentDisplay = Display.GetDisplays().FirstOrDefault(x => x.DisplayDevice.DisplayId == displayId);
}
else
{
_currentDisplay = Display.GetDisplays().FirstOrDefault(x => x.Name.Equals(preset.displayName));
}
}
public bool ApplyPreset(string idOrName)
{
var preset = GetPresetByIdOrName(idOrName);
if (preset != null)
{
return ApplyPreset(preset, Program.AppContext);
}
else
{
return false;
}
}
public override bool ApplyPreset(NvPreset preset)
{
return ApplyPreset(preset, Program.AppContext);
}
public bool ApplyPreset(NvPreset preset, AppContext appContext)
{
var result = true;
if (!HasDisplaysAttached())
{
return false;
}
SetCurrentDisplay(preset);
var display = GetCurrentDisplay();
if (display == null)
{
return false;
}
var hdrEnabled = IsHDREnabled();
var newHdrEnabled = preset.applyHDR && (preset.HDREnabled || (preset.toggleHDR && !hdrEnabled));
var applyHdr = preset.applyHDR && (preset.toggleHDR || preset.HDREnabled != hdrEnabled);
if (preset.applyColorData && (ColorDataDiffers(preset.colorData) || (!newHdrEnabled && preset.applyColorData && preset.colorData.Colorimetry != ColorDataColorimetry.Auto)))
{
//if (hdrEnabled)
//{
// SetHDRState(display, false);
// applyHdr = false;
//}
if (preset.applyRefreshRate || preset.applyResolution)
{
var timing = display.DisplayDevice.CurrentTiming;
if ((preset.applyRefreshRate && preset.refreshRate < timing.Extra.RefreshRate) || (preset.applyResolution && preset.resolutionWidth < timing.HorizontalVisible))
{
SetMode(preset.applyResolution ? preset.resolutionWidth : 0, preset.applyResolution ? preset.resolutionHeight : 0, preset.applyRefreshRate ? preset.refreshRate : 0, true);
}
}
//if (preset.applyRefreshRate)
//{
// var timing = display.DisplayDevice.CurrentTiming;
// if (preset.refreshRate < timing.Extra.RefreshRate)
// {
// SetRefreshRate(preset.refreshRate, true);
// }
//}
try
{
display.DisplayDevice.SetColorData(preset.colorData);
}
catch (Exception ex)
{
Logger.Error($"SetColorData threw an exception: {ex.Message}");
result = false;
}
//if (hdrEnabled && newHdrEnabled)
//{
// SetHDRState(display, true, useSwitch: appContext.StartUpParams.NoGui);
// applyHdr = false;
//}
}
if (applyHdr)
{
var colorData = preset.applyColorData ? preset.colorData : display.DisplayDevice.CurrentColorData;
SetHDRState(display, newHdrEnabled, colorData, appContext.StartUpParams.NoGui);
}
if (preset.applyRefreshRate || preset.applyResolution)
{
if (!SetMode(preset.applyResolution ? preset.resolutionWidth : 0, preset.applyResolution ? preset.resolutionHeight : 0, preset.applyRefreshRate ? preset.refreshRate : 0, true))
{
result = false;
}
//var timing = display.DisplayDevice.CurrentTiming;
//if (preset.refreshRate != timing.Extra.RefreshRate && !SetRefreshRate(preset.refreshRate, true))
//{
// result = false;
//}
}
if (preset.applyDithering)
{
if (!SetDithering(preset.ditheringEnabled ? NvDitherState.Enabled : NvDitherState.Disabled, preset: preset))
{
result = false;
}
}
if (preset.applyDriverSettings)
{
SetDriverSettings(preset);
}
_lastAppliedPreset = preset;
PresetApplied();
return result;
}
private void SetDriverSettings(NvPreset preset)
{
var settings = new List<KeyValuePair<uint, string>>();
foreach (var keyValue in preset.driverSettings)
{
var settingMeta = _meta.GetSettingMeta(keyValue.Key);
var value = settingMeta.DwordValues.FirstOrDefault(v => v.Value == keyValue.Value);
if (value == null)
{
continue;
}
settings.Add(new KeyValuePair<uint, string>(keyValue.Key, value.ValueName));
}
_drs.StoreSettingsToProfile(_baseProfileName, settings);
}
private ColorData GetCurrentColorData(Display display)
{
try
{
return display.DisplayDevice.CurrentColorData;
}
catch (Exception e)
{
Logger.Error("Error while reading current color data: " + e.Message);
return new ColorData();
}
}
private bool ColorDataDiffers(ColorData colorData)
{
var display = GetCurrentDisplay();
var currentColorData = GetCurrentColorData(display);
var settingRange = colorData.DynamicRange == ColorDataDynamicRange.Auto ? colorData.ColorFormat == ColorDataFormat.RGB ? ColorDataDynamicRange.VESA : ColorDataDynamicRange.CEA : colorData.DynamicRange;
var settingSpace = colorData.Colorimetry;
return colorData.ColorFormat != currentColorData.ColorFormat || colorData.ColorDepth != currentColorData.ColorDepth || settingRange != currentColorData.DynamicRange || settingSpace != currentColorData.Colorimetry;
}
private void SetHDRState(Display display, bool enabled, ColorData colorData = null, bool useSwitch = false)
{
if (useSwitch)
{
if (enabled != IsHDREnabled())
{
CCD.SetHDRState(enabled, display.Name);
//ToggleHDR();
}
return;
}
CCD.SetHDRState(enabled, display.Name);
//var newMaster = new MasteringDisplayColorData();
//var hdr = new HDRColorData(enabled ? ColorDataHDRMode.UHDA : ColorDataHDRMode.Off, newMaster, colorData?.ColorFormat, colorData?.DynamicRange, colorData?.ColorDepth);
//display.DisplayDevice.SetHDRColorData(hdr);
// HDR will not always be disabled this way, then we can only disable it through the display settings
//if (!enabled && IsHDREnabled())
//{
// CCD.SetHDRState(enabled);
// //ToggleHDR();
//}
//else if (enabled)
//{
// // Currectly there seems to be a bug that after enabling HDR via NVAPI, some settings are only applied upon opening the Display Settings...
// ////OpenDisplaySettings();
//}
}
public bool SetDithering(NvDitherState state, uint bits = 1, uint mode = 4, NvPreset preset = null)
{
var result = true;
var ptr = NvAPI64_QueryInterface(0xDF0DFCDD);
if (ptr != IntPtr.Zero)
{
var delegateValue = Marshal.GetDelegateForFunctionPointer(ptr, typeof(NvAPI_Disp_SetDitherControl)) as NvAPI_Disp_SetDitherControl;
var display = GetCurrentDisplay();
if (display == null)
{
return false;
}
var displayDevice = display.DisplayDevice;
var gpuHandle = displayDevice.PhysicalGPU.Handle;
var displayId = displayDevice.DisplayId;
if (state == NvDitherState.Auto)
{
// These seem to be ignored in Auto-state
bits = 0;
mode = 0;
}
else if (preset != null)
{
bits = preset.ditheringBits;
mode = preset.ditheringMode;
}
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}");
result = false;
}
}
else
{
Logger.Error($"Could not set dithering because the function NvAPI_Disp_SetDitherControl could not be found");
result = false;
}
return result;
}
public NV_GPU_DITHER_CONTROL_V1 GetDithering()
{
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 display = GetCurrentDisplay();
if (display == null)
{
dither.state = -1;
return dither;
}
var displayDevice = display.DisplayDevice;
var displayId = displayDevice.DisplayId;
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;
}
}
else
{
dither.state = -2;
}
return dither;
}
public bool SetMode(uint resolutionWidth = 0, uint resolutionHeight = 0, uint refreshRate = 0, bool updateRegistry = false)
{
var display = GetCurrentDisplay();
if (display == null)
{
return false;
}
var desktopRect = display.DisplayDevice.ScanOutInformation.SourceDesktopRectangle;
if (refreshRate == 0)
{
var timing = display.DisplayDevice.CurrentTiming;
refreshRate = (uint)timing.Extra.RefreshRate;
}
if (resolutionWidth == 0)
{
resolutionWidth = (uint)desktopRect.Width;
resolutionHeight = (uint)desktopRect.Height;
}
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
return SetRefreshRateInternal(display.Name, (int)refreshRate, portrait, (int)resolutionWidth, (int)resolutionHeight, updateRegistry);
}
public List<uint> GetAvailableRefreshRates(NvPreset preset = null)
{
if (preset != null)
{
SetCurrentDisplay(preset);
}
var display = GetCurrentDisplay();
if (display == null)
{
return new List<uint>();
}
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
var desktopRect = display.DisplayDevice.ScanOutInformation.SourceDesktopRectangle;
return GetAvailableRefreshRatesInternal(display.Name, portrait, desktopRect.Width, desktopRect.Height);
}
public List<DEVMODEA> GetAvailableResolutions(NvPreset preset = null)
{
if (preset != null)
{
SetCurrentDisplay(preset);
}
var display = GetCurrentDisplay();
if (display == null)
{
return new List<DEVMODEA>();
}
var portrait = new[] { Rotate.Degree90, Rotate.Degree270 }.Contains(display.DisplayDevice.ScanOutInformation.SourceToTargetRotation);
var timing = display.DisplayDevice.CurrentTiming;
return GetAvailableResolutionsInternal(display.Name, portrait, (uint)(timing.Extra.RefreshRate));
}
public bool IsHDREnabled()
{
var display = GetCurrentDisplay();
if (display == null)
{
return false;
}
var displayDevice = display.DisplayDevice;
var hdr = displayDevice.HDRColorData;
return hdr?.HDRMode == ColorDataHDRMode.UHDA;
}
public Display[] GetDisplays()
{
return Display.GetDisplays();
}
public List<NvDisplayInfo> GetDisplayInfos()
{
try
{
var displays = GetDisplays();
var list = new List<NvDisplayInfo>();
RefreshProfileSettings();
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 desktopRect = display.DisplayDevice.ScanOutInformation.SourceDesktopRectangle;
values.Add($"{desktopRect.Width}x{desktopRect.Height}");
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 settings = GetVisibleSettings();
var drsValues = new List<string>();
foreach (var setting in settings)
{
var settingMeta = GetSettingMeta(setting.SettingId);
var settingValue = settingMeta.DwordValues.FirstOrDefault(s => s.ValueName == setting.ValueText);
drsValues.Add($"{settingMeta.SettingName}: {settingValue?.ValueName ?? "Unknown"}");
}
values.Add(string.Join(", ", drsValues));
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;
}
}
public List<SettingItem> GetVisibleSettings()
{
return GetSettings().Where(s => _driverSettingIds.Contains(s.SettingId)).ToList();
}
public SettingMeta GetSettingMeta(uint settingId)
{
return _meta.GetSettingMeta(settingId);
}
public bool IsGsyncEnabled()
{
var setting = GetSettings().FirstOrDefault(s => s.SettingId == DRS_GSYNC_APPLICATION_MODE);
if (setting == null)
{
return false;
}
var settingMeta = GetSettingMeta(setting.SettingId);
var settingValue = settingMeta.DwordValues.FirstOrDefault(s => s.ValueName == setting.ValueText);
return settingValue.Value >= 1;
}
protected List<SettingItem> GetSettings()
{
_semaphore.WaitOne();
try
{
if (!_settings.Any())
{
RefreshProfileSettings();
}
return _settings;
}
finally
{
_semaphore.Release();
}
}
protected override void Initialize()
{
NvAPIWrapper.NVIDIA.Initialize();
_initialized = true;
_drs = DrsServiceLocator.SettingService;
_meta = DrsServiceLocator.MetaService;
_drs.GetProfileNames(ref _baseProfileName, false);
Task.Run(() =>
{
RefreshProfileSettings();
});
}
private void RefreshProfileSettings()
{
_semaphore.WaitOne();
try
{
var applications = new Dictionary<string, string>();
var normalSettings = _drs.GetSettingsForProfile(_baseProfileName, SettingViewMode.Normal, ref applications);
_settings = new List<SettingItem>();
_settings.AddRange(normalSettings);
}
finally
{
_semaphore.Release();
}
}
protected override void Uninitialize()
{
if (_initialized)
{
NvAPIWrapper.NVIDIA.Unload();
}
}
}
}
+189 -1
View File
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using ColorControl.Common;
using Microsoft.Win32.TaskScheduler;
using System.Collections.Generic;
namespace ColorControl
{
@@ -7,16 +9,119 @@ namespace ColorControl
public const string RunningFromScheduledTaskParam = "--scheduled";
public const string ActivateChromeFontFixParam = "--activate-chrome-font-fix";
public const string DeactivateChromeFontFixParam = "--deactivate-chrome-font-fix";
public const string ExecuteHelpParam = "--help";
public const string ExecuteNvidiaPresetParam = "--nvpreset";
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 const string SetProcessAffinityParam = "--set-process-affinity";
public const string SetProcessPriorityParam = "--set-process-priority";
public const string StartElevatedParam = "--elevated";
public const string SendWakeOnLanParam = "--send-wol";
public const string InstallServiceParam = "--install-service";
public const string UninstallServiceParam = "--uninstall-service";
public const string StartServiceParam = "--start-service";
public const string StopServiceParam = "--stop-service";
public bool RunningFromScheduledTask { get; private set; }
public bool ActivateChromeFontFix { get; private set; }
public bool DeactivateChromeFontFix { get; private set; }
public bool ExecuteHelp { get; private set; }
public bool ExecuteNvidiaPreset { get; private set; }
public string NvidiaPresetIdOrName { get; private set; }
public bool ExecuteAmdPreset { get; private set; }
public string AmdPresetIdOrName { get; private set; }
public bool ExecuteLgPreset { get; private set; }
public string LgPresetName { get; private set; }
public bool NoGui { get; set; }
public bool EnableAutoStart { get; private set; }
public TaskRunLevel AutoStartRunLevel { get; private set; } = TaskRunLevel.LUA;
public bool DisableAutoStart { get; private set; }
public bool SetProcessAffinity { get; private set; }
public int ProcessId { get; private set; }
public uint AffinityMask { get; private set; }
public bool SetProcessPriority { get; private set; }
public uint PriorityClass { get; private set; }
public bool StartElevated { get; private set; }
public bool SendWol { get; private set; }
public string WolMacAddress { get; private set; }
public string WolIpAddress { get; private set; }
public bool InstallService { get; private set; }
public bool UninstallService { get; private set; }
public bool StartService { get; private set; }
public bool StopService { get; private set; }
public static StartUpParams Parse(IEnumerable<string> args)
{
var settings = new StartUpParams();
var parseNameParam = string.Empty;
foreach (var arg in args)
{
if (!string.IsNullOrEmpty(parseNameParam))
{
switch (parseNameParam)
{
case ExecuteNvidiaPresetParam:
{
settings.NvidiaPresetIdOrName = arg;
break;
}
case ExecuteAmdPresetParam:
{
settings.AmdPresetIdOrName = arg;
break;
}
case ExecuteLgPresetParam:
{
settings.LgPresetName = arg;
break;
}
case SetProcessAffinityParam:
{
if (settings.ProcessId == 0)
{
settings.ProcessId = int.Parse(arg);
continue;
}
settings.AffinityMask = uint.Parse(arg);
break;
}
case SetProcessPriorityParam:
{
if (settings.ProcessId == 0)
{
settings.ProcessId = int.Parse(arg);
continue;
}
settings.PriorityClass = uint.Parse(arg);
break;
}
case SendWakeOnLanParam:
{
if (settings.WolMacAddress == null)
{
settings.WolMacAddress = arg;
continue;
}
settings.WolIpAddress = arg;
break;
}
case EnableAutoStartParam:
{
settings.AutoStartRunLevel = (TaskRunLevel)Utils.ParseInt(arg, 0);
break;
}
}
parseNameParam = string.Empty;
continue;
}
switch (arg.ToLowerInvariant())
{
case RunningFromScheduledTaskParam:
@@ -34,6 +139,89 @@ namespace ColorControl
settings.DeactivateChromeFontFix = true;
break;
}
case ExecuteHelpParam:
{
settings.ExecuteHelp = true;
settings.NoGui = true;
break;
}
case ExecuteNvidiaPresetParam:
{
settings.ExecuteNvidiaPreset = true;
parseNameParam = ExecuteNvidiaPresetParam;
break;
}
case ExecuteAmdPresetParam:
{
settings.ExecuteAmdPreset = true;
parseNameParam = ExecuteAmdPresetParam;
break;
}
case ExecuteLgPresetParam:
{
settings.ExecuteLgPreset = true;
parseNameParam = ExecuteLgPresetParam;
break;
}
case NoGuiParam:
{
settings.NoGui = true;
break;
}
case EnableAutoStartParam:
{
settings.EnableAutoStart = true;
parseNameParam = EnableAutoStartParam;
break;
}
case DisableAutoStartParam:
{
settings.DisableAutoStart = true;
break;
}
case SetProcessAffinityParam:
{
settings.SetProcessAffinity = true;
parseNameParam = SetProcessAffinityParam;
break;
}
case SetProcessPriorityParam:
{
settings.SetProcessPriority = true;
parseNameParam = SetProcessPriorityParam;
break;
}
case StartElevatedParam:
{
settings.StartElevated = true;
break;
}
case SendWakeOnLanParam:
{
settings.SendWol = true;
parseNameParam = SendWakeOnLanParam;
break;
}
case InstallServiceParam:
{
settings.InstallService = true;
break;
}
case UninstallServiceParam:
{
settings.UninstallService = true;
break;
}
case StartServiceParam:
{
settings.StartService = true;
break;
}
case StopServiceParam:
{
settings.StopService = true;
break;
}
}
}
return settings;
@@ -0,0 +1,224 @@
using ColorControl.Common;
using ColorControl.Services.LG;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using NLog;
using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
namespace ColorControl.Svc
{
public sealed class ColorControlBackgroundService : BackgroundService
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static string LgConfigFile = "LgConfig.json";
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
LgConfigFile = Path.Combine(Program.DataDir, LgConfigFile);
//var lgService = new LgService(Program.DataDir, true);
//lgService.InstallEventHandlers(true);
await WakeDevicesAsync();
var securityIdentifier = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
var ps = new PipeSecurity();
ps.AddAccessRule(new PipeAccessRule(securityIdentifier, PipeAccessRights.ReadWrite, AccessControlType.Allow));
Logger.Debug("EXECUTING BACKGROUND");
while (!stoppingToken.IsCancellationRequested)
{
Logger.Debug("WAITING FOR MESSAGE...");
var pipeServer = NamedPipeServerStreamAcl.Create("testpipe", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous, 1024, 1024, ps);
await pipeServer.WaitForConnectionAsync(stoppingToken);
var ss = new StreamString(pipeServer);
var message = ss.ReadString();
//Logger.Debug("MESSAGE RECEIVED: " + message);
try
{
var result = await HandleMessageAsync(message);
var resultJson = JsonConvert.SerializeObject(result);
//Logger.Debug("SENDING RESULT: " + resultJson);
ss.WriteString(resultJson);
}
catch (Exception ex)
{
Logger.Error(ex, $"Error while handling message: {message}");
var result = SvcResultMessage.FromResult(false, ex.Message);
var resultJson = JsonConvert.SerializeObject(result);
ss.WriteString(resultJson);
}
pipeServer.Close();
//pipeServer.Disconnect();
}
Logger.Debug("STOPPING...");
}
catch (OperationCanceledException)
{
Logger.Debug("Service stopping");
}
catch (Exception ex)
{
Logger.Error(ex, "{Message}", ex.Message);
// Terminates this process and returns an exit code to the operating system.
// This is required to avoid the 'BackgroundServiceExceptionBehavior', which
// performs one of two scenarios:
// 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
// 2. When set to "StopHost": will cleanly stop the host, and log errors.
//
// In order for the Windows Service Management system to leverage configured
// recovery options, we need to terminate the process with a non-zero exit code.
//Environment.Exit(1);
}
}
private async Task<SvcResultMessage> HandleMessageAsync(string json)
{
var message = JsonConvert.DeserializeObject<SvcMessage>(json);
var result = message.MessageType switch
{
SvcMessageType.SetLgConfig => HandleSetLgConfigMessage(message),
SvcMessageType.GetLog => HandleGetLogMessage(message),
SvcMessageType.ClearLog => HandleClearLogMessage(message),
SvcMessageType.ExecuteRpc => HandleExecuteRpcCommand(message),
SvcMessageType.ExecuteUpdate => await HandleExecuteUpdateCommandAsync(JsonConvert.DeserializeObject<SvcInstallUpdateMessage>(json)),
SvcMessageType.RestartAfterUpdate => HandleRestartAfterUpdateMessage(),
_ => SvcResultMessage.FromResult(false, "Unexpected message type")
};
return result;
}
private SvcResultMessage HandleExecuteRpcCommand(SvcMessage message)
{
var args = message.Data.Split(' ');
var startUpParams = StartUpParams.Parse(args);
return SvcResultMessage.FromResult(Program.HandleStartupParams(startUpParams, null));
}
private SvcResultMessage HandleSetLgConfigMessage(SvcMessage message)
{
var data = message.Data;
Utils.WriteText(LgConfigFile, data);
return SvcResultMessage.FromResult(true);
}
private SvcResultMessage HandleGetLogMessage(SvcMessage _)
{
var logFile = Program.LogFilename;
var data = Utils.ReadText(logFile);
return SvcResultMessage.FromResult(data);
}
private SvcResultMessage HandleClearLogMessage(SvcMessage _)
{
var logFile = Program.LogFilename;
if (File.Exists(logFile))
{
File.Delete(logFile);
}
return SvcResultMessage.FromResult(true);
}
private async Task<SvcResultMessage> HandleExecuteUpdateCommandAsync(SvcInstallUpdateMessage message)
{
var localFile = Path.Combine(Program.DataDir, "update.zip");
await Utils.DownloadFileAsync(message.DownloadUrl, localFile);
var updatePath = Path.Combine(Program.DataDir, "ColorControl");
var directory = new DirectoryInfo(updatePath);
if (directory.Exists)
{
directory.Delete(true);
}
Utils.UnZipFile(localFile, Program.DataDir);
Utils.UpdateFiles(message.ClientPath, updatePath);
return SvcResultMessage.FromResult(true);
}
private SvcResultMessage HandleRestartAfterUpdateMessage()
{
Utils.StartProcess("cmd.exe", $@"/C net stop ""{Utils.SERVICE_NAME}"" && net start ""{Utils.SERVICE_NAME}""", true);
return SvcResultMessage.FromResult(true);
}
private async Task WakeDevicesAsync()
{
if (!UserSessionInfo.UserLocalSession)
{
return;
}
if (!File.Exists(LgConfigFile))
{
return;
}
var config = Utils.DeserializeJson<LgServiceConfig>(LgConfigFile);
if (config == null)
{
return;
}
var wakeDevices = config.Devices.Where(d => d.PowerOnAfterStartup).ToList();
foreach (var wakeDevice in wakeDevices)
{
for (var i = 0; i < config.PowerOnRetries; i++)
{
WOL.WakeFunction(wakeDevice.MacAddress, wakeDevice.IpAddress);
await Task.Delay(1000);
var result = Utils.PingHost(wakeDevice.IpAddress);
if (result)
{
Logger.Debug($"Device {wakeDevice.Name} is pingable, stopping WOL");
break;
}
}
}
}
}
}
+31
View File
@@ -0,0 +1,31 @@
namespace ColorControl.Svc
{
public enum SvcMessageType
{
SetLgConfig = 1,
GetLog = 2,
ClearLog = 3,
ExecuteRpc = 10,
ExecuteUpdate = 20,
RestartAfterUpdate = 21,
}
public class SvcMessage
{
public SvcMessageType MessageType { get; set; }
public string Data { get; set; }
}
public class SvcInstallUpdateMessage : SvcMessage
{
public SvcInstallUpdateMessage()
{
MessageType = SvcMessageType.ExecuteUpdate;
}
public string DownloadUrl { get; set; }
public string ClientPath { get; set; }
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace ColorControl.Svc
{
public class SvcResultMessage
{
public bool Result { get; set; }
public string Data { get; set; }
public string ErrorMessage { get; set; }
public static SvcResultMessage FromResult(bool result, string errorMessage = null) => new SvcResultMessage { Result = result, ErrorMessage = errorMessage };
public static SvcResultMessage FromResult(string data) => new SvcResultMessage { Result = true, Data = data };
}
}
-655
View File
@@ -1,655 +0,0 @@
using Microsoft.Win32;
using Microsoft.Win32.TaskScheduler;
using NStandard;
using NWin32;
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.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Windows.Devices.Enumeration;
using Windows.Devices.Enumeration.Pnp;
namespace ColorControl
{
static class Utils
{
[Flags]
public enum ModKeys : int
{
Alt = 1,
Control = 2,
Shift = 4,
Win = 8
}
public const int WM_BRINGTOFRONT = NativeConstants.WM_USER + 1;
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 delegate void PCREATE_PROCESS_NOTIFY_ROUTINE(IntPtr ParentId, IntPtr ProcessId, bool Create);
[DllImport("user32.dll")]
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);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);
//[DllImport("Wtsapi32.dll")]
//public extern static bool WTSRegisterSessionNotification(IntPtr hWnd, uint dwFlags);
//[DllImport("Wtsapi32.dll")]
//public extern static bool WTSUnRegisterSessionNotification(IntPtr hWnd);
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
private static bool WinKeyDown = false;
public static Bitmap SubPixelShift(Bitmap bitmap)
{
Bitmap bitmap2 = (Bitmap)bitmap.Clone();
int shift = -1;
bool shiftRed = true;
bool shiftGreen = true;
bool shiftBlue = false;
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
Color pixel = bitmap.GetPixel(i, j);
if (shift > 0 && i < bitmap.Width - shift)
{
Color pixel2 = bitmap2.GetPixel(i + shift, j);
Color pixel3 = Color.FromArgb(shiftRed ? pixel.R : pixel2.R, shiftGreen ? pixel.G : pixel2.G, shiftBlue ? pixel.B : pixel2.B);
bitmap2.SetPixel(i + shift, j, pixel3);
}
else if (i + shift >= 0)
{
Color pixel2 = bitmap2.GetPixel(i + shift, j);
Color pixel3 = Color.FromArgb(shiftRed ? pixel.R : pixel2.R, shiftGreen ? pixel.G : pixel2.G, shiftBlue ? pixel.B : pixel2.B);
bitmap2.SetPixel(i + shift, j, pixel3);
}
}
}
return bitmap2;
//bitmap2.Save("d:\\ss_shifted.bmp");
}
public static string GetDataPath(string subDir = "")
{
var path = Directory.GetParent(Application.UserAppDataPath).FullName;
if (!string.IsNullOrEmpty(subDir))
{
path = Path.Combine(path, subDir);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
return path;
}
public static (int mods, int key) ParseShortcut(string shortcut)
{
int mods = 0;
var key = 0;
shortcut = shortcut.Replace(",", "");
shortcut = shortcut.Replace("+", "");
shortcut = shortcut.Replace(" ", " ");
var parts = shortcut.Split(' ');
if (parts.Length > 0)
{
for (int i = 0; i + 1 < parts.Length; i++)
{
switch (parts[i])
{
case "Alt":
mods += (int)ModKeys.Alt;
break;
case "Control":
mods += (int)ModKeys.Control;
break;
case "Shift":
mods += (int)ModKeys.Shift;
break;
case "Win":
mods += (int)ModKeys.Win;
break;
}
}
var keyName = parts[parts.Length - 1];
key = (int)Enum.Parse(typeof(Keys), keyName);
}
return (mods, key);
}
internal static bool ExecuteElevated(string args)
{
var info = new ProcessStartInfo(Assembly.GetEntryAssembly().Location, args)
{
Verb = "runas", // indicates to elevate privileges
};
var process = new Process
{
EnableRaisingEvents = true, // enable WaitForExit()
StartInfo = info
};
try
{
process.Start();
process.WaitForExit(); // sleep calling process thread until evoked process exit
return true;
}
catch (Exception e)
{
Logger.Error("ExecuteElevated: " + e.Message);
}
return false;
}
internal static bool IsChromeFixInstalled()
{
var key = Registry.ClassesRoot.OpenSubKey(@"ChromeHTML\shell\open\command");
return key != null && key.GetValue(null).ToString().Contains("--disable-lcd-text");
}
internal static bool InstallChromeFix(bool install)
{
var argument = "--disable-lcd-text";
var key = Registry.ClassesRoot.OpenSubKey(@"ChromeHTML\shell\open\command", true);
if (key != null)
{
var value = key.GetValue(null).ToString();
value = value.Replace("chrome.exe\" " + argument + " --", "chrome.exe\" --");
if (install)
{
value = value.Replace("chrome.exe\" --", "chrome.exe\" " + argument + " --");
}
key.SetValue(null, value);
}
var roamingFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
UpdateShortcut(Path.Combine(roamingFolder, @"Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Google Chrome.lnk"), argument, !install);
UpdateShortcut(Path.Combine(roamingFolder, @"Microsoft\Internet Explorer\Quick Launch\Google Chrome.lnk"), argument, !install);
var allUsersStartMenuFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
UpdateShortcut(Path.Combine(allUsersStartMenuFolder, @"Programs\Google Chrome.lnk"), argument, !install);
return true;
}
internal static bool IsChromeInstalled()
{
var key = Registry.ClassesRoot.OpenSubKey(@"ChromeHTML\shell\open\command");
return key != null;
}
internal static void GetRegistryKeyValue(string keyname, string valueName, bool deepSearch = false)
{
var key = Registry.LocalMachine.OpenSubKey(keyname);
foreach (var subKeyName in key.GetSubKeyNames())
{
var subKey = key.OpenSubKey(subKeyName);
if (subKey.GetValueNames().Contains(valueName))
{
Thread.Sleep(0);
}
}
}
internal static bool UpdateShortcut(string path, string arguments, bool removeArguments = false)
{
if (File.Exists(path))
{
IWshRuntimeLibrary.WshShell shell = new IWshRuntimeLibrary.WshShell();
IWshRuntimeLibrary.IWshShortcut shortcut = (IWshRuntimeLibrary.IWshShortcut)shell.CreateShortcut(path);
shortcut.Arguments = shortcut.Arguments.Replace(arguments, "");
if (!removeArguments)
{
if (!string.IsNullOrEmpty(shortcut.Arguments))
{
shortcut.Arguments = shortcut.Arguments + " ";
}
shortcut.Arguments = shortcut.Arguments + arguments;
}
// save it / create
shortcut.Save();
return true;
}
return false;
}
public static string GetDeviceProperty(PnpObject device, string propertyName)
{
object objValue;
string value = null;
//foreach (var key in device.Properties.Keys)
//{
// Debug.WriteLine(key);
//}
if (device.Properties.TryGetValue(propertyName, out objValue))
{
if (objValue is string[])
{
var arr = objValue as string[];
if (arr.Length > 0)
{
value = arr[0];
}
}
else if (objValue is byte[])
{
value = BitConverter.ToString((byte[])objValue);
}
else
{
value = objValue?.ToString();
}
}
return value;
}
public static async Task<List<PnpDev>> GetPnpDevices(string deviceName)
{
var devices = new List<PnpDev>();
var list = await DeviceInformation.FindAllAsync("", new List<string>());
var reqs = new List<string>
{
PKEY_PNPX_IpAddress,
PKEY_PNPX_PhysicalAddress
};
foreach (var dev in list)
{
string name = dev.Name;
if (/*dev.IsEnabled &&*/ dev.Name.Contains(deviceName))
{
var guid = dev.Properties["System.Devices.DeviceInstanceId"];
var aqs = "System.Devices.DeviceInstanceId:=\"" + guid + "\"";
var list2 = await PnpObject.FindAllAsync(PnpObjectType.Device, reqs, aqs);
foreach (var dev2 in list2)
{
var ipAddress = GetDeviceProperty(dev2, "System.Devices.IpAddress");
var macAddress = GetDeviceProperty(dev2, PKEY_PNPX_PhysicalAddress);
if (!devices.Any(x => x.Name.Equals(name) && x.IpAddress != null && x.IpAddress.Equals(ipAddress)))
{
var device = new PnpDev(dev, dev2, name, ipAddress, macAddress);
devices.Add(device);
}
}
}
}
return devices;
}
public static void WaitForTask(System.Threading.Tasks.Task task)
{
while (task != null && (task.Status < TaskStatus.WaitingForChildrenToComplete))
{
Thread.Sleep(100);
Application.DoEvents();
}
}
public static void SetNotifyIconText(NotifyIcon ni, string text)
{
text = text.Substring(0, Math.Min(text.Length, 127));
Type t = typeof(NotifyIcon);
BindingFlags hidden = BindingFlags.NonPublic | BindingFlags.Instance;
t.GetField("text", hidden).SetValue(ni, text);
if ((bool)t.GetField("added", hidden).GetValue(ni))
t.GetMethod("UpdateIcon", hidden).Invoke(ni, new object[] { true });
}
internal static Image GenerateGradientBitmap(int width, int height)
{
var bitmap = new Bitmap(512, height);
for (var i = 0; i < 256; i++)
{
var pixel = Color.FromArgb(i, i, i);
for (var h = 0; h < height; h++)
{
bitmap.SetPixel(i * 2, h, pixel);
bitmap.SetPixel(i * 2 + 1, h, pixel);
}
}
return bitmap;
}
public static string GetDescription<T>(this T e) where T : IConvertible
{
if (e is Enum)
{
var type = e.GetType();
var values = Enum.GetValues(type);
foreach (int val in values)
{
if (val == e.ToInt32(CultureInfo.InvariantCulture))
{
var memInfo = type.GetMember(type.GetEnumName(val));
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 List<string> GetDescriptions<T>() where T : IConvertible
{
var list = new List<string>();
foreach (var enumValue in Enum.GetValues(typeof(T)))
{
list.Add(((T)enumValue).GetDescription());
}
return list;
}
public static void RegisterTask(string taskName, bool enabled)
{
var file = Assembly.GetExecutingAssembly().Location;
var directory = Path.GetDirectoryName(file);
try
{
using (TaskService ts = new TaskService())
{
if (enabled)
{
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = "Start ColorControl";
td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name });
td.Actions.Add(new ExecAction(file, StartUpParams.RunningFromScheduledTaskParam, directory));
ts.RootFolder.RegisterTaskDefinition(taskName, td);
}
else
{
ts.RootFolder.DeleteTask(taskName, false);
}
}
}
catch (Exception e)
{
MessageForms.ErrorOk("Could not create/delete task: " + e.Message);
}
}
public static bool TaskExists(string taskName, bool update)
{
var file = Assembly.GetExecutingAssembly().Location;
using (TaskService ts = new TaskService())
{
var task = ts.RootFolder.Tasks.FirstOrDefault(x => x.Name.Equals(taskName));
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);
}
}
}
return true;
}
return false;
}
}
public static void RegisterShortcut(IntPtr handle, int id, string shortcut, bool clear = false)
{
if (clear)
{
UnregisterHotKey(handle, id);
}
if (!string.IsNullOrEmpty(shortcut))
{
var (mods, key) = ParseShortcut(shortcut);
RegisterHotKey(handle, id, mods, key);
}
}
public static void BuildDropDownMenu(ToolStripMenuItem mnuParent, string name, Type enumType, object colorData, string propertyName, EventHandler clickEvent)
{
PropertyInfo property;
var subMenuItems = mnuParent.DropDownItems.Find("miColorSettings_" + name, false);
ToolStripMenuItem subMenuItem;
if (subMenuItems.Length == 0)
{
subMenuItem = (ToolStripMenuItem)mnuParent.DropDownItems.Add(name);
subMenuItem.Name = "miColorSettings_" + name;
property = colorData.GetType().GetDeclaredProperty(propertyName);
subMenuItem.Tag = property;
foreach (var enumValue in Enum.GetValues(enumType))
{
var item = subMenuItem.DropDownItems.Add(enumValue.ToString());
item.Tag = enumValue;
item.Click += clickEvent;
}
}
else
{
subMenuItem = (ToolStripMenuItem)subMenuItems[0];
property = (PropertyInfo)subMenuItem.Tag;
}
var value = property.GetValue(colorData);
foreach (var item in subMenuItem.DropDownItems)
{
if (item is ToolStripMenuItem)
{
var menuItem = (ToolStripMenuItem)item;
if (menuItem.Tag != null)
{
menuItem.Checked = menuItem.Tag.Equals(value);
}
}
}
}
public static string FormatKeyboardShortcut(KeyEventArgs keyEvent)
{
var pressedModifiers = keyEvent.Modifiers;
//Debug.WriteLine("KD: " + e.Modifiers + ", " + e.KeyCode);
var shortcutString = (pressedModifiers > 0 ? pressedModifiers.ToString() : "");
if (keyEvent.KeyCode == Keys.LWin || WinKeyDown)
{
WinKeyDown = true;
if (!string.IsNullOrEmpty(shortcutString))
{
shortcutString += ", ";
}
shortcutString += "Win";
}
if (!string.IsNullOrEmpty(shortcutString) && keyEvent.KeyCode != Keys.ControlKey && keyEvent.KeyCode != Keys.ShiftKey && keyEvent.KeyCode != Keys.Menu && keyEvent.KeyCode != Keys.LWin)
{
shortcutString += " + " + keyEvent.KeyCode.ToString();
}
if (pressedModifiers == 0 && !WinKeyDown)
{
keyEvent.SuppressKeyPress = true;
}
return shortcutString;
}
public static void HandleKeyboardShortcutUp(KeyEventArgs keyEvent)
{
if (keyEvent.KeyCode == Keys.LWin)
{
WinKeyDown = false;
}
}
public static void InitListView(ListView listView, IEnumerable<string> columns)
{
foreach (var name in columns)
{
var columnName = name;
var parts = name.Split('|');
var width = 120;
if (parts.Length > 1)
{
width = int.Parse(parts[1]);
columnName = parts[0];
}
var header = listView.Columns.Add(columnName);
header.Width = width == 120 ? -2 : width;
}
}
public static void AddOrUpdateListItem<T>(ListView listView, List<T> presets, Config config, T preset = null, ListViewItem specItem = null) where T : PresetBase
{
ListViewItem item = null;
if (preset == null)
{
item = listView.SelectedItems[0];
preset = (T)item.Tag;
}
else
{
item = specItem;
}
if (preset.id == 0)
{
preset.id = preset.GetHashCode();
}
var values = preset.GetDisplayValues(config);
if (item == null)
{
item = listView.Items.Add(values[0]);
item.Tag = preset;
for (var i = 1; i < values.Count; i++)
{
item.SubItems.Add(values[i]);
}
if (!presets.Any(x => x.id == preset.id))
{
presets.Add(preset);
}
}
else
{
item.Text = values[0];
for (var i = 1; i < values.Count; i++)
{
item.SubItems[i].Text = values[i];
}
}
}
}
public static class ProcessExtensions
{
private static string FindIndexedProcessName(int pid)
{
var processName = Process.GetProcessById(pid).ProcessName;
var processesByName = Process.GetProcessesByName(processName);
string processIndexdName = null;
for (var index = 0; index < processesByName.Length; index++)
{
processIndexdName = index == 0 ? processName : processName + "#" + index;
var processId = new PerformanceCounter("Process", "ID Process", processIndexdName);
if ((int)processId.NextValue() == pid)
{
return processIndexdName;
}
}
return processIndexdName;
}
private static Process FindPidFromIndexedProcessName(string indexedProcessName)
{
var parentId = new PerformanceCounter("Process", "Creating Process ID", indexedProcessName);
return Process.GetProcessById((int)parentId.NextValue());
}
public static Process Parent(this Process process)
{
return FindPidFromIndexedProcessName(FindIndexedProcessName(process.Id));
}
}
}
-186
View File
@@ -1,186 +0,0 @@
using NLog;
using System;
using PacketDotNet;
using System.Net.NetworkInformation;
using SharpPcap;
using SharpPcap.Npcap;
using System.Net;
using System.Net.Sockets;
namespace ColorControl
{
class WOL
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private const string ArgumentExceptionInvalidMacAddressLength = "Invalid MAC address length.";
private const string ArgumentExceptionInvalidPasswordLength = "Invalid password length.";
private const int DefaultWolPort = 9;
public static bool WakeFunction(string macAddress, bool usePcap = false)
{
if (usePcap)
{
return WakeFunctionPcap(macAddress);
}
return WakeFunctionToAllNics(macAddress);
}
public static bool WakeFunctionToAllNics(string macAddress)
{
var result = false;
try
{
var address = PhysicalAddress.Parse(macAddress);
var addressBytes = address.GetAddressBytes();
var data = GetWolPacket(addressBytes);
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var ni in interfaces)
{
if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || ni.Name.Contains("VirtualBox"))
{
continue;
}
Logger.Debug($"Found network: {ni.Name} ({ni.Description}), type: {ni.NetworkInterfaceType}");
try
{
if (/*ni.OperationalStatus == OperationalStatus.Up &&*/ ni.SupportsMulticast && ni.GetIPProperties().GetIPv4Properties() != null)
{
foreach (var uip in ni.GetIPProperties().UnicastAddresses)
{
if (uip.Address.ToString().StartsWith("169.254"))
{
continue;
}
if (uip.Address.AddressFamily == AddressFamily.InterNetwork)
{
Logger.Debug($"Broadcast WOL in network: {ni.Name} ({ni.Description}), local address: {uip.Address}");
var local = new IPEndPoint(uip.Address, 0);
var udpc = new UdpClient(local);
udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
var target = new IPEndPoint(IPAddress.Broadcast, DefaultWolPort);
udpc.Send(data, data.Length, target);
result = true;
}
}
}
}
catch (Exception ex)
{
Logger.Error($"WakeFunctionToAllNics: while sending to specific network: {ni.Name} ({ni.Description}): {ex.ToLogString()}");
}
}
}
catch (Exception ex)
{
Logger.Error($"WakeFunctionToAllNics: {ex.ToLogString()}");
}
return result;
}
/// <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 = null)
{
if (macAddress == null)
throw new ArgumentNullException(nameof(macAddress));
if (macAddress.Length != 6)
throw new ArgumentException(ArgumentExceptionInvalidMacAddressLength);
password = password ?? new byte[0];
if (password.Length != 0 && password.Length != 6)
throw new ArgumentException(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;
}
//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;
}
}
}
+27 -1
View File
@@ -1,4 +1,4 @@
using ColorControl;
using ColorControl.Common;
using System.IO;
namespace LgTv
@@ -6,10 +6,12 @@ namespace LgTv
public class ClientKeyStore
{
private string ip;
public ClientKeyStore(string ip)
{
this.ip = ip;
}
public string GetClientKey()
{
var filename = Path.Combine(Utils.GetDataPath(), ip + "_ClientKey.txt");
@@ -19,10 +21,34 @@ namespace LgTv
}
return null;
}
public void SaveClientKey(string key)
{
var filename = Path.Combine(Utils.GetDataPath(), ip + "_ClientKey.txt");
File.WriteAllText(filename, key);
}
public bool HasValidHandShake(string expectedHandShake)
{
var handShake = GetHandShake();
return handShake?.Equals(expectedHandShake) ?? false;
}
public string GetHandShake()
{
var filename = Path.Combine(Utils.GetDataPath(), ip + "_HandShake.txt");
if (File.Exists(filename))
{
return File.ReadAllText(filename);
}
return null;
}
public void SaveHandShake(string data)
{
var filename = Path.Combine(Utils.GetDataPath(), ip + "_HandShake.txt");
File.WriteAllText(filename, data);
}
}
}
+537 -41
View File
@@ -1,7 +1,8 @@
using ColorControl;
using ColorControl.Common;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -12,13 +13,263 @@ namespace LgTv
public enum ControlButtons
{
Back,Down,Left,Right,
Back, Down, Left, Right,
OK,
Exit
}
public class LgTvApi:IDisposable
public enum PictureMode
{
public bool ConnectionClosed { get => _connection == null || _connection.ConnectionClosed; }
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
{
off,
low,
medium,
high
}
public enum LowToAuto
{
low,
medium,
high,
auto
}
public enum OffToOn
{
off,
on
}
public enum OffToAuto2
{
off,
on,
auto
}
public enum OffToAuto
{
off,
on,
low,
medium,
high,
auto
}
public enum ColorGamut
{
auto,
extended,
wide,
srgb,
native
}
public enum EnergySaving
{
auto,
off,
min,
med,
max,
screen_off
}
public enum DynamicTonemapping
{
on,
off,
HGIG,
}
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 BoolFalseToTrue
{
[Description("Disabled")]
bool_false,
[Description("Enabled")]
bool_true
}
public enum GammaExp
{
low,
medium,
high1,
high2
}
public enum BlackLevel
{
low,
medium,
high,
limited,
full,
auto
}
public enum AspectRatio
{
_21x9,
_16x9,
_4x3,
_14x9,
_32x9,
_32x12,
just_scan,
original,
full_wide,
limited,
zoom,
zoom2,
cinema_zoom,
vertZoom,
allDirZoom,
twinZoom
}
public enum InputOptimization
{
auto,
on,
standard,
boost
}
public enum BlueLight
{
off,
level1,
level2
}
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; }
private readonly LgTvApiCore _connection;
private LgWebOsMouseService _mouseConnection;
@@ -28,18 +279,7 @@ 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\"],\"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\"],\"signatures\":[{\"signatureVersion\":1,\"signature\":\"eyJhbGdvcml0aG0iOiJSU0EtU0hBMjU2Iiwia2V5SWQiOiJ0ZXN0LXNpZ25pbmctY2VydCIsInNpZ25hdHVyZVZlcnNpb24iOjF9.hrVRgjCwXVvE2OOSpDZ58hR+59aFNwYDyjQgKk3auukd7pcegmE2CzPCa0bJ0ZsRAcKkCTJrWo5iDzNhMBWRyaMOv5zWSrthlf7G128qvIlpMT0YNY+n/FaOHE73uLrS/g7swl3/qH/BGFG2Hu4RlL48eb3lLKqTt2xKHdCs6Cd4RMfJPYnzgvI4BNrFUKsjkcu+WD4OO2A27Pq1n50cMchmcaXadJhGrOqH5YmHdOCj5NSHzJYrsW0HPlpuAx/ECMeIZYDh6RMqaFM2DXzdKX9NmmyqzJ3o/0lkk/N97gfVRLW5hA29yeAwaCViZNCP8iC9aO0q9fQojoa7NQnAtw==\"}]}}}";
public Task ControlButton(object oK)
{
// throw new NotImplementedException();
return null;
}
private string _currentPariKey;
private string _currentPairKey;
private string webSocketUri;
public static async Task<LgTvApi> CreateLgTvApi(string ip, int retries = 1)
@@ -75,23 +315,27 @@ namespace LgTv
public async Task MakeHandShake()
{
_currentPariKey = _keyStore.GetClientKey();
if (_currentPariKey != null)
var registerJson = Utils.GetResourceFile("LG_register.json");
_currentPairKey = _keyStore.GetClientKey();
if (_currentPairKey != null && !_currentPairKey.All(k => k == '\0') && _keyStore.HasValidHandShake(registerJson))
{
var key = AFTER_PAIR_HAND_SHAKE.Replace("CLIENTKEYGOESHERE", _currentPariKey);
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(registerJson);
}
public async Task<bool> Connect()
{
var ctx = _connection.Connect(new Uri(webSocketUri));
var ctx = _connection.Connect(new Uri(webSocketUri));
return await ctx;
}
@@ -101,7 +345,7 @@ namespace LgTv
var requestMessage = new RequestMessage("ssap://com.webos.service.networkinput/getPointerInputSocket", new { });
var s = await _connection.SendCommandAsync(requestMessage);
var socketPath = (string)s.socketPath;
_mouseConnection = new LgWebOsMouseService(new LgTvApiCore());
await _mouseConnection.Connect(socketPath);
return _mouseConnection;
@@ -135,10 +379,10 @@ namespace LgTv
}
public async Task ShowToast()
{
var requestMessage = new RequestMessage("ssap://system.notifications/createToast",new {message = "Co tam u Ciebie?"});
var requestMessage = new RequestMessage("ssap://system.notifications/createToast", new { message = "Co tam u Ciebie?" });
await _connection.SendCommandAsync(requestMessage);
}
//public async Task<string> LaunchApp(string appId,Uri uri)
//{
// var url = uri.ToString();
@@ -152,7 +396,7 @@ namespace LgTv
var requestMessage = new RequestMessage("volumedown", "ssap://audio/volumeDown");
await _connection.SendCommandAsync(requestMessage);
}
public async Task VolumeUp()
public async Task VolumeUp()
{
var requestMessage = new RequestMessage("volumeup", "ssap://audio/volumeUp");
await _connection.SendCommandAsync(requestMessage);
@@ -163,12 +407,12 @@ namespace LgTv
await _connection.SendCommandAsync(requestMessage);
}
public async Task SetChannel(string channelId)
public async Task SetChannel(string channelId)
{
var requestMessage = new RequestMessage("ssap://tv/openChannel", new { channelId });
await _connection.SendCommandAsync(requestMessage);
}
public async Task SetMute(bool value)
public async Task SetMute(bool value)
{
var requestMessage = new RequestMessage("ssap://audio/setMute", new { mute = value });
await _connection.SendCommandAsync(requestMessage);
@@ -184,7 +428,7 @@ namespace LgTv
return (bool)(await command).mute;
}
public async Task SetVolume(int value)
public async Task SetVolume(int value)
{
if (value >= 0 && value <= 100)
{
@@ -192,11 +436,16 @@ namespace LgTv
await _connection.SendCommandAsync(requestMessage);
}
}
public async Task TurnOff()
public async Task TurnOff()
{
await _connection.SendCommandAsync(new RequestMessage("","ssap://system/turnOff"));
await _connection.SendCommandAsync(new RequestMessage("", "ssap://system/turnOff"));
}
public async Task Play()
public async Task Reboot()
{
await ExecuteRequest("luna://com.webos.service.tv.power/reboot", new { reason = "" });
}
public async Task Play()
{
await _connection.SendCommandAsync(new RequestMessage("play", "ssap://media.controls/play"));
}
@@ -214,7 +463,7 @@ namespace LgTv
await _connection.SendCommandAsync(new RequestMessage("channelDown", "ssap://tv/channelDown"));
}
public async Task Stop()
public async Task Stop()
{
await _connection.SendCommandAsync(new RequestMessage("stop", "ssap://media.controls/stop"));
}
@@ -227,12 +476,23 @@ 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’ } }
var requestMessage = new RequestMessage("status3D", "ssap://com.webos.service.tv.display/get3DStatus");
var o = await _connection.SendCommandAsync(requestMessage);
return (bool)o.status3D.status;
return (bool)o.status3D.status;
}
public async Task<int> GetVolume()
@@ -252,12 +512,12 @@ namespace LgTv
// }
var requestMessage = new RequestMessage("status", "ssap://audio/getVolume");
var o = await _connection.SendCommandAsync(requestMessage);
return( (bool)o.muted) ? -1 :(int) o.volume;
return ((bool)o.muted) ? -1 : (int)o.volume;
}
public async Task<IEnumerable<ExternalInput>> GetInputList()
{
var requestMessage = new RequestMessage("input","ssap://tv/getExternalInputList");
var results = await _connection.SendCommandAsync(requestMessage);
var requestMessage = new RequestMessage("input", "ssap://tv/getExternalInputList");
var results = await _connection.SendCommandAsync(requestMessage);
var l = new List<ExternalInput>();
foreach (var result in results)
{
@@ -269,12 +529,49 @@ namespace LgTv
return l;
}
public async Task SetInput(string id)
public async Task SetInput(string id)
{
var requestMessage = new RequestMessage("ssap://tv/switchInput", new { inputId = id });
await _connection.SendCommandAsync(requestMessage);
}
public async Task SubscribeVolume(Func<dynamic, bool> callback, dynamic payload = null)
{
var requestMessage = new RequestMessage("ssap://audio/getVolume", null, "subscribe");
await _connection.SubscribeAsync(requestMessage, callback);
}
public async Task SubscribePowerState(Func<dynamic, bool> callback, dynamic payload = null)
{
var requestMessage = new RequestMessage("ssap://com.webos.service.tvpower/power/getPowerState", null, "subscribe");
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"", ""brightness"", ""color"" ]";
var json = @"{ ""category"": ""picture"", ""keys"": " + keys + @", ""subscribe"": true }";
payload = JObject.Parse(json);
}
var requestMessage = new RequestMessage("ssap://settings/getSystemSettings", payload, "subscribe");
await _connection.SubscribeAsync(requestMessage, callback);
}
public async Task<IEnumerable<App>> GetLaunchPoints()
{
_appList = new List<App>();
@@ -333,9 +630,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;
}
@@ -365,6 +672,195 @@ namespace LgTv
return (string)response.sessionId;
}
public async Task SetSystemSettings(string key, object value, string category = "picture")
{
var jsonValue = ParamToJson(value, ref key);
var lunauri = "luna://com.webos.settingsservice/setSystemSettings";
var json = @"{ ""category"": """ + category + @""", ""settings"": { """ + key + @""": " + jsonValue + @" } }";
var @params = JObject.Parse(json);
await ExecuteRequest(lunauri, @params);
}
private static string ParamToJson(object value, ref string key, bool skipSeparator = false)
{
string jsonValue;
var valueType = value.GetType();
var intType = typeof(int);
if (value.ToString().StartsWith("bool_"))
{
value = bool.Parse(value.ToString().Substring(5));
}
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 && !skipSeparator)
{
var keys = key.Split('_');
key = keys[0];
var childKey = keys[1];
if (value is string strValue && strValue != "false" && strValue != "true" && Utils.ParseInt(strValue, int.MinValue) == int.MinValue)
{
value = $"\"{value}\"";
}
jsonValue = @"{ """ + childKey + @""": " + value + " }";
}
else if (value is bool)
{
jsonValue = value.ToString().ToLowerInvariant();
}
else if (key.Equals("blackLevel", StringComparison.Ordinal))
{
jsonValue = @"{
""ntsc"": ""auto"",
""ntsc443"": ""auto"",
""pal"": ""auto"",
""pal60"": ""auto"",
""palm"": ""auto"",
""paln"": ""auto"",
""secam"": ""auto"",
""unknown"": ""auto""
}";
jsonValue = jsonValue.Replace("auto", value.ToString());
}
else
{
if (key.Equals("arcPerApp") && value.ToString().StartsWith("_"))
{
value = value.ToString().Substring(1);
}
jsonValue = $"\"{value}\"";
}
return jsonValue;
}
public async Task SetConfig(string key, object value)
{
var lunauri = "luna://com.webos.service.config/setConfigs";
var jsonValue = ParamToJson(value, ref key);
var @params = JObject.Parse(@"{ ""configs"": { """ + key + @""": " + jsonValue + @" } }");
await ExecuteRequest(lunauri, @params);
}
public async Task SetSystemProperty(string key, object value)
{
var lunauri = "luna://com.webos.service.tv.systemproperty/setProperties";
var jsonValue = ParamToJson(value, ref key, true);
//var @params = JObject.Parse(@"{ ""keys"": { """ + key + @""": " + jsonValue + @" } }");
var @params = JObject.Parse(@"{ """ + key + @""": " + jsonValue + @" }");
await ExecuteRequest(lunauri, @params);
}
public async Task SetDeviceConfig(string id, string icon, string label)
{
var lunauri = "luna://com.webos.service.eim/setDeviceInfo";
var iconPng = icon + ".png";
var @params = new
{
id = id,
label = label,
icon = iconPng
};
await ExecuteRequest(lunauri, @params);
}
private async Task ExecuteRequest(string lunauri, object @params)
{
var buttons = new[]
{
new {
label = "",
onClick = lunauri,
@params = @params
}
};
var payload = new
{
message = "Applying...",
buttons = buttons,
onclose = new { uri = lunauri, @params = @params },
onfail = new { uri = lunauri, @params = @params }
};
var requestMessage = new RequestMessage("ssap://system.notifications/createAlert", payload);
var response = await _connection.SendCommandAsync(requestMessage);
var alertId = (string)response.alertId;
if (alertId != null)
{
var closeAlert = new
{
alertId = alertId
};
requestMessage = new RequestMessage("ssap://system.notifications/closeAlert", closeAlert);
await _connection.SendCommandAsync(requestMessage);
}
}
public async Task<dynamic> GetSystemInfo(params string[] keys)
{
var payload = new
{
keys = keys
};
var requestMessage = new RequestMessage("ssap://system/getSystemInfo", payload);
var command = _connection.SendCommandAsync(requestMessage);
var res = await command;
return res;
}
public async Task<dynamic> GetSystemSettings(string category, params string[] keys)
{
var payload = new
{
category = category,
keys = keys
};
var requestMessage = new RequestMessage("ssap://settings/getSystemSettings", payload);
var command = _connection.SendCommandAsync(requestMessage);
var res = await command;
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()
{
@@ -372,7 +868,7 @@ namespace LgTv
}
public void Dispose()
{
_connection?.Dispose();
_connection?.Dispose();
}
}
}
+112 -27
View File
@@ -1,13 +1,13 @@
using System;
using ColorControl.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.Web;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Linq;
namespace LgTv
{
@@ -18,7 +18,7 @@ namespace LgTv
}
}
public class LgTvApiCore : IDisposable
{
public bool ConnectionClosed { get; private set; }
@@ -29,6 +29,7 @@ namespace LgTv
public event IsConnectedDelegate IsConnected;
private readonly ConcurrentDictionary<string, TaskCompletionSource<dynamic>> _tokens = new ConcurrentDictionary<string, TaskCompletionSource<dynamic>>();
private readonly ConcurrentDictionary<string, Func<dynamic, bool>> _callbacks = new ConcurrentDictionary<string, Func<dynamic, bool>>();
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
@@ -40,16 +41,22 @@ 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;
await _connection.ConnectAsync(uri);
IsConnected?.Invoke(true);
try
{
await _connection.ConnectAsync(uri).AsTask().WaitAsync(TimeSpan.FromSeconds(5));
_messageWriter = new DataWriter(_connection.OutputStream);
return true;
_messageWriter = new DataWriter(_connection.OutputStream);
IsConnected?.Invoke(true);
return true;
}
catch (TimeoutException)
{
return false;
}
}
catch (Exception e)
{
@@ -66,12 +73,27 @@ namespace LgTv
}
}
public async void SendMessageAsync(string message)
public async Task SendMessageAsync(string message)
{
try
{
if (_messageWriter == null)
{
throw new Exception("SendMessageAsync: no writer");
}
_messageWriter.WriteString(message);
await _messageWriter.StoreAsync();
using (var cancellationTokenSource = new CancellationTokenSource(5000))
{
await _messageWriter.StoreAsync().AsTask(cancellationTokenSource.Token).ContinueWith((antecedent) =>
{
if (antecedent.Status != TaskStatus.RanToCompletion)
{
throw new Exception("SendMessageAsync cancelled due to timeout");
}
});
}
}
catch (Exception e)
{
@@ -81,24 +103,25 @@ namespace LgTv
}
}
public Task<dynamic> SendCommandAsync(string message)
public async Task<dynamic> SendCommandAsync(string message)
{
var obj = JsonConvert.DeserializeObject<dynamic>(message);
return SendCommandAsync((string)obj.id, message);
return await SendCommandAsync((string)obj.id, message);
}
private Task<dynamic> SendCommandAsync(string id, string message)
private async Task<dynamic> SendCommandAsync(string id, string message)
{
try
{
var taskSource = new TaskCompletionSource<dynamic>();
_tokens.TryAdd(id, taskSource);
SendMessageAsync(message);
await SendMessageAsync(message);
if (ConnectionClosed)
{
throw new Exception("Connection closed");
}
return taskSource.Task;
return await taskSource.Task;
}
catch (Exception e)
{
@@ -106,7 +129,7 @@ namespace LgTv
}
}
public Task<dynamic> SendCommandAsync(RequestMessage message)
public async Task<dynamic> SendCommandAsync(RequestMessage message)
{
var rawMessage = new RawRequestMessage(message, ++_commandCount);
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
@@ -114,7 +137,21 @@ namespace LgTv
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return SendCommandAsync(rawMessage.Id, serialized);
return await SendCommandAsync(rawMessage.Id, serialized);
}
public async Task<dynamic> SubscribeAsync(RequestMessage message, Func<dynamic, bool> callback)
{
var rawMessage = new RawRequestMessage(message, ++_commandCount);
var serialized = JsonConvert.SerializeObject(rawMessage, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
_callbacks.TryAdd(rawMessage.Id, callback);
return await SendCommandAsync(rawMessage.Id, serialized);
}
private void Connection_Closed(IWebSocket sender, WebSocketClosedEventArgs args)
@@ -128,7 +165,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);
@@ -146,30 +193,68 @@ namespace LgTv
}
}
else
if (_tokens.TryGetValue(id, out taskCompletion))
else if (_tokens.TryGetValue(id, out taskCompletion))
{
if (id == "register_0") return;
if (obj.type == "error")
{
taskCompletion.SetException(new Exception(obj.error));
taskCompletion.SetException(new Exception(obj.error?.ToString()));
}
//else if (args.Cancelled)
//{
// taskSource.SetCanceled();
//}
taskCompletion.TrySetResult(obj.payload);
if (_callbacks.TryGetValue(id, out Func<dynamic, bool> callback))
{
try
{
callback(obj.payload);
}
catch (Exception callbackException)
{
Logger.Error($"Connection_MessageReceived: the callback threw an exception: {callbackException.ToLogString()}");
}
}
}
}
}
catch (Exception ex)
{
var status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.Message}");
Logger.Error($"Connection_MessageReceived: status: {status}, exception: {ex.ToLogString()}");
SetExceptionOnAllTokens(ex);
ConnectionClosed = true;
_messageWriter?.Dispose();
}
}
private void SetExceptionOnAllTokens(Exception ex)
{
//Logger.Debug($"Tokens: {_tokens.Count}");
foreach (var taskCompletion in _tokens.Values)
{
try
{
if (taskCompletion.Task.Status != TaskStatus.RanToCompletion)
{
Logger.Debug("taskCompletion.Task.Status: " + taskCompletion.Task.Status);
taskCompletion.SetException(new Exception(ex.Message));
}
}
catch (Exception ex2)
{
Logger.Error("SetExceptionOnAllTokens: " + ex2.Message);
}
}
_tokens.Clear();
}
public void Close()
{
_connection?.Close(1, "");
+28 -6
View File
@@ -33,7 +33,29 @@ namespace LgTv
MUTE,
MENU,
CC,
DASH
DASH,
CHANNELUP,
CHANNELDOWN,
LIST,
AD,
SAP,
PROGRAM,
PLAY,
PAUSE,
STOP,
REWIND,
FASTFORWARD,
GUIDE,
AMAZON,
NETFLIX,
MAGNIFIER_ZOOM,
LIVE_ZOOM,
_3D_MODE,
ASPECT_RATIO,
RECENT,
RECORD,
SCREEN_REMOTE,
MYAPPS
}
public class LgWebOsMouseService : IDisposable
@@ -51,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)
{
@@ -60,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 -1
View File
@@ -4,7 +4,7 @@ LG TV WebOS API for .NET
For now, there is only API, example app will be added in the future.
## inspiration:
* https://github.com/msloth/lgtv.js/blob/master/index.js
* https://github.com/msloth/lgtv.js/blob/master/lgtv.js
## references:
* https://github.com/ConnectSDK/Connect-SDK-Android-Core
+6 -2
View File
@@ -9,10 +9,14 @@ namespace LgTv
Prefix = prefix;
Uri = uri;
}
public RequestMessage(string uri,object payload)
public RequestMessage(string uri, object payload, string type = "request")
{
Uri = uri;
Payload = JsonConvert.SerializeObject(payload);
if (payload != null)
{
Payload = JsonConvert.SerializeObject(payload);
}
Type = type;
}
public string Prefix { get; set; }
-16
View File
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Native" version="0.1.0" targetFramework="net472" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net472" />
<package id="NLog" version="4.7.9" targetFramework="net472" />
<package id="NStandard" version="0.6.12.5" targetFramework="net472" />
<package id="NvAPIWrapper.Net" version="0.8.1.101" targetFramework="net472" />
<package id="NWin32" version="1.0.7.6" 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>
@@ -0,0 +1,38 @@
using System;
using System.Text;
namespace nspector.Common
{
internal class CachedSettingValue
{
internal CachedSettingValue() { }
internal CachedSettingValue(uint Value, string ProfileNames)
{
this.Value = Value;
this.ProfileNames = new StringBuilder(ProfileNames);
this.ValueProfileCount = 1;
}
internal CachedSettingValue(string ValueStr, string ProfileNames)
{
this.ValueStr = ValueStr;
this.ProfileNames = new StringBuilder(ProfileNames);
this.ValueProfileCount = 1;
}
internal CachedSettingValue(byte[] ValueBin, string ProfileNames)
{
this.ValueBin = ValueBin;
this.ProfileNames = new StringBuilder(ProfileNames);
this.ValueProfileCount = 1;
}
internal string ValueStr = "";
internal uint Value = 0;
internal byte[] ValueBin = new byte[0];
internal StringBuilder ProfileNames;
internal uint ValueProfileCount;
}
}
+72
View File
@@ -0,0 +1,72 @@
using nspector.Native.NVAPI2;
using System.Collections.Generic;
using System.Linq;
namespace nspector.Common
{
internal class CachedSettings
{
internal CachedSettings() { }
internal CachedSettings(uint settingId, NVDRS_SETTING_TYPE settingType)
{
SettingId = settingId;
SettingType = settingType;
}
internal uint SettingId;
internal List<CachedSettingValue> SettingValues = new List<CachedSettingValue>();
internal uint ProfileCount = 0;
internal NVDRS_SETTING_TYPE SettingType = NVDRS_SETTING_TYPE.NVDRS_DWORD_TYPE;
internal void AddDwordValue(uint valueDword, string Profile)
{
var setting = SettingValues.FirstOrDefault(s => s.Value == valueDword);
if (setting == null)
{
SettingValues.Add(new CachedSettingValue(valueDword, Profile));
}
else
{
setting.ProfileNames.Append(", " + Profile);
setting.ValueProfileCount++;
}
ProfileCount++;
}
internal void AddStringValue(string valueStr, string Profile)
{
var setting = SettingValues.FirstOrDefault(s => s.ValueStr == valueStr);
if (setting == null)
{
SettingValues.Add(new CachedSettingValue(valueStr, Profile));
}
else
{
setting.ProfileNames.Append(", " + Profile);
setting.ValueProfileCount++;
}
ProfileCount++;
}
internal void AddBinaryValue(byte[] valueBin, string Profile)
{
var setting = SettingValues.FirstOrDefault(s => s.ValueBin.SequenceEqual(valueBin));
if (setting == null)
{
SettingValues.Add(new CachedSettingValue(valueBin, Profile));
}
else
{
setting.ProfileNames.Append(", " + Profile);
setting.ValueProfileCount++;
}
ProfileCount++;
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
namespace nspector.Common.CustomSettings
{
[Serializable]
public class CustomSetting
{
public string UserfriendlyName { get; set; }
[XmlElement(ElementName = "HexSettingID")]
public string HexSettingId { get; set; }
public string Description { get; set; }
public string GroupName { get; set; }
public string OverrideDefault { get; set; }
public float MinRequiredDriverVersion { get; set; }
public List<CustomSettingValue> SettingValues { get; set; }
internal uint SettingId
{
get { return Convert.ToUInt32(HexSettingId.Trim(), 16); }
}
internal uint? DefaultValue
{
get { return string.IsNullOrEmpty(OverrideDefault) ? null : (uint?)Convert.ToUInt32(OverrideDefault.Trim(), 16); }
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
using nspector.Common.Helper;
namespace nspector.Common.CustomSettings
{
[Serializable]
public class CustomSettingNames
{
public List<CustomSetting> Settings = new List<CustomSetting>();
public void StoreToFile(string filename)
{
XMLHelper<CustomSettingNames>.SerializeToXmlFile(this, filename, Encoding.Unicode, true);
}
public static CustomSettingNames FactoryLoadFromFile(string filename)
{
return XMLHelper<CustomSettingNames>.DeserializeFromXMLFile(filename);
}
public static CustomSettingNames FactoryLoadFromString(string xml)
{
return XMLHelper<CustomSettingNames>.DeserializeFromXmlString(xml);
}
}
}
@@ -0,0 +1,18 @@
using System;
namespace nspector.Common.CustomSettings
{
[Serializable]
public class CustomSettingValue
{
internal uint SettingValue
{
get { return Convert.ToUInt32(HexValue.Trim(), 16); }
}
public string UserfriendlyName { get; set; }
public string HexValue { get; set; }
}
}
+143
View File
@@ -0,0 +1,143 @@
using nspector.Common.Helper;
using nspector.Native.NVAPI2;
using System.Text;
using System.Text.RegularExpressions;
namespace nspector.Common
{
public class DrsDecrypterService : DrsSettingsServiceBase
{
private static readonly byte[] _InternalSettingsKey = new byte[] {
0x2f, 0x7c, 0x4f, 0x8b, 0x20, 0x24, 0x52, 0x8d, 0x26, 0x3c, 0x94, 0x77, 0xf3, 0x7c, 0x98, 0xa5,
0xfa, 0x71, 0xb6, 0x80, 0xdd, 0x35, 0x84, 0xba, 0xfd, 0xb6, 0xa6, 0x1b, 0x39, 0xc4, 0xcc, 0xb0,
0x7e, 0x95, 0xd9, 0xee, 0x18, 0x4b, 0x9c, 0xf5, 0x2d, 0x4e, 0xd0, 0xc1, 0x55, 0x17, 0xdf, 0x18,
0x1e, 0x0b, 0x18, 0x8b, 0x88, 0x58, 0x86, 0x5a, 0x1e, 0x03, 0xed, 0x56, 0xfb, 0x16, 0xfe, 0x8a,
0x01, 0x32, 0x9c, 0x8d, 0xf2, 0xe8, 0x4a, 0xe6, 0x90, 0x8e, 0x15, 0x68, 0xe8, 0x2d, 0xf4, 0x40,
0x37, 0x9a, 0x72, 0xc7, 0x02, 0x0c, 0xd1, 0xd3, 0x58, 0xea, 0x62, 0xd1, 0x98, 0x36, 0x2b, 0xb2,
0x16, 0xd5, 0xde, 0x93, 0xf1, 0xba, 0x74, 0xe3, 0x32, 0xc4, 0x9f, 0xf6, 0x12, 0xfe, 0x18, 0xc0,
0xbb, 0x35, 0x79, 0x9c, 0x6b, 0x7a, 0x23, 0x7f, 0x2b, 0x15, 0x9b, 0x42, 0x07, 0x1a, 0xff, 0x69,
0xfb, 0x9c, 0xbd, 0x23, 0x97, 0xa8, 0x22, 0x63, 0x8f, 0x32, 0xc8, 0xe9, 0x9b, 0x63, 0x1c, 0xee,
0x2c, 0xd9, 0xed, 0x8d, 0x3a, 0x35, 0x9c, 0xb1, 0x60, 0xae, 0x5e, 0xf5, 0x97, 0x6b, 0x9f, 0x20,
0x8c, 0xf7, 0x98, 0x2c, 0x43, 0x79, 0x95, 0x1d, 0xcd, 0x46, 0x36, 0x6c, 0xd9, 0x67, 0x20, 0xab,
0x41, 0x22, 0x21, 0xe5, 0x55, 0x82, 0xf5, 0x27, 0x20, 0xf5, 0x08, 0x07, 0x3f, 0x6d, 0x69, 0xd9,
0x1c, 0x4b, 0xf8, 0x26, 0x03, 0x6e, 0xb2, 0x3f, 0x1e, 0xe6, 0xca, 0x3d, 0x61, 0x44, 0xb0, 0x92,
0xaf, 0xf0, 0x88, 0xca, 0xe0, 0x5f, 0x5d, 0xf4, 0xdf, 0xc6, 0x4c, 0xa4, 0xe0, 0xca, 0xb0, 0x20,
0x5d, 0xc0, 0xfa, 0xdd, 0x9a, 0x34, 0x8f, 0x50, 0x79, 0x5a, 0x5f, 0x7c, 0x19, 0x9e, 0x40, 0x70,
0x71, 0xb5, 0x45, 0x19, 0xb8, 0x53, 0xfc, 0xdf, 0x24, 0xbe, 0x22, 0x1c, 0x79, 0xbf, 0x42, 0x89 };
public DrsDecrypterService(DrsSettingsMetaService metaService) : base(metaService)
{
try
{
CreateInternalSettingMap();
}
catch { }
}
private uint GetDwordFromKey(uint offset)
{
var bytes = new byte[4];
bytes[0] = _InternalSettingsKey[(offset + 0) % 256];
bytes[1] = _InternalSettingsKey[(offset + 1) % 256];
bytes[2] = _InternalSettingsKey[(offset + 2) % 256];
bytes[3] = _InternalSettingsKey[(offset + 3) % 256];
return BitConverter.ToUInt32(bytes, 0);
}
public uint DecryptDwordValue(uint orgValue, uint settingId)
{
var keyOffset = (settingId << 1);
var key = GetDwordFromKey(keyOffset);
return orgValue ^ key;
}
public string DecryptStringValue(byte[] rawData, uint settingId)
{
var keyOffset = (settingId << 1);
for (uint i = 0; i < (uint)rawData.Length; i++)
{
rawData[i] ^= _InternalSettingsKey[(keyOffset + i) % 256];
}
return Encoding.Unicode.GetString(rawData).Trim('\0');
}
public void DecryptSettingIfNeeded(string profileName, ref NVDRS_SETTING setting)
{
if (setting.isPredefinedValid == 1)
{
if (IsInternalSetting(profileName, setting.settingId))
{
if (setting.settingType == NVDRS_SETTING_TYPE.NVDRS_WSTRING_TYPE)
{
setting.predefinedValue.stringValue = DecryptStringValue(setting.predefinedValue.rawData, setting.settingId);
if (setting.isCurrentPredefined == 1)
setting.currentValue.stringValue = DecryptStringValue(setting.currentValue.rawData, setting.settingId);
}
else if (setting.settingType == NVDRS_SETTING_TYPE.NVDRS_DWORD_TYPE)
{
setting.predefinedValue.dwordValue = DecryptDwordValue(setting.predefinedValue.dwordValue, setting.settingId);
if (setting.isCurrentPredefined == 1)
setting.currentValue.dwordValue = DecryptDwordValue(setting.currentValue.dwordValue, setting.settingId);
}
}
}
}
private string FormatInternalSettingKey(string profileName, uint settingId)
{
return profileName + settingId.ToString("X8").ToLower();
}
public bool IsInternalSetting(string profileName, uint settingId)
{
return _InternalSettings.Contains(FormatInternalSettingKey(profileName, settingId));
}
private HashSet<string> _InternalSettings = new HashSet<string>();
private void CreateInternalSettingMap()
{
string tmpfile = TempFile.GetTempFileName();
try
{
DrsSession((hSession) =>
{
SaveSettingsFileEx(hSession, tmpfile);
});
if (File.Exists(tmpfile))
{
var lines = File.ReadAllLines(tmpfile);
_InternalSettings = new HashSet<string>();
var paProfile = "Profile\\s\\\"(?<profileName>.*?)\\\"";
var rxProfile = new Regex(paProfile, RegexOptions.Compiled);
var paSetting = "ID_0x(?<sid>[0-9a-fA-F]+)\\s\\=.*?InternalSettingFlag\\=V0";
var rxSetting = new Regex(paSetting, RegexOptions.Compiled);
var currentProfileName = "";
for (int i = 0; i < lines.Length; i++)
{
foreach (Match ms in rxProfile.Matches(lines[i]))
{
currentProfileName = ms.Result("${profileName}");
}
foreach (Match ms in rxSetting.Matches(lines[i]))
{
_InternalSettings.Add(currentProfileName + ms.Result("${sid}"));
}
}
}
}
finally
{
if (File.Exists(tmpfile))
File.Delete(tmpfile);
}
}
}
}
+274
View File
@@ -0,0 +1,274 @@
using nspector.Common.Helper;
using nspector.Common.Import;
using nspector.Native.NVAPI2;
using System.Text;
using nvw = nspector.Native.NVAPI2.NvapiDrsWrapper;
namespace nspector.Common
{
public class DrsImportService : DrsSettingsServiceBase
{
private readonly DrsSettingsService _SettingService;
private readonly DrsScannerService _ScannerService;
private readonly DrsDecrypterService _DecrypterService;
public DrsImportService(
DrsSettingsMetaService metaService,
DrsSettingsService settingService,
DrsScannerService scannerService,
DrsDecrypterService decrypterService)
: base(metaService)
{
_SettingService = settingService;
_ScannerService = scannerService;
_DecrypterService = decrypterService;
}
internal void ExportAllProfilesToNvidiaTextFile(string filename)
{
DrsSession((hSession) =>
{
SaveSettingsFileEx(hSession, filename);
});
}
internal void ImportAllProfilesFromNvidiaTextFile(string filename)
{
DrsSession((hSession) =>
{
LoadSettingsFileEx(hSession, filename);
SaveSettings(hSession);
}, forceNonGlobalSession: true, preventLoadSettings: true);
}
internal void ExportProfiles(List<string> profileNames, string filename, bool includePredefined)
{
var exports = new Profiles();
DrsSession((hSession) =>
{
foreach (var profileName in profileNames)
{
var profile = CreateProfileForExport(hSession, profileName, includePredefined);
exports.Add(profile);
}
});
XMLHelper<Profiles>.SerializeToXmlFile(exports, filename, Encoding.Unicode, true);
}
private Profile CreateProfileForExport(IntPtr hSession, string profileName, bool includePredefined)
{
var result = new Profile();
var hProfile = GetProfileHandle(hSession, profileName);
if (hProfile != IntPtr.Zero)
{
result.ProfileName = profileName;
var apps = GetProfileApplications(hSession, hProfile);
foreach (var app in apps)
{
result.Executeables.Add(app.appName);
}
var settings = GetProfileSettings(hSession, hProfile);
foreach (var setting in settings)
{
var isPredefined = setting.isCurrentPredefined == 1;
var isCurrentProfile = setting.settingLocation ==
NVDRS_SETTING_LOCATION.NVDRS_CURRENT_PROFILE_LOCATION;
if (isCurrentProfile && (!isPredefined || includePredefined))
{
var exportSetting = setting;
_DecrypterService.DecryptSettingIfNeeded(profileName, ref exportSetting);
var profileSetting = ImportExportUitl
.ConvertDrsSettingToProfileSetting(exportSetting);
result.Settings.Add(profileSetting);
}
}
}
return result;
}
internal string ImportProfiles(string filename)
{
var sbFailedProfilesMessage = new StringBuilder();
var appInUseHint = false;
var profiles = XMLHelper<Profiles>.DeserializeFromXMLFile(filename);
DrsSession((hSession) =>
{
foreach (Profile profile in profiles)
{
var profileCreated = false;
var hProfile = GetProfileHandle(hSession, profile.ProfileName);
if (hProfile == IntPtr.Zero)
{
hProfile = CreateProfile(hSession, profile.ProfileName);
nvw.DRS_SaveSettings(hSession);
profileCreated = true;
}
if (hProfile != IntPtr.Zero)
{
var modified = false;
_SettingService.ResetProfile(profile.ProfileName, out modified);
try
{
UpdateApplications(hSession, hProfile, profile);
UpdateSettings(hSession, hProfile, profile, profile.ProfileName);
}
catch (NvapiException nex)
{
if (profileCreated)
{
nvw.DRS_DeleteProfile(hSession, hProfile);
}
sbFailedProfilesMessage.AppendLine(string.Format("Failed to import profile '{0}'", profile.ProfileName));
var appEx = nex as NvapiAddApplicationException;
if (appEx != null)
{
var profilesWithThisApp = _ScannerService.FindProfilesUsingApplication(appEx.ApplicationName);
sbFailedProfilesMessage.AppendLine(string.Format("- application '{0}' is already in use by profile '{1}'", appEx.ApplicationName, profilesWithThisApp));
appInUseHint = true;
}
else
{
sbFailedProfilesMessage.AppendLine(string.Format("- {0}", nex.Message));
}
sbFailedProfilesMessage.AppendLine("");
}
nvw.DRS_SaveSettings(hSession);
}
}
});
if (appInUseHint)
{
sbFailedProfilesMessage.AppendLine("Hint: If just the profile name has been changed by nvidia, consider to manually modify the profile name inside the import file using a text editor.");
}
return sbFailedProfilesMessage.ToString();
}
private bool ExistsImportApp(string appName, Profile importProfile)
{
return importProfile.Executeables.Any(x => x.Equals(appName));
}
private void UpdateApplications(IntPtr hSession, IntPtr hProfile, Profile importProfile)
{
var alreadySet = new HashSet<string>();
var apps = GetProfileApplications(hSession, hProfile);
foreach (var app in apps)
{
if (ExistsImportApp(app.appName, importProfile) && !alreadySet.Contains(app.appName))
alreadySet.Add(app.appName);
else
nvw.DRS_DeleteApplication(hSession, hProfile, new StringBuilder(app.appName));
}
foreach (string appName in importProfile.Executeables)
{
if (!alreadySet.Contains(appName))
{
try
{
AddApplication(hSession, hProfile, appName);
}
catch (NvapiException)
{
throw new NvapiAddApplicationException(appName);
}
}
}
}
private uint GetImportValue(uint settingId, Profile importProfile)
{
var setting = importProfile.Settings
.FirstOrDefault(x => x.SettingId.Equals(settingId));
if (setting != null)
return uint.Parse(setting.SettingValue);
return 0;
}
private ProfileSetting GetImportProfileSetting(uint settingId, Profile importProfile)
{
return importProfile.Settings
.FirstOrDefault(x => x.SettingId.Equals(settingId));
}
private bool ExistsImportValue(uint settingId, Profile importProfile)
{
return importProfile.Settings
.Any(x => x.SettingId.Equals(settingId));
}
private void UpdateSettings(IntPtr hSession, IntPtr hProfile, Profile importProfile, string profileName)
{
var alreadySet = new HashSet<uint>();
var settings = GetProfileSettings(hSession, hProfile);
foreach (var setting in settings)
{
var isCurrentProfile = setting.settingLocation == NVDRS_SETTING_LOCATION.NVDRS_CURRENT_PROFILE_LOCATION;
var isPredefined = setting.isCurrentPredefined == 1;
if (isCurrentProfile)
{
bool exitsValueInImport = ExistsImportValue(setting.settingId, importProfile);
var importSetting = GetImportProfileSetting(setting.settingId, importProfile);
var decryptedSetting = setting;
_DecrypterService.DecryptSettingIfNeeded(profileName, ref decryptedSetting);
if (isPredefined && exitsValueInImport && ImportExportUitl.AreDrsSettingEqualToProfileSetting(decryptedSetting, importSetting))
{
alreadySet.Add(setting.settingId);
}
else if (exitsValueInImport)
{
var updatedSetting = ImportExportUitl.ConvertProfileSettingToDrsSetting(importSetting);
StoreSetting(hSession, hProfile, updatedSetting);
alreadySet.Add(setting.settingId);
}
else if (!isPredefined)
{
nvw.DRS_DeleteProfileSetting(hSession, hProfile, setting.settingId);
}
}
}
foreach (var setting in importProfile.Settings)
{
if (!alreadySet.Contains(setting.SettingId))
{
var newSetting = ImportExportUitl.ConvertProfileSettingToDrsSetting(setting);
try
{
StoreSetting(hSession, hProfile, newSetting);
}
catch (NvapiException ex)
{
if (ex.Status != NvAPI_Status.NVAPI_SETTING_NOT_FOUND)
throw;
}
}
}
}
}
}
Loaded 100 of 207 files, more files were not shown because too many files have changed in this diff. Show more