1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00
RGB.NET/RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs
2025-02-08 21:52:28 +01:00

351 lines
16 KiB
C#

// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Collections.Generic;
using System.Threading;
using RGB.NET.Core;
using RGB.NET.Devices.Corsair.Native;
namespace RGB.NET.Devices.Corsair;
/// <inheritdoc />
/// <summary>
/// Represents a device provider responsible for corsair (CUE) devices.
/// </summary>
public sealed class CorsairDeviceProvider : AbstractRGBDeviceProvider
{
#region Properties & Fields
// ReSharper disable once InconsistentNaming
private static readonly Lock _lock = new();
private static CorsairDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="CorsairDeviceProvider"/> instance.
/// </summary>
public static CorsairDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new CorsairDeviceProvider();
}
}
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x86 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX86NativePaths { get; } = ["x86/iCUESDK.dll", "x86/CUESDK_2019.dll"];
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x64 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX64NativePaths { get; } = ["x64/iCUESDK.dll", "x64/iCUESDK.x64_2019.dll", "x64/CUESDK.dll", "x64/CUESDK.x64_2019.dll"];
/// <summary>
/// Gets or sets the timeout used when connecting to the SDK.
/// </summary>
public static TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
/// <summary>
/// Gets or sets a bool indicating if exclusive request should be requested through the iCUE-SDK.
/// </summary>
public static bool ExclusiveAccess { get; set; } = false;
/// <summary>
/// Gets the details for the current SDK-session.
/// </summary>
public CorsairSessionDetails SessionDetails { get; private set; } = new();
private CorsairSessionState _sessionState = CorsairSessionState.Invalid;
public CorsairSessionState SessionState
{
get => _sessionState;
private set
{
_sessionState = value;
try { SessionStateChanged?.Invoke(this, SessionState); }
catch { /* catch faulty event-handlers*/ }
}
}
#endregion
#region Events
// ReSharper disable once UnassignedField.Global
public EventHandler<CorsairSessionState>? SessionStateChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="CorsairDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public CorsairDeviceProvider()
{
lock (_lock)
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(CorsairDeviceProvider)}");
_instance = this;
}
}
#endregion
#region Methods
protected override void InitializeSDK()
{
_CUESDK.Reload();
using ManualResetEventSlim waitEvent = new(false);
void OnInitializeSessionStateChanged(object? sender, CorsairSessionState state)
{
if (state == CorsairSessionState.Connected)
// ReSharper disable once AccessToDisposedClosure
waitEvent.Set();
}
try
{
_CUESDK.SessionStateChanged += OnSessionStateChanged;
_CUESDK.SessionStateChanged += OnInitializeSessionStateChanged;
CorsairError errorCode = _CUESDK.CorsairConnect();
if (errorCode != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to initialized Corsair-SDK. (ErrorCode: {errorCode})"));
if (!waitEvent.Wait(ConnectionTimeout))
Throw(new RGBDeviceException($"Failed to initialized Corsair-SDK. (Timeout - Current connection state: {_CUESDK.SesionState})"));
_CUESDK.CorsairGetSessionDetails(out _CorsairSessionDetails? details);
if (errorCode != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to get session details. (ErrorCode: {errorCode})"));
SessionDetails = new CorsairSessionDetails(details!);
}
finally
{
_CUESDK.SessionStateChanged -= OnInitializeSessionStateChanged;
}
}
private void OnSessionStateChanged(object? sender, CorsairSessionState state) => SessionState = state;
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
foreach (ICorsairRGBDevice corsairDevice in LoadCorsairDevices())
{
corsairDevice.Initialize();
yield return corsairDevice;
}
}
private IEnumerable<ICorsairRGBDevice> LoadCorsairDevices()
{
CorsairError error = _CUESDK.CorsairGetDevices(new _CorsairDeviceFilter(CorsairDeviceType.All), out _CorsairDeviceInfo[] devices);
if (error != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to load devices. (ErrorCode: {error})"));
foreach (_CorsairDeviceInfo device in devices)
{
if (string.IsNullOrWhiteSpace(device.id)) continue;
error = _CUESDK.CorsairRequestControl(device.id, ExclusiveAccess ? CorsairAccessLevel.ExclusiveLightingControl : CorsairAccessLevel.Shared);
if (error != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to take control of device '{device.id}'. (ErrorCode: {error})"));
CorsairDeviceUpdateQueue updateQueue = new(GetUpdateTrigger(), device);
int channelLedCount = 0;
for (int i = 0; i < device.channelCount; i++)
{
Console.WriteLine($"Channel {i}/{device.channelCount}");
channelLedCount += _CUESDK.ReadDevicePropertySimpleInt32(device.id!, CorsairDevicePropertyId.ChannelLedCount, (uint)i);
}
int deviceLedCount = device.ledCount - channelLedCount;
if (deviceLedCount > 0)
switch (device.type)
{
case CorsairDeviceType.Keyboard:
yield return new CorsairKeyboardRGBDevice(new CorsairKeyboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Mouse:
yield return new CorsairMouseRGBDevice(new CorsairMouseRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Headset:
yield return new CorsairHeadsetRGBDevice(new CorsairHeadsetRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Mousemat:
yield return new CorsairMousepadRGBDevice(new CorsairMousepadRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.HeadsetStand:
yield return new CorsairHeadsetStandRGBDevice(new CorsairHeadsetStandRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.MemoryModule:
yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Motherboard:
yield return new CorsairMainboardRGBDevice(new CorsairMainboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.GraphicsCard:
yield return new CorsairGraphicsCardRGBDevice(new CorsairGraphicsCardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Touchbar:
yield return new CorsairTouchbarRGBDevice(new CorsairTouchbarRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Cooler:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.GameController:
yield return new CorsairGameControllerRGBDevice(new CorsairGameControllerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.FanLedController:
case CorsairDeviceType.LedController:
case CorsairDeviceType.Unknown:
yield return new CorsairUnknownRGBDevice(new CorsairUnknownRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
default:
Throw(new RGBDeviceException("Unknown Device-Type"));
break;
}
int offset = deviceLedCount;
for (int i = 0; i < device.channelCount; i++)
{
int deviceCount = _CUESDK.ReadDevicePropertySimpleInt32(device.id!, CorsairDevicePropertyId.ChannelDeviceCount, (uint)i);
if (deviceCount <= 0) continue; // DarthAffe 10.02.2023: There seem to be an issue in the SDK where it reports empty channels and fails when getting ledCounts and device types from them
int[] ledCounts = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceLedCountArray, (uint)i);
int[] deviceTypes = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceTypeArray, (uint)i);
for (int j = 0; j < deviceCount; j++)
{
CorsairChannelDeviceType deviceType = (CorsairChannelDeviceType)deviceTypes[j];
int ledCount = ledCounts[j];
switch (deviceType)
{
case CorsairChannelDeviceType.FanHD:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "HD Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanSP:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "SP Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanLL:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "LL Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanML:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "ML Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanQL:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QL Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanQX:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QX Fan"), updateQueue);
break;
case CorsairChannelDeviceType.EightLedSeriesFan:
string fanModelName = "8-Led-Series Fan";
if (device.model == "iCUE LINK System Hub")
fanModelName = "RX Fan";
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, fanModelName), updateQueue);
break;
case CorsairChannelDeviceType.DAP:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "DAP Fan"), updateQueue);
break;
case CorsairChannelDeviceType.Pump:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Pump"), updateQueue);
break;
case CorsairChannelDeviceType.WaterBlock:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Water Block"), updateQueue);
break;
case CorsairChannelDeviceType.Strip:
string stripModelName = "LED Strip";
// LS100 Led Strips are reported as one big strip if configured in monitor mode in iCUE, 138 LEDs for dual monitor, 84 for single
if ((device.model == "LS100 Starter Kit") && (ledCount == 138))
stripModelName = "LS100 LED Strip (dual monitor)";
else if ((device.model == "LS100 Starter Kit") && (ledCount == 84))
stripModelName = "LS100 LED Strip (single monitor)";
// Any other value means an "External LED Strip" in iCUE, these are reported per-strip, 15 for short strips, 27 for long
else if ((device.model == "LS100 Starter Kit") && (ledCount == 15))
stripModelName = "LS100 LED Strip (short)";
else if ((device.model == "LS100 Starter Kit") && (ledCount == 27))
stripModelName = "LS100 LED Strip (long)";
yield return new CorsairLedStripRGBDevice(new CorsairLedStripRGBDeviceInfo(device, ledCount, offset, stripModelName), updateQueue);
break;
case CorsairChannelDeviceType.DRAM:
yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, ledCount, offset, "DRAM"), updateQueue);
break;
default:
//Workaround to support LX Fans because they have an invalid ChannelDeviceType
if ((device.model == "iCUE LINK System Hub") && (ledCount == 18))
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "LX Fan"), updateQueue);
else
Throw(new RGBDeviceException("Unknown Device-Type"));
break;
}
offset += ledCount;
}
}
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
lock (_lock)
{
base.Dispose(disposing);
try { _CUESDK.CorsairDisconnect(); }
catch { /* at least we tried */ }
try { _CUESDK.UnloadCUESDK(); }
catch { /* at least we tried */ }
_instance = null;
}
}
#endregion
}