// 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; /// /// /// Represents a device provider responsible for corsair (CUE) devices. /// public class CorsairDeviceProvider : AbstractRGBDeviceProvider { #region Properties & Fields private static CorsairDeviceProvider? _instance; /// /// Gets the singleton instance. /// public static CorsairDeviceProvider Instance => _instance ?? new CorsairDeviceProvider(); /// /// Gets a modifiable list of paths used to find the native SDK-dlls for x86 applications. /// The first match will be used. /// public static List PossibleX86NativePaths { get; } = new() { "x86/iCUESDK.dll", "x86/CUESDK_2019.dll" }; /// /// Gets a modifiable list of paths used to find the native SDK-dlls for x64 applications. /// The first match will be used. /// public static List PossibleX64NativePaths { get; } = new() { "x64/iCUESDK.dll", "x64/iCUESDK.x64_2019.dll", "x64/CUESDK.dll", "x64/CUESDK.x64_2019.dll" }; /// /// Gets or sets the timeout used when connecting to the SDK. /// public static TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromMilliseconds(500); /// /// Gets or sets a bool indicating if exclusive request should be requested through the iCUE-SDK. /// public static bool ExclusiveAccess { get; set; } = false; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// Thrown if this constructor is called even if there is already an instance of this class. public CorsairDeviceProvider() { 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 OnSessionStateChanged(object? sender, CorsairSessionState state) { if (state == CorsairSessionState.Connected) // ReSharper disable once AccessToDisposedClosure waitEvent.Set(); } try { _CUESDK.SessionStateChanged += OnSessionStateChanged; 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})")); } finally { _CUESDK.SessionStateChanged -= OnSessionStateChanged; } } /// protected override IEnumerable LoadDevices() { foreach (ICorsairRGBDevice corsairDevice in LoadCorsairDevices()) { corsairDevice.Initialize(); yield return corsairDevice; } } private IEnumerable 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); Console.WriteLine("Loading " + device.model); 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.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.EightLedSeriesFan: yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "8-Led-Series Fan Fan"), 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 modelName = "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)) modelName = "LS100 LED Strip (dual monitor)"; else if ((device.model == "LS100 Starter Kit") && (ledCount == 84)) modelName = "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)) modelName = "LS100 LED Strip (short)"; else if ((device.model == "LS100 Starter Kit") && (ledCount == 27)) modelName = "LS100 LED Strip (long)"; yield return new CorsairLedStripRGBDevice(new CorsairLedStripRGBDeviceInfo(device, ledCount, offset, modelName), updateQueue); break; case CorsairChannelDeviceType.DRAM: yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, ledCount, offset, "DRAM"), updateQueue); break; default: Throw(new RGBDeviceException("Unknown Device-Type")); break; } offset += ledCount; } } } } /// public override void Dispose() { base.Dispose(); try { _CUESDK.CorsairDisconnect(); } catch { /* at least we tried */ } try { _CUESDK.UnloadCUESDK(); } catch { /* at least we tried */ } GC.SuppressFinalize(this); } #endregion }