From 1324a770114ebc305ada8d04bb1bf47158b51d53 Mon Sep 17 00:00:00 2001 From: Andrew Babbitt Date: Sat, 13 Jul 2024 15:28:57 -0600 Subject: [PATCH] feat: openrgb sdk --- ChromaControl.SDK.sln | 14 ++- .../ChromaControl.SDK.OpenRGB.Sample.csproj | 14 +++ .../Program.cs | 13 +++ .../Properties/launchSettings.json | 12 +++ .../Worker.cs | 59 ++++++++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 8 ++ .../ChromaControl.SDK.OpenRGB.csproj | 16 ++++ .../ServiceCollectionOpenRGBExtensions.cs | 25 +++++ .../IOpenRGBService.cs | 21 +++++ src/ChromaControl.SDK.OpenRGB/Internal/Job.cs | 93 +++++++++++++++++++ .../Internal/OpenRGBManager.cs | 77 +++++++++++++++ .../NativeMethods.txt | 4 + .../OpenRGBService.cs | 33 +++++++ 14 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/ChromaControl.SDK.OpenRGB.Sample.csproj create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/Program.cs create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/Properties/launchSettings.json create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/Worker.cs create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/appsettings.Development.json create mode 100644 src/ChromaControl.SDK.OpenRGB.Sample/appsettings.json create mode 100644 src/ChromaControl.SDK.OpenRGB/ChromaControl.SDK.OpenRGB.csproj create mode 100644 src/ChromaControl.SDK.OpenRGB/Extensions/ServiceCollectionOpenRGBExtensions.cs create mode 100644 src/ChromaControl.SDK.OpenRGB/IOpenRGBService.cs create mode 100644 src/ChromaControl.SDK.OpenRGB/Internal/Job.cs create mode 100644 src/ChromaControl.SDK.OpenRGB/Internal/OpenRGBManager.cs create mode 100644 src/ChromaControl.SDK.OpenRGB/NativeMethods.txt create mode 100644 src/ChromaControl.SDK.OpenRGB/OpenRGBService.cs diff --git a/ChromaControl.SDK.sln b/ChromaControl.SDK.sln index 2e71779..4229409 100644 --- a/ChromaControl.SDK.sln +++ b/ChromaControl.SDK.sln @@ -5,7 +5,11 @@ VisualStudioVersion = 17.11.35017.193 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChromaControl.SDK.Synapse", "src\ChromaControl.SDK.Synapse\ChromaControl.SDK.Synapse.csproj", "{D192C5EA-EC40-4BA9-AE43-ADA9FB4F446A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChromaControl.SDK.Synapse.Sample", "src\ChromaControl.SDK.Synapse.Sample\ChromaControl.SDK.Synapse.Sample.csproj", "{D8F00B39-3709-4157-9F87-521FDEA6041C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChromaControl.SDK.Synapse.Sample", "src\ChromaControl.SDK.Synapse.Sample\ChromaControl.SDK.Synapse.Sample.csproj", "{D8F00B39-3709-4157-9F87-521FDEA6041C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChromaControl.SDK.OpenRGB", "src\ChromaControl.SDK.OpenRGB\ChromaControl.SDK.OpenRGB.csproj", "{9542A5ED-B1B8-4E27-9AC1-B4C0565F72C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChromaControl.SDK.OpenRGB.Sample", "src\ChromaControl.SDK.OpenRGB.Sample\ChromaControl.SDK.OpenRGB.Sample.csproj", "{ED14E023-F619-4B15-8730-5C6195CB9358}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +25,14 @@ Global {D8F00B39-3709-4157-9F87-521FDEA6041C}.Debug|Any CPU.Build.0 = Debug|Any CPU {D8F00B39-3709-4157-9F87-521FDEA6041C}.Release|Any CPU.ActiveCfg = Release|Any CPU {D8F00B39-3709-4157-9F87-521FDEA6041C}.Release|Any CPU.Build.0 = Release|Any CPU + {9542A5ED-B1B8-4E27-9AC1-B4C0565F72C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9542A5ED-B1B8-4E27-9AC1-B4C0565F72C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9542A5ED-B1B8-4E27-9AC1-B4C0565F72C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9542A5ED-B1B8-4E27-9AC1-B4C0565F72C1}.Release|Any CPU.Build.0 = Release|Any CPU + {ED14E023-F619-4B15-8730-5C6195CB9358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED14E023-F619-4B15-8730-5C6195CB9358}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED14E023-F619-4B15-8730-5C6195CB9358}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED14E023-F619-4B15-8730-5C6195CB9358}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/ChromaControl.SDK.OpenRGB.Sample.csproj b/src/ChromaControl.SDK.OpenRGB.Sample/ChromaControl.SDK.OpenRGB.Sample.csproj new file mode 100644 index 0000000..eb22be0 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/ChromaControl.SDK.OpenRGB.Sample.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + + + + + + + + + + diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/Program.cs b/src/ChromaControl.SDK.OpenRGB.Sample/Program.cs new file mode 100644 index 0000000..8974f0e --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/Program.cs @@ -0,0 +1,13 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using ChromaControl.SDK.OpenRGB.Extensions; +using ChromaControl.SDK.OpenRGB.Sample; + +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddOpenRGBSDK(); +builder.Services.AddHostedService(); + +await builder.Build().RunAsync(); diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/Properties/launchSettings.json b/src/ChromaControl.SDK.OpenRGB.Sample/Properties/launchSettings.json new file mode 100644 index 0000000..ef1c941 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "ChromaControl.SDK.OpenRGB.Sample": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/Worker.cs b/src/ChromaControl.SDK.OpenRGB.Sample/Worker.cs new file mode 100644 index 0000000..d7cf9ec --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/Worker.cs @@ -0,0 +1,59 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ChromaControl.SDK.OpenRGB.Sample; + +/// +/// The worker. +/// +public partial class Worker : IHostedService +{ + private readonly ILogger _logger; + private readonly IOpenRGBService _openRGB; + + [LoggerMessage(Level = LogLevel.Information, Message = "Starting OpenRGB Service...")] + private static partial void LogStartMessage(ILogger logger); + + [LoggerMessage(Level = LogLevel.Information, Message = "Stopping OpenRGB Service...")] + private static partial void LogStopMessage(ILogger logger); + + /// + /// Creates a instance. + /// + /// The . + /// The . + public Worker(ILogger logger, IOpenRGBService openRGB) + { + _logger = logger; + _openRGB = openRGB; + } + + /// + /// Starts the worker. + /// + /// The . + /// A representing the worker starting. + public Task StartAsync(CancellationToken cancellationToken) + { + LogStartMessage(_logger); + + _openRGB.StartService(); + + return Task.CompletedTask; + } + + /// + /// Stops the worker. + /// + /// The . + /// A representing the worker stopping. + public Task StopAsync(CancellationToken cancellationToken) + { + LogStopMessage(_logger); + + _openRGB.StopService(); + + return Task.CompletedTask; + } +} diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.Development.json b/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.json b/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB.Sample/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/ChromaControl.SDK.OpenRGB/ChromaControl.SDK.OpenRGB.csproj b/src/ChromaControl.SDK.OpenRGB/ChromaControl.SDK.OpenRGB.csproj new file mode 100644 index 0000000..b7154ad --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/ChromaControl.SDK.OpenRGB.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/ChromaControl.SDK.OpenRGB/Extensions/ServiceCollectionOpenRGBExtensions.cs b/src/ChromaControl.SDK.OpenRGB/Extensions/ServiceCollectionOpenRGBExtensions.cs new file mode 100644 index 0000000..0065822 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/Extensions/ServiceCollectionOpenRGBExtensions.cs @@ -0,0 +1,25 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.DependencyInjection; + +namespace ChromaControl.SDK.OpenRGB.Extensions; + +/// +/// Extension methods for adding the OpenRGB SDK to an . +/// +public static class ServiceCollectionOpenRGBExtensions +{ + /// + /// Registers the OpenRGB SDK in an . + /// + /// The to register with. + /// The original . + public static IServiceCollection AddOpenRGBSDK(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } +} diff --git a/src/ChromaControl.SDK.OpenRGB/IOpenRGBService.cs b/src/ChromaControl.SDK.OpenRGB/IOpenRGBService.cs new file mode 100644 index 0000000..8af7dc0 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/IOpenRGBService.cs @@ -0,0 +1,21 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ChromaControl.SDK.OpenRGB; + +/// +/// The OpenRGB Service. +/// +public interface IOpenRGBService +{ + /// + /// Starts the OpenRGB service. + /// + void StartService(); + + /// + /// Stopes the OpenRGB service. + /// + void StopService(); +} diff --git a/src/ChromaControl.SDK.OpenRGB/Internal/Job.cs b/src/ChromaControl.SDK.OpenRGB/Internal/Job.cs new file mode 100644 index 0000000..7454032 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/Internal/Job.cs @@ -0,0 +1,93 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Security; +using Windows.Win32.System.JobObjects; + +namespace ChromaControl.SDK.OpenRGB.Internal; + +internal sealed class Job : IDisposable +{ + private readonly SafeFileHandle _jobHandle; + + public Job() + { + _jobHandle = CreateJob(); + + SetJobInfo(_jobHandle); + } + + private static unsafe SafeFileHandle CreateJob() + { + var securityAttributes = new SECURITY_ATTRIBUTES + { + bInheritHandle = false, + lpSecurityDescriptor = IntPtr.Zero.ToPointer(), + nLength = (uint)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)) + }; + +#pragma warning disable CA1416 // Validate platform compatibility + var jobHandle = PInvoke.CreateJobObject(securityAttributes, "ChromaControl.SDK.OpenRGB"); +#pragma warning restore CA1416 // Validate platform compatibility + + if (jobHandle.IsInvalid) + { + jobHandle.Dispose(); + + var lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); + } + + return jobHandle; + } + + private static unsafe void SetJobInfo(SafeFileHandle jobHandle) + { + var info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + BasicLimitInformation = new() + { + LimitFlags = JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION + | JOB_OBJECT_LIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + } + }; + + var infoSize = (uint)Marshal.SizeOf(); + +#pragma warning disable CA1416 // Validate platform compatibility + var setInfoResult = PInvoke.SetInformationJobObject(jobHandle, JOBOBJECTINFOCLASS.JobObjectExtendedLimitInformation, &info, infoSize); +#pragma warning restore CA1416 // Validate platform compatibility + + if (!setInfoResult) + { + var lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); + } + } + + public void AssignProcess(Process process) + { +#pragma warning disable CA1416 // Validate platform compatibility + var assignProcessResult = PInvoke.AssignProcessToJobObject(_jobHandle, process.SafeHandle); +#pragma warning restore CA1416 // Validate platform compatibility + + if (!assignProcessResult) + { + var lastError = Marshal.GetLastWin32Error(); + throw new Win32Exception(lastError); + } + } + + public void Dispose() + { + _jobHandle?.Dispose(); + + GC.SuppressFinalize(this); + } +} diff --git a/src/ChromaControl.SDK.OpenRGB/Internal/OpenRGBManager.cs b/src/ChromaControl.SDK.OpenRGB/Internal/OpenRGBManager.cs new file mode 100644 index 0000000..13a3aeb --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/Internal/OpenRGBManager.cs @@ -0,0 +1,77 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; + +namespace ChromaControl.SDK.OpenRGB.Internal; + +internal sealed partial class OpenRGBManager : IDisposable +{ + private static bool Is32Bit => nint.Size == 4; + + private readonly Process _process = CreateProcess(); + private readonly Job _job; + + public OpenRGBManager() + { + _job = new(); + } + + public void Start() + { + _job.AssignProcess(Process.GetCurrentProcess()); + _process.Start(); + } + + public void Stop() + { + _process.Kill(); + } + + public void Dispose() + { + _process.Kill(); + _process.Dispose(); + + _job.Dispose(); + + GC.SuppressFinalize(this); + } + + private static Process CreateProcess() + { + var process = new Process(); + + process.StartInfo.FileName = "cmd.exe"; + process.StartInfo.UseShellExecute = true; + process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; + process.StartInfo.Arguments = $"/C \"{FindExecutablePath()}\" --noautoconnect --server"; + + return process; + } + + private static string FindExecutablePath() + { + var assembly = typeof(OpenRGBManager).Assembly; + var assemblyFilePath = assembly.Location; + var assemblyPath = Path.GetDirectoryName(assemblyFilePath) ?? throw new FileNotFoundException("Unable to find the OpenRGB executable."); + var runtimeIdentifier = Is32Bit ? "win-x86" : "win-x64"; + + var searchPaths = new List() + { + Path.Combine(assemblyPath, "OpenRGB.exe"), + Path.Combine(assemblyPath, "runtimes", runtimeIdentifier, "native", "OpenRGB.exe") + }; + + foreach (var searchPath in searchPaths) + { + if (File.Exists(searchPath)) + { + return searchPath; + } + } + + throw new FileNotFoundException("Unable to find the OpenRGB executable."); + } +} diff --git a/src/ChromaControl.SDK.OpenRGB/NativeMethods.txt b/src/ChromaControl.SDK.OpenRGB/NativeMethods.txt new file mode 100644 index 0000000..fcf170d --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/NativeMethods.txt @@ -0,0 +1,4 @@ +AssignProcessToJobObject +CreateJobObject +SetInformationJobObject +JOBOBJECT_EXTENDED_LIMIT_INFORMATION diff --git a/src/ChromaControl.SDK.OpenRGB/OpenRGBService.cs b/src/ChromaControl.SDK.OpenRGB/OpenRGBService.cs new file mode 100644 index 0000000..79b90f4 --- /dev/null +++ b/src/ChromaControl.SDK.OpenRGB/OpenRGBService.cs @@ -0,0 +1,33 @@ +// Licensed to the Chroma Control Contributors under one or more agreements. +// The Chroma Control Contributors licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using ChromaControl.SDK.OpenRGB.Internal; + +namespace ChromaControl.SDK.OpenRGB; + +/// +public class OpenRGBService : IOpenRGBService, IDisposable +{ + private readonly OpenRGBManager _openRGB = new(); + + /// + public void StartService() + { + _openRGB.Start(); + } + + /// + public void StopService() + { + _openRGB.Stop(); + } + + /// + public void Dispose() + { + _openRGB.Dispose(); + + GC.SuppressFinalize(this); + } +}