diff --git a/RGB.NET.Core/Devices/AbstractRGBDeviceProvider.cs b/RGB.NET.Core/Devices/AbstractRGBDeviceProvider.cs index ff2f7df6..717ac8ac 100644 --- a/RGB.NET.Core/Devices/AbstractRGBDeviceProvider.cs +++ b/RGB.NET.Core/Devices/AbstractRGBDeviceProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Runtime.CompilerServices; namespace RGB.NET.Core; @@ -94,6 +95,15 @@ public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwE Reset(); throw; } + catch (RGBDeviceException) + { + Reset(); + if (throwExceptions) + { + throw; + } + return false; + } catch (Exception ex) { Reset(); @@ -158,6 +168,7 @@ protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFil /// <param name="id">The id of the update trigger.</param> /// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param> /// <returns>The update trigger mapped to the specified id.</returns> + [MethodImpl(MethodImplOptions.Synchronized)] protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); diff --git a/RGB.NET.Core/Ids/IdGenerator.cs b/RGB.NET.Core/Ids/IdGenerator.cs index f9bf2177..435cfcff 100644 --- a/RGB.NET.Core/Ids/IdGenerator.cs +++ b/RGB.NET.Core/Ids/IdGenerator.cs @@ -13,8 +13,8 @@ public static class IdGenerator // ReSharper disable InconsistentNaming private static readonly HashSet<string> _registeredIds = []; - private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = []; - private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = []; + private static readonly Dictionary<object, Dictionary<string, string>> _idMappings = []; + private static readonly Dictionary<object, Dictionary<string, int>> _counter = []; // ReSharper restore InconsistentNaming #endregion @@ -29,7 +29,7 @@ public static class IdGenerator [MethodImpl(MethodImplOptions.NoInlining)] public static string MakeUnique(string id) => MakeUnique(Assembly.GetCallingAssembly(), id); - internal static string MakeUnique(Assembly callingAssembly, string id) + public static string MakeUnique(object callingAssembly, string id) { if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping)) { @@ -63,7 +63,7 @@ internal static string MakeUnique(Assembly callingAssembly, string id) [MethodImpl(MethodImplOptions.NoInlining)] public static void ResetCounter() => ResetCounter(Assembly.GetCallingAssembly()); - internal static void ResetCounter(Assembly callingAssembly) + public static void ResetCounter(object callingAssembly) { if (_counter.TryGetValue(callingAssembly, out Dictionary<string, int>? counter)) counter.Clear(); diff --git a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs index a45ae6ca..2957fb85 100644 --- a/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs +++ b/RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs @@ -141,11 +141,15 @@ public virtual async void Stop() UpdateTokenSource?.Cancel(); if (UpdateTask != null) - try { await UpdateTask.ConfigureAwait(false); } + try + { + await UpdateTask.ConfigureAwait(false); + UpdateTask?.Dispose(); + } catch (TaskCanceledException) { } catch (OperationCanceledException) { } + catch (InvalidOperationException) { } - UpdateTask?.Dispose(); UpdateTask = null; } diff --git a/RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs b/RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs index 4dba9ea8..e4d13adf 100644 --- a/RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs +++ b/RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using RGB.NET.Core; using RGB.NET.Devices.Corsair.Native; @@ -103,6 +104,7 @@ public CorsairDeviceProvider() protected override void InitializeSDK() { + _CUESDK.SessionStateChanged -= CancelOnConnect; _CUESDK.Reload(); using ManualResetEventSlim waitEvent = new(false); @@ -124,13 +126,17 @@ void OnInitializeSessionStateChanged(object? sender, CorsairSessionState state) 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.SessionStateChanged += CancelOnConnect; //We can't cancel connection. All we can do is disconnect after connection + Throw(new RGBDeviceException($"Failed to initialized Corsair-SDK. (Timeout - Current connection state: {_CUESDK.SessionState})")); + } _CUESDK.CorsairGetSessionDetails(out _CorsairSessionDetails? details); if (errorCode != CorsairError.Success) Throw(new RGBDeviceException($"Failed to get session details. (ErrorCode: {errorCode})")); SessionDetails = new CorsairSessionDetails(details!); + _CUESDK.DeviceConnectionEvent += OnDeviceConnectionEvent; } finally { @@ -138,6 +144,39 @@ void OnInitializeSessionStateChanged(object? sender, CorsairSessionState state) } } + private void CancelOnConnect(object? sender, CorsairSessionState e) + { + if (e != CorsairSessionState.Connected) return; + _CUESDK.SessionStateChanged -= CancelOnConnect; + _CUESDK.CorsairDisconnect(); + } + + private void OnDeviceConnectionEvent(object? sender, _CorsairDeviceConnectionStatusChangedEvent connectionStatusChangedEvent) + { + string? deviceId = connectionStatusChangedEvent.deviceId; + if (string.IsNullOrWhiteSpace(deviceId)) return; + if (connectionStatusChangedEvent.isConnected) + { + _CUESDK.CorsairGetDeviceInfo(deviceId, out _CorsairDeviceInfo deviceInfo); + IDeviceUpdateTrigger deviceUpdateTrigger = GetUpdateTrigger(); + IEnumerable<ICorsairRGBDevice> device = LoadDevice(deviceInfo, deviceUpdateTrigger); + foreach (ICorsairRGBDevice corsairRGBDevice in device) + { + corsairRGBDevice.Initialize(); + if (AddDevice(corsairRGBDevice)) + { + deviceUpdateTrigger.Start(); + } + } + } + else + { + IRGBDevice? removedDevice = Devices.FirstOrDefault(device => ((ICorsairRGBDevice)device).DeviceId == deviceId); + if (removedDevice == null) return; + RemoveDevice(removedDevice); //TODO disposing the device disposes device queue! + } + } + private void OnSessionStateChanged(object? sender, CorsairSessionState state) => SessionState = state; /// <inheritdoc /> @@ -156,170 +195,171 @@ private IEnumerable<ICorsairRGBDevice> LoadCorsairDevices() if (error != CorsairError.Success) Throw(new RGBDeviceException($"Failed to load devices. (ErrorCode: {error})")); - foreach (_CorsairDeviceInfo device in devices) + return devices.SelectMany(LoadDevice); + } + + private IEnumerable<ICorsairRGBDevice> LoadDevice(_CorsairDeviceInfo device) + { + return LoadDevice(device, GetUpdateTrigger()); + } + + private IEnumerable<ICorsairRGBDevice> LoadDevice(_CorsairDeviceInfo device, IDeviceUpdateTrigger updateTrigger) + { + if (string.IsNullOrWhiteSpace(device.id)) yield break; + + // sometimes it is okay to fail :) (can cause problems with reconnections) + _CUESDK.CorsairRequestControl(device.id, ExclusiveAccess ? CorsairAccessLevel.ExclusiveLightingControl : CorsairAccessLevel.Shared); + + CorsairDeviceUpdateQueue updateQueue = new(updateTrigger, device); + + int channelLedCount = 0; + for (int i = 0; i < device.channelCount; i++) { - if (string.IsNullOrWhiteSpace(device.id)) continue; + Console.WriteLine($"Channel {i}/{device.channelCount}"); + channelLedCount += _CUESDK.ReadDevicePropertySimpleInt32(device.id!, CorsairDevicePropertyId.ChannelLedCount, (uint)i); + } - 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})")); + int deviceLedCount = device.ledCount - channelLedCount; + if (deviceLedCount > 0) + { + ICorsairRGBDevice singleChannelDevice = CreateSingleChannelDevice(device, deviceLedCount, updateQueue); + yield return singleChannelDevice; + } - 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 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 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[] ledCounts = + _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceLedCountArray, (uint)i); + int[] deviceTypes = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceTypeArray, (uint)i); - int offset = deviceLedCount; - for (int i = 0; i < device.channelCount; i++) + for (int j = 0; j < deviceCount; j++) { - 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 + CorsairChannelDeviceType deviceType = (CorsairChannelDeviceType)deviceTypes[j]; + int ledCount = ledCounts[j]; - int[] ledCounts = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceLedCountArray, (uint)i); - int[] deviceTypes = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceTypeArray, (uint)i); + yield return CreateCorsairDeviceChannel(device, deviceType, ledCount, offset, updateQueue); - 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: - 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; - } + offset += ledCount; } } } + private static ICorsairRGBDevice CreateSingleChannelDevice(_CorsairDeviceInfo device, int deviceLedCount, CorsairDeviceUpdateQueue updateQueue) + { + return device.type switch + { + CorsairDeviceType.Keyboard => + new CorsairKeyboardRGBDevice(new CorsairKeyboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Mouse => + new CorsairMouseRGBDevice(new CorsairMouseRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Headset => + new CorsairHeadsetRGBDevice(new CorsairHeadsetRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Mousemat => + new CorsairMousepadRGBDevice(new CorsairMousepadRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.HeadsetStand => + new CorsairHeadsetStandRGBDevice(new CorsairHeadsetStandRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.MemoryModule => + new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Motherboard => + new CorsairMainboardRGBDevice(new CorsairMainboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.GraphicsCard => + new CorsairGraphicsCardRGBDevice(new CorsairGraphicsCardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Touchbar => + new CorsairTouchbarRGBDevice(new CorsairTouchbarRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.Cooler => + new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.GameController => + new CorsairGameControllerRGBDevice(new CorsairGameControllerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + CorsairDeviceType.FanLedController or + CorsairDeviceType.LedController or + CorsairDeviceType.Unknown => + new CorsairUnknownRGBDevice(new CorsairUnknownRGBDeviceInfo(device, deviceLedCount, 0), updateQueue), + + _ => throw new RGBDeviceException("Unknown Device-Type") + }; + } + + private static ICorsairRGBDevice CreateCorsairDeviceChannel(_CorsairDeviceInfo device, CorsairChannelDeviceType deviceType, + int ledCount, int offset, CorsairDeviceUpdateQueue updateQueue) + { + return deviceType switch + { + CorsairChannelDeviceType.FanHD => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "HD Fan"), updateQueue), + + CorsairChannelDeviceType.FanSP => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "SP Fan"), updateQueue), + + CorsairChannelDeviceType.FanLL => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "LL Fan"), updateQueue), + + CorsairChannelDeviceType.FanML => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "ML Fan"), updateQueue), + + CorsairChannelDeviceType.FanQL => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QL Fan"), updateQueue), + + CorsairChannelDeviceType.FanQX => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QX Fan"), updateQueue), + + CorsairChannelDeviceType.EightLedSeriesFan => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "8-Led-Series Fan Fan"), updateQueue), + + CorsairChannelDeviceType.DAP => + new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "DAP Fan"), updateQueue), + + CorsairChannelDeviceType.Pump => + new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Pump"), updateQueue), + + CorsairChannelDeviceType.WaterBlock => + new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Water Block"), updateQueue), + + CorsairChannelDeviceType.Strip => CreateLedStripDevice(device, ledCount, offset, updateQueue), + + CorsairChannelDeviceType.DRAM => + new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, ledCount, offset, "DRAM"), updateQueue), + + _ => throw new RGBDeviceException("Unknown Device-Type") + }; + } + + private static CorsairLedStripRGBDevice CreateLedStripDevice(_CorsairDeviceInfo device, int ledCount, int offset, CorsairDeviceUpdateQueue updateQueue) + { + 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)"; + + CorsairLedStripRGBDevice ledStripDevice = new(new CorsairLedStripRGBDeviceInfo(device, ledCount, offset, modelName), updateQueue); + return ledStripDevice; + } + /// <inheritdoc /> protected override void Dispose(bool disposing) { @@ -333,6 +373,8 @@ protected override void Dispose(bool disposing) try { _CUESDK.UnloadCUESDK(); } catch { /* at least we tried */ } + IdGenerator.ResetCounter(typeof(CorsairDeviceProvider)); + _instance = null; } } diff --git a/RGB.NET.Devices.Corsair/Enum/CorsairEventId.cs b/RGB.NET.Devices.Corsair/Enum/CorsairEventId.cs new file mode 100644 index 00000000..c1b8988b --- /dev/null +++ b/RGB.NET.Devices.Corsair/Enum/CorsairEventId.cs @@ -0,0 +1,8 @@ +namespace RGB.NET.Devices.Corsair; + +internal enum CorsairEventId +{ + Invalid = 0, + DeviceConnectionStatusChangedEvent = 1, + KeyEvent = 2, +} diff --git a/RGB.NET.Devices.Corsair/Generic/CorsairRGBDevice.cs b/RGB.NET.Devices.Corsair/Generic/CorsairRGBDevice.cs index 9fcbca62..3faa86ff 100644 --- a/RGB.NET.Devices.Corsair/Generic/CorsairRGBDevice.cs +++ b/RGB.NET.Devices.Corsair/Generic/CorsairRGBDevice.cs @@ -14,6 +14,11 @@ public abstract class CorsairRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceI { #region Properties & Fields + /// <summary> + /// <inheritdoc cref="ICorsairRGBDevice"/> + /// </summary> + public string DeviceId => DeviceInfo.DeviceId; + /// <summary> /// Gets the mapping of <see cref="LedId"/> to <see cref="CorsairLedId"/> used to update the LEDs of this device. /// </summary> @@ -29,7 +34,7 @@ public abstract class CorsairRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceI /// <param name="info">The generic information provided by CUE for the device.</param> /// <param name="mapping">The mapping <see cref="LedId"/> to <see cref="CorsairLedId"/> used to update the LEDs of this device.</param> /// <param name="updateQueue">The queue used to update this device.</param> - protected CorsairRGBDevice(TDeviceInfo info, CorsairDeviceUpdateQueue updateQueue) + protected CorsairRGBDevice(TDeviceInfo info, IUpdateQueue updateQueue) : base(info, updateQueue) { } @@ -37,6 +42,24 @@ protected CorsairRGBDevice(TDeviceInfo info, CorsairDeviceUpdateQueue updateQueu #region Methods + protected bool Equals(CorsairRGBDevice<TDeviceInfo> other) + { + return DeviceId == other.DeviceId; + } + + 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((CorsairRGBDevice<TDeviceInfo>)obj); + } + + public override int GetHashCode() + { + return DeviceId.GetHashCode(); + } + void ICorsairRGBDevice.Initialize() => InitializeLayout(); /// <summary> diff --git a/RGB.NET.Devices.Corsair/Generic/CorsairRGBDeviceInfo.cs b/RGB.NET.Devices.Corsair/Generic/CorsairRGBDeviceInfo.cs index 6137b95a..c7560bbb 100644 --- a/RGB.NET.Devices.Corsair/Generic/CorsairRGBDeviceInfo.cs +++ b/RGB.NET.Devices.Corsair/Generic/CorsairRGBDeviceInfo.cs @@ -1,4 +1,7 @@ -using System.Text.RegularExpressions; +using System; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; using RGB.NET.Core; using RGB.NET.Devices.Corsair.Native; @@ -33,7 +36,7 @@ public class CorsairRGBDeviceInfo : IRGBDeviceInfo /// Returns the unique ID provided by the Corsair-SDK. /// Returns string.Empty for Custom devices. /// </summary> - public string DeviceId { get; } + public string DeviceId { get; init; } /// <inheritdoc /> public object? LayoutMetadata { get; set; } @@ -67,7 +70,14 @@ internal CorsairRGBDeviceInfo(RGBDeviceType deviceType, _CorsairDeviceInfo nativ this.LedCount = ledCount; this.LedOffset = ledOffset; - DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model); + if (nativeInfo.id == null) // this device is 99% unpluggable + { + DeviceName = IdGenerator.MakeUnique(typeof(CorsairDeviceProvider), Manufacturer + " " + Model); + } + else + { + DeviceName = Manufacturer + " " + Model + " #" + HashAndShorten(DeviceId); + } } /// <summary> @@ -86,7 +96,34 @@ internal CorsairRGBDeviceInfo(RGBDeviceType deviceType, _CorsairDeviceInfo nativ this.LedCount = ledCount; this.LedOffset = ledOffset; - DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model); + if (nativeInfo.id == null) + { + DeviceName = IdGenerator.MakeUnique(typeof(CorsairDeviceProvider),Manufacturer + " " + Model) + " " + ledOffset;; + } + else + { + DeviceName = Manufacturer + " " + Model + " #" + HashAndShorten(DeviceId) + " " + ledOffset; + } + } + + #endregion + + #region Methods + + private static string HashAndShorten(string input) + { + using SHA256 sha256Hash = SHA256.Create(); + byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input)); + // Take the first 4 bytes of the hash + byte[] shortenedBytes = new byte[4]; + Array.Copy(bytes, shortenedBytes, 4); + // Convert the bytes to a string + StringBuilder shortenedHash = new(); + foreach (byte b in shortenedBytes) + { + shortenedHash.Append(b.ToString("X2")); + } + return shortenedHash.ToString(); } #endregion diff --git a/RGB.NET.Devices.Corsair/Generic/ICorsairRGBDevice.cs b/RGB.NET.Devices.Corsair/Generic/ICorsairRGBDevice.cs index 5e49da25..9bfde69a 100644 --- a/RGB.NET.Devices.Corsair/Generic/ICorsairRGBDevice.cs +++ b/RGB.NET.Devices.Corsair/Generic/ICorsairRGBDevice.cs @@ -7,5 +7,7 @@ namespace RGB.NET.Devices.Corsair; /// </summary> public interface ICorsairRGBDevice : IRGBDevice { + internal string DeviceId { get; } + internal void Initialize(); } \ No newline at end of file diff --git a/RGB.NET.Devices.Corsair/Native/_CUESDK.cs b/RGB.NET.Devices.Corsair/Native/_CUESDK.cs index 00d9f922..70a8b4d4 100644 --- a/RGB.NET.Devices.Corsair/Native/_CUESDK.cs +++ b/RGB.NET.Devices.Corsair/Native/_CUESDK.cs @@ -12,6 +12,7 @@ namespace RGB.NET.Devices.Corsair.Native; internal delegate void CorsairSessionStateChangedHandler(nint context, _CorsairSessionStateChanged eventData); +internal delegate void CorsairEventHandler(nint context, _CorsairEvent corsairEvent); // ReSharper disable once InconsistentNaming internal static unsafe class _CUESDK @@ -47,23 +48,59 @@ internal static unsafe class _CUESDK #region Properties & Fields - internal static bool IsConnected => SesionState == CorsairSessionState.Connected; - internal static CorsairSessionState SesionState { get; private set; } + internal static bool IsConnected => SessionState == CorsairSessionState.Connected; + internal static CorsairSessionState SessionState { get; private set; } #endregion #region Events internal static event EventHandler<CorsairSessionState>? SessionStateChanged; + internal static event EventHandler<_CorsairDeviceConnectionStatusChangedEvent>? DeviceConnectionEvent; #endregion #region Methods - private static void CorsairSessionStateChangedCallback(nint context, _CorsairSessionStateChanged eventdata) + private static void CorsairSessionStateChangedCallback(nint context, _CorsairSessionStateChanged eventData) { - SesionState = eventdata.state; - SessionStateChanged?.Invoke(null, eventdata.state); + SessionState = eventData.state; + try + { + SessionStateChanged?.Invoke(null, eventData.state); + } + catch { /* dont let exception go to sdk */ } + + switch (eventData.state) + { + case CorsairSessionState.Connected: + _corsairSubscribeForEvents(CorsairEventCallback, 0); + break; + case CorsairSessionState.Closed: + _corsairUnsubscribeForEvents(); + break; + } + } + + private static void CorsairEventCallback(nint context, _CorsairEvent eventData) + { + if (eventData.id != CorsairEventId.DeviceConnectionStatusChangedEvent) + { + return; + } + + try + { + if (eventData.eventPointer == 0) + { + return; + } + + _CorsairDeviceConnectionStatusChangedEvent connectionStatusChangedEvent = + Marshal.PtrToStructure<_CorsairDeviceConnectionStatusChangedEvent>(eventData.eventPointer)!; + + DeviceConnectionEvent?.Invoke(null, connectionStatusChangedEvent); + }catch { /* dont let exception go to sdk */ } } #endregion @@ -97,7 +134,7 @@ private static void LoadCUESDK() _corsairGetSessionDetails = (delegate* unmanaged[Cdecl]<nint, CorsairError>)LoadFunction("CorsairGetSessionDetails"); _corsairDisconnect = (delegate* unmanaged[Cdecl]<CorsairError>)LoadFunction("CorsairDisconnect"); _corsairGetDevices = (delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError>)LoadFunction("CorsairGetDevices"); - _corsairGetDeviceInfo = (delegate* unmanaged[Cdecl]<string, _CorsairDeviceInfo, CorsairError>)LoadFunction("CorsairGetDeviceInfo"); + _corsairGetDeviceInfo = (delegate* unmanaged[Cdecl]<string, ref _CorsairDeviceInfo, CorsairError>)LoadFunction("CorsairGetDeviceInfo"); _corsairGetLedPositions = (delegate* unmanaged[Cdecl]<string, int, nint, out int, CorsairError>)LoadFunction("CorsairGetLedPositions"); _corsairSetLedColors = (delegate* unmanaged[Cdecl]<string, int, nint, CorsairError>)LoadFunction("CorsairSetLedColors"); _corsairSetLayerPriority = (delegate* unmanaged[Cdecl]<uint, CorsairError>)LoadFunction("CorsairSetLayerPriority"); @@ -106,6 +143,8 @@ private static void LoadCUESDK() _corsairReleaseControl = (delegate* unmanaged[Cdecl]<string, CorsairError>)LoadFunction("CorsairReleaseControl"); _getDevicePropertyInfo = (delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, out CorsairDataType, out CorsairPropertyFlag, CorsairError>)LoadFunction("CorsairGetDevicePropertyInfo"); _readDeviceProperty = (delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, nint, CorsairError>)LoadFunction("CorsairReadDeviceProperty"); + _corsairSubscribeForEvents = (delegate* unmanaged[Cdecl]<CorsairEventHandler, nint, CorsairError>)LoadFunction("CorsairSubscribeForEvents"); + _corsairUnsubscribeForEvents = (delegate* unmanaged[Cdecl]<CorsairError>)LoadFunction("CorsairSubscribeForEvents"); } private static nint LoadFunction(string function) @@ -158,7 +197,7 @@ internal static void UnloadCUESDK() private static delegate* unmanaged[Cdecl]<nint, CorsairError> _corsairGetSessionDetails; private static delegate* unmanaged[Cdecl]<CorsairError> _corsairDisconnect; private static delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError> _corsairGetDevices; - private static delegate* unmanaged[Cdecl]<string, _CorsairDeviceInfo, CorsairError> _corsairGetDeviceInfo; + private static delegate* unmanaged[Cdecl]<string, ref _CorsairDeviceInfo, CorsairError> _corsairGetDeviceInfo; private static delegate* unmanaged[Cdecl]<string, int, nint, out int, CorsairError> _corsairGetLedPositions; private static delegate* unmanaged[Cdecl]<string, int, nint, CorsairError> _corsairSetLedColors; private static delegate* unmanaged[Cdecl]<uint, CorsairError> _corsairSetLayerPriority; @@ -167,13 +206,18 @@ internal static void UnloadCUESDK() private static delegate* unmanaged[Cdecl]<string, CorsairError> _corsairReleaseControl; private static delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, out CorsairDataType, out CorsairPropertyFlag, CorsairError> _getDevicePropertyInfo; private static delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, nint, CorsairError> _readDeviceProperty; + private static delegate* unmanaged[Cdecl]<CorsairEventHandler, nint, CorsairError> _corsairSubscribeForEvents; + private static delegate* unmanaged[Cdecl]<CorsairError> _corsairUnsubscribeForEvents; #endregion internal static CorsairError CorsairConnect() { if (_corsairConnectPtr == null) throw new RGBDeviceException("The Corsair-SDK is not initialized."); - if (IsConnected) throw new RGBDeviceException("The Corsair-SDK is already connected."); + if (SessionState is CorsairSessionState.Connecting or CorsairSessionState.Timeout) + { + return CorsairError.Success; + } return _corsairConnectPtr(CorsairSessionStateChangedCallback, 0); } @@ -197,7 +241,6 @@ internal static CorsairError CorsairGetSessionDetails(out _CorsairSessionDetails internal static CorsairError CorsairDisconnect() { - if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected."); return _corsairDisconnect(); } @@ -220,10 +263,12 @@ internal static CorsairError CorsairGetDevices(_CorsairDeviceFilter filter, out } } - internal static CorsairError CorsairGetDeviceInfo(string deviceId, _CorsairDeviceInfo deviceInfo) + internal static CorsairError CorsairGetDeviceInfo(string deviceId, out _CorsairDeviceInfo deviceInfo) { if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected."); - return _corsairGetDeviceInfo(deviceId, deviceInfo); + + deviceInfo = new _CorsairDeviceInfo(); + return _corsairGetDeviceInfo(deviceId, ref deviceInfo); } internal static CorsairError CorsairGetLedPositions(string deviceId, out _CorsairLedPosition[] ledPositions) diff --git a/RGB.NET.Devices.Corsair/Native/_CorsairDeviceConnectionStatusChangedEvent.cs b/RGB.NET.Devices.Corsair/Native/_CorsairDeviceConnectionStatusChangedEvent.cs new file mode 100644 index 00000000..bc71d914 --- /dev/null +++ b/RGB.NET.Devices.Corsair/Native/_CorsairDeviceConnectionStatusChangedEvent.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace RGB.NET.Devices.Corsair.Native; + +[StructLayout(LayoutKind.Sequential)] +public class _CorsairDeviceConnectionStatusChangedEvent +{ + /// <summary> + /// iCUE-SDK: null terminated Unicode string that contains unique device identifier + /// </summary> + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _CUESDK.CORSAIR_STRING_SIZE_M)] + internal string? deviceId; + + /// <summary> + /// iCUE-SDK: true if connected, false if disconnected + /// </summary> + [MarshalAs(UnmanagedType.U1)] + internal bool isConnected; +} diff --git a/RGB.NET.Devices.Corsair/Native/_CorsairDeviceInfo.cs b/RGB.NET.Devices.Corsair/Native/_CorsairDeviceInfo.cs index b4c8813d..2fc6e31d 100644 --- a/RGB.NET.Devices.Corsair/Native/_CorsairDeviceInfo.cs +++ b/RGB.NET.Devices.Corsair/Native/_CorsairDeviceInfo.cs @@ -12,7 +12,7 @@ namespace RGB.NET.Devices.Corsair.Native; /// iCUE-SDK: contains information about device /// </summary> [StructLayout(LayoutKind.Sequential)] -internal sealed class _CorsairDeviceInfo +internal struct _CorsairDeviceInfo { /// <summary> /// iCUE-SDK: enum describing device type diff --git a/RGB.NET.Devices.Corsair/Native/_CorsairEvent.cs b/RGB.NET.Devices.Corsair/Native/_CorsairEvent.cs new file mode 100644 index 00000000..6f5285ad --- /dev/null +++ b/RGB.NET.Devices.Corsair/Native/_CorsairEvent.cs @@ -0,0 +1,22 @@ +using System.Runtime.InteropServices; + +namespace RGB.NET.Devices.Corsair.Native; + +// ReSharper disable once InconsistentNaming +/// <summary> +/// iCUE-SDK: contains information about event id and event data +/// </summary> +[StructLayout(LayoutKind.Sequential)] +internal sealed class _CorsairEvent +{ + /// <summary> + /// iCUE-SDK: event identifier + /// </summary> + internal CorsairEventId id; + + /// <summary> + /// Points to <see cref="_CorsairDeviceConnectionStatusChangedEvent"/> if _CorsairEvent's id is CEI_DeviceConnectionStatusChangedEvent, + /// points to <see cref="_CorsairKeyEvent"/> if _CorsairEvent's id is CEI_KeyEvent + /// </summary> + internal nint eventPointer; +}