Skip to content

Commit 55a648c

Browse files
logicallysyncedAytaç Kayadelenclaude
committed
Merge PR DarthAffe#339: Corsair DeviceConnection event implementation
Brings in Aytackydln's Corsair device-changed event implementation (DarthAffe#339) with conflicts resolved and a few regressions repaired. Conflict resolutions: - _CUESDK.cs: kept the stored SESSION_STATE_CHANGED_CALLBACK delegate field (required to prevent GC of the marshaled callback) while adopting the PR's typo fix (SesionState -> SessionState) and its early-return guard for Connecting/Timeout states. - CorsairDeviceProvider.cs: took the PR's refactored structure (CreateSingleChannelDevice / CreateCorsairDeviceChannel methods) and restored three regressions: * "iCUE LINK System Hub" 8-Led-Series rename to "RX Fan" * "LX Fan" workaround for invalid ChannelDeviceType when device.model == "iCUE LINK System Hub" && ledCount == 18 * Typo "8-Led-Series Fan Fan" -> "8-Led-Series Fan" Co-Authored-By: Aytaç Kayadelen <kayadelena@gmail.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 3cd2e14 + 4ff11a0 commit 55a648c

11 files changed

Lines changed: 391 additions & 181 deletions

RGB.NET.Core/Devices/AbstractRGBDeviceProvider.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Collections.ObjectModel;
44
using System.Linq;
5+
using System.Runtime.CompilerServices;
56

67
namespace RGB.NET.Core;
78

@@ -94,6 +95,15 @@ public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwE
9495
Reset();
9596
throw;
9697
}
98+
catch (RGBDeviceException)
99+
{
100+
Reset();
101+
if (throwExceptions)
102+
{
103+
throw;
104+
}
105+
return false;
106+
}
97107
catch (Exception ex)
98108
{
99109
Reset();
@@ -158,6 +168,7 @@ protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFil
158168
/// <param name="id">The id of the update trigger.</param>
159169
/// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param>
160170
/// <returns>The update trigger mapped to the specified id.</returns>
171+
[MethodImpl(MethodImplOptions.Synchronized)]
161172
protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null)
162173
{
163174
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);

RGB.NET.Core/Update/Devices/DeviceUpdateTrigger.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,15 @@ public virtual async void Stop()
141141

142142
UpdateTokenSource?.Cancel();
143143
if (UpdateTask != null)
144-
try { await UpdateTask.ConfigureAwait(false); }
144+
try
145+
{
146+
await UpdateTask.ConfigureAwait(false);
147+
UpdateTask?.Dispose();
148+
}
145149
catch (TaskCanceledException) { }
146150
catch (OperationCanceledException) { }
151+
catch (InvalidOperationException) { }
147152

148-
UpdateTask?.Dispose();
149153
UpdateTask = null;
150154
}
151155

RGB.NET.Devices.Corsair/CorsairDeviceProvider.cs

Lines changed: 204 additions & 163 deletions
Large diffs are not rendered by default.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace RGB.NET.Devices.Corsair;
2+
3+
internal enum CorsairEventId
4+
{
5+
Invalid = 0,
6+
DeviceConnectionStatusChangedEvent = 1,
7+
KeyEvent = 2,
8+
}

RGB.NET.Devices.Corsair/Generic/CorsairRGBDevice.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public abstract class CorsairRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceI
1414
{
1515
#region Properties & Fields
1616

17+
/// <summary>
18+
/// <inheritdoc cref="ICorsairRGBDevice"/>
19+
/// </summary>
20+
public string DeviceId => DeviceInfo.DeviceId;
21+
1722
/// <summary>
1823
/// Gets the mapping of <see cref="LedId"/> to <see cref="CorsairLedId"/> used to update the LEDs of this device.
1924
/// </summary>
@@ -29,14 +34,32 @@ public abstract class CorsairRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceI
2934
/// <param name="info">The generic information provided by CUE for the device.</param>
3035
/// <param name="mapping">The mapping <see cref="LedId"/> to <see cref="CorsairLedId"/> used to update the LEDs of this device.</param>
3136
/// <param name="updateQueue">The queue used to update this device.</param>
32-
protected CorsairRGBDevice(TDeviceInfo info, CorsairDeviceUpdateQueue updateQueue)
37+
protected CorsairRGBDevice(TDeviceInfo info, IUpdateQueue updateQueue)
3338
: base(info, updateQueue)
3439
{ }
3540

3641
#endregion
3742

3843
#region Methods
3944

45+
protected bool Equals(CorsairRGBDevice<TDeviceInfo> other)
46+
{
47+
return DeviceId == other.DeviceId;
48+
}
49+
50+
public override bool Equals(object? obj)
51+
{
52+
if (ReferenceEquals(null, obj)) return false;
53+
if (ReferenceEquals(this, obj)) return true;
54+
if (obj.GetType() != this.GetType()) return false;
55+
return Equals((CorsairRGBDevice<TDeviceInfo>)obj);
56+
}
57+
58+
public override int GetHashCode()
59+
{
60+
return DeviceId.GetHashCode();
61+
}
62+
4063
void ICorsairRGBDevice.Initialize() => InitializeLayout();
4164

4265
/// <summary>

RGB.NET.Devices.Corsair/Generic/CorsairRGBDeviceInfo.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System.Text.RegularExpressions;
1+
using System;
2+
using System.Security.Cryptography;
3+
using System.Text;
4+
using System.Text.RegularExpressions;
25
using RGB.NET.Core;
36
using RGB.NET.Devices.Corsair.Native;
47

@@ -33,7 +36,7 @@ public class CorsairRGBDeviceInfo : IRGBDeviceInfo
3336
/// Returns the unique ID provided by the Corsair-SDK.
3437
/// Returns string.Empty for Custom devices.
3538
/// </summary>
36-
public string DeviceId { get; }
39+
public string DeviceId { get; init; }
3740

3841
/// <inheritdoc />
3942
public object? LayoutMetadata { get; set; }
@@ -67,7 +70,14 @@ internal CorsairRGBDeviceInfo(RGBDeviceType deviceType, _CorsairDeviceInfo nativ
6770
this.LedCount = ledCount;
6871
this.LedOffset = ledOffset;
6972

70-
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
73+
if (nativeInfo.id == null) // this device is 99% unpluggable
74+
{
75+
DeviceName = IdGenerator.MakeUnique(typeof(CorsairDeviceProvider), Manufacturer + " " + Model);
76+
}
77+
else
78+
{
79+
DeviceName = Manufacturer + " " + Model + " #" + HashAndShorten(DeviceId);
80+
}
7181
}
7282

7383
/// <summary>
@@ -86,7 +96,34 @@ internal CorsairRGBDeviceInfo(RGBDeviceType deviceType, _CorsairDeviceInfo nativ
8696
this.LedCount = ledCount;
8797
this.LedOffset = ledOffset;
8898

89-
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
99+
if (nativeInfo.id == null)
100+
{
101+
DeviceName = IdGenerator.MakeUnique(typeof(CorsairDeviceProvider),Manufacturer + " " + Model) + " " + ledOffset;;
102+
}
103+
else
104+
{
105+
DeviceName = Manufacturer + " " + Model + " #" + HashAndShorten(DeviceId) + " " + ledOffset;
106+
}
107+
}
108+
109+
#endregion
110+
111+
#region Methods
112+
113+
private static string HashAndShorten(string input)
114+
{
115+
using SHA256 sha256Hash = SHA256.Create();
116+
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
117+
// Take the first 4 bytes of the hash
118+
byte[] shortenedBytes = new byte[4];
119+
Array.Copy(bytes, shortenedBytes, 4);
120+
// Convert the bytes to a string
121+
StringBuilder shortenedHash = new();
122+
foreach (byte b in shortenedBytes)
123+
{
124+
shortenedHash.Append(b.ToString("X2"));
125+
}
126+
return shortenedHash.ToString();
90127
}
91128

92129
#endregion

RGB.NET.Devices.Corsair/Generic/ICorsairRGBDevice.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ namespace RGB.NET.Devices.Corsair;
77
/// </summary>
88
public interface ICorsairRGBDevice : IRGBDevice
99
{
10+
internal string DeviceId { get; }
11+
1012
internal void Initialize();
1113
}

RGB.NET.Devices.Corsair/Native/_CUESDK.cs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace RGB.NET.Devices.Corsair.Native;
1313

1414
internal delegate void CorsairSessionStateChangedHandler(nint context, _CorsairSessionStateChanged eventData);
15+
internal delegate void CorsairEventHandler(nint context, _CorsairEvent corsairEvent);
1516

1617
// ReSharper disable once InconsistentNaming
1718
internal static unsafe class _CUESDK
@@ -50,14 +51,15 @@ internal static unsafe class _CUESDK
5051
// ReSharper disable once NotAccessedField.Local - This is important, the delegate can be collected if it's not stored!
5152
private static readonly CorsairSessionStateChangedHandler SESSION_STATE_CHANGED_CALLBACK;
5253

53-
internal static bool IsConnected => SesionState == CorsairSessionState.Connected;
54-
internal static CorsairSessionState SesionState { get; private set; }
54+
internal static bool IsConnected => SessionState == CorsairSessionState.Connected;
55+
internal static CorsairSessionState SessionState { get; private set; }
5556

5657
#endregion
5758

5859
#region Events
5960

6061
internal static event EventHandler<CorsairSessionState>? SessionStateChanged;
62+
internal static event EventHandler<_CorsairDeviceConnectionStatusChangedEvent>? DeviceConnectionEvent;
6163

6264
#endregion
6365

@@ -72,10 +74,45 @@ static _CUESDK()
7274

7375
#region Methods
7476

75-
private static void CorsairSessionStateChangedCallback(nint context, _CorsairSessionStateChanged eventdata)
77+
private static void CorsairSessionStateChangedCallback(nint context, _CorsairSessionStateChanged eventData)
7678
{
77-
SesionState = eventdata.state;
78-
SessionStateChanged?.Invoke(null, eventdata.state);
79+
SessionState = eventData.state;
80+
try
81+
{
82+
SessionStateChanged?.Invoke(null, eventData.state);
83+
}
84+
catch { /* dont let exception go to sdk */ }
85+
86+
switch (eventData.state)
87+
{
88+
case CorsairSessionState.Connected:
89+
_corsairSubscribeForEvents(CorsairEventCallback, 0);
90+
break;
91+
case CorsairSessionState.Closed:
92+
_corsairUnsubscribeForEvents();
93+
break;
94+
}
95+
}
96+
97+
private static void CorsairEventCallback(nint context, _CorsairEvent eventData)
98+
{
99+
if (eventData.id != CorsairEventId.DeviceConnectionStatusChangedEvent)
100+
{
101+
return;
102+
}
103+
104+
try
105+
{
106+
if (eventData.eventPointer == 0)
107+
{
108+
return;
109+
}
110+
111+
_CorsairDeviceConnectionStatusChangedEvent connectionStatusChangedEvent =
112+
Marshal.PtrToStructure<_CorsairDeviceConnectionStatusChangedEvent>(eventData.eventPointer)!;
113+
114+
DeviceConnectionEvent?.Invoke(null, connectionStatusChangedEvent);
115+
}catch { /* dont let exception go to sdk */ }
79116
}
80117

81118
#endregion
@@ -109,7 +146,7 @@ private static void LoadCUESDK()
109146
_corsairGetSessionDetails = (delegate* unmanaged[Cdecl]<nint, CorsairError>)LoadFunction("CorsairGetSessionDetails");
110147
_corsairDisconnect = (delegate* unmanaged[Cdecl]<CorsairError>)LoadFunction("CorsairDisconnect");
111148
_corsairGetDevices = (delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError>)LoadFunction("CorsairGetDevices");
112-
_corsairGetDeviceInfo = (delegate* unmanaged[Cdecl]<string, _CorsairDeviceInfo, CorsairError>)LoadFunction("CorsairGetDeviceInfo");
149+
_corsairGetDeviceInfo = (delegate* unmanaged[Cdecl]<string, ref _CorsairDeviceInfo, CorsairError>)LoadFunction("CorsairGetDeviceInfo");
113150
_corsairGetLedPositions = (delegate* unmanaged[Cdecl]<string, int, nint, out int, CorsairError>)LoadFunction("CorsairGetLedPositions");
114151
_corsairSetLedColors = (delegate* unmanaged[Cdecl]<string, int, nint, CorsairError>)LoadFunction("CorsairSetLedColors");
115152
_corsairSetLayerPriority = (delegate* unmanaged[Cdecl]<uint, CorsairError>)LoadFunction("CorsairSetLayerPriority");
@@ -118,6 +155,8 @@ private static void LoadCUESDK()
118155
_corsairReleaseControl = (delegate* unmanaged[Cdecl]<string, CorsairError>)LoadFunction("CorsairReleaseControl");
119156
_getDevicePropertyInfo = (delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, out CorsairDataType, out CorsairPropertyFlag, CorsairError>)LoadFunction("CorsairGetDevicePropertyInfo");
120157
_readDeviceProperty = (delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, nint, CorsairError>)LoadFunction("CorsairReadDeviceProperty");
158+
_corsairSubscribeForEvents = (delegate* unmanaged[Cdecl]<CorsairEventHandler, nint, CorsairError>)LoadFunction("CorsairSubscribeForEvents");
159+
_corsairUnsubscribeForEvents = (delegate* unmanaged[Cdecl]<CorsairError>)LoadFunction("CorsairSubscribeForEvents");
121160
}
122161

123162
private static nint LoadFunction(string function)
@@ -170,7 +209,7 @@ internal static void UnloadCUESDK()
170209
private static delegate* unmanaged[Cdecl]<nint, CorsairError> _corsairGetSessionDetails;
171210
private static delegate* unmanaged[Cdecl]<CorsairError> _corsairDisconnect;
172211
private static delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError> _corsairGetDevices;
173-
private static delegate* unmanaged[Cdecl]<string, _CorsairDeviceInfo, CorsairError> _corsairGetDeviceInfo;
212+
private static delegate* unmanaged[Cdecl]<string, ref _CorsairDeviceInfo, CorsairError> _corsairGetDeviceInfo;
174213
private static delegate* unmanaged[Cdecl]<string, int, nint, out int, CorsairError> _corsairGetLedPositions;
175214
private static delegate* unmanaged[Cdecl]<string, int, nint, CorsairError> _corsairSetLedColors;
176215
private static delegate* unmanaged[Cdecl]<uint, CorsairError> _corsairSetLayerPriority;
@@ -179,12 +218,15 @@ internal static void UnloadCUESDK()
179218
private static delegate* unmanaged[Cdecl]<string, CorsairError> _corsairReleaseControl;
180219
private static delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, out CorsairDataType, out CorsairPropertyFlag, CorsairError> _getDevicePropertyInfo;
181220
private static delegate* unmanaged[Cdecl]<string, CorsairDevicePropertyId, uint, nint, CorsairError> _readDeviceProperty;
221+
private static delegate* unmanaged[Cdecl]<CorsairEventHandler, nint, CorsairError> _corsairSubscribeForEvents;
222+
private static delegate* unmanaged[Cdecl]<CorsairError> _corsairUnsubscribeForEvents;
182223

183224
#endregion
184225

185226
internal static CorsairError CorsairConnect()
186227
{
187228
if (_corsairConnectPtr == null) throw new RGBDeviceException("The Corsair-SDK is not initialized.");
229+
if (SessionState is CorsairSessionState.Connecting or CorsairSessionState.Timeout) return CorsairError.Success;
188230
if (IsConnected) throw new RGBDeviceException("The Corsair-SDK is already connected.");
189231
return _corsairConnectPtr(SESSION_STATE_CHANGED_CALLBACK, 0);
190232
}
@@ -209,7 +251,6 @@ internal static CorsairError CorsairGetSessionDetails(out _CorsairSessionDetails
209251

210252
internal static CorsairError CorsairDisconnect()
211253
{
212-
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
213254
return _corsairDisconnect();
214255
}
215256

@@ -232,10 +273,12 @@ internal static CorsairError CorsairGetDevices(_CorsairDeviceFilter filter, out
232273
}
233274
}
234275

235-
internal static CorsairError CorsairGetDeviceInfo(string deviceId, _CorsairDeviceInfo deviceInfo)
276+
internal static CorsairError CorsairGetDeviceInfo(string deviceId, out _CorsairDeviceInfo deviceInfo)
236277
{
237278
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
238-
return _corsairGetDeviceInfo(deviceId, deviceInfo);
279+
280+
deviceInfo = new _CorsairDeviceInfo();
281+
return _corsairGetDeviceInfo(deviceId, ref deviceInfo);
239282
}
240283

241284
internal static CorsairError CorsairGetLedPositions(string deviceId, out _CorsairLedPosition[] ledPositions)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace RGB.NET.Devices.Corsair.Native;
4+
5+
[StructLayout(LayoutKind.Sequential)]
6+
public class _CorsairDeviceConnectionStatusChangedEvent
7+
{
8+
/// <summary>
9+
/// iCUE-SDK: null terminated Unicode string that contains unique device identifier
10+
/// </summary>
11+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = _CUESDK.CORSAIR_STRING_SIZE_M)]
12+
internal string? deviceId;
13+
14+
/// <summary>
15+
/// iCUE-SDK: true if connected, false if disconnected
16+
/// </summary>
17+
[MarshalAs(UnmanagedType.U1)]
18+
internal bool isConnected;
19+
}

RGB.NET.Devices.Corsair/Native/_CorsairDeviceInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace RGB.NET.Devices.Corsair.Native;
1212
/// iCUE-SDK: contains information about device
1313
/// </summary>
1414
[StructLayout(LayoutKind.Sequential)]
15-
internal sealed class _CorsairDeviceInfo
15+
internal struct _CorsairDeviceInfo
1616
{
1717
/// <summary>
1818
/// iCUE-SDK: enum describing device type

0 commit comments

Comments
 (0)