#pragma warning disable IDE1006 // Naming Styles
// ReSharper disable UnusedMethodReturnValue.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using RGB.NET.Core;
namespace RGB.NET.Devices.Corsair.Native;
internal delegate void CorsairSessionStateChangedHandler(nint context, _CorsairSessionStateChanged eventData);
// ReSharper disable once InconsistentNaming
internal static unsafe class _CUESDK
{
#region Constants
///
/// iCUE-SDK: small string length
///
internal const int CORSAIR_STRING_SIZE_S = 64;
///
/// iCUE-SDK: medium string length
///
internal const int CORSAIR_STRING_SIZE_M = 128;
///
/// iCUE-SDK: maximum level of layer’s priority that can be used in CorsairSetLayerPriority
///
internal const int CORSAIR_LAYER_PRIORITY_MAX = 255;
///
/// iCUE-SDK: maximum number of devices to be discovered
///
internal const int CORSAIR_DEVICE_COUNT_MAX = 64;
///
/// iCUE-SDK: maximum number of LEDs controlled by device
///
internal const int CORSAIR_DEVICE_LEDCOUNT_MAX = 512;
#endregion
#region Properties & Fields
// ReSharper disable once NotAccessedField.Local - This is important, the delegate can be collected if it's not stored!
private static readonly CorsairSessionStateChangedHandler SESSION_STATE_CHANGED_CALLBACK;
internal static bool IsConnected => SesionState == CorsairSessionState.Connected;
internal static CorsairSessionState SesionState { get; private set; }
#endregion
#region Events
internal static event EventHandler? SessionStateChanged;
#endregion
#region Constructors
static _CUESDK()
{
SESSION_STATE_CHANGED_CALLBACK = CorsairSessionStateChangedCallback;
}
#endregion
#region Methods
private static void CorsairSessionStateChangedCallback(nint context, _CorsairSessionStateChanged eventdata)
{
SesionState = eventdata.state;
SessionStateChanged?.Invoke(null, eventdata.state);
}
#endregion
#region Libary Management
private static nint _handle = 0;
///
/// Reloads the SDK.
///
internal static void Reload()
{
UnloadCUESDK();
LoadCUESDK();
}
private static void LoadCUESDK()
{
if (_handle != 0) return;
List possiblePathList = GetPossibleLibraryPaths().ToList();
string? dllPath = possiblePathList.FirstOrDefault(File.Exists);
if (dllPath == null) throw new RGBDeviceException($"Can't find the CUE-SDK at one of the expected locations:\r\n '{string.Join("\r\n", possiblePathList.Select(Path.GetFullPath))}'");
if (!NativeLibrary.TryLoad(dllPath, out _handle))
throw new RGBDeviceException($"Corsair LoadLibrary failed with error code {Marshal.GetLastPInvokeError()}");
_corsairConnectPtr = (delegate* unmanaged[Cdecl])LoadFunction("CorsairConnect");
_corsairGetSessionDetails = (delegate* unmanaged[Cdecl])LoadFunction("CorsairGetSessionDetails");
_corsairDisconnect = (delegate* unmanaged[Cdecl])LoadFunction("CorsairDisconnect");
_corsairGetDevices = (delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError>)LoadFunction("CorsairGetDevices");
_corsairGetDeviceInfo = (delegate* unmanaged[Cdecl])LoadFunction("CorsairGetDeviceInfo");
_corsairGetLedPositions = (delegate* unmanaged[Cdecl])LoadFunction("CorsairGetLedPositions");
_corsairSetLedColors = (delegate* unmanaged[Cdecl])LoadFunction("CorsairSetLedColors");
_corsairSetLayerPriority = (delegate* unmanaged[Cdecl])LoadFunction("CorsairSetLayerPriority");
_corsairGetLedLuidForKeyName = (delegate* unmanaged[Cdecl])LoadFunction("CorsairGetLedLuidForKeyName");
_corsairRequestControl = (delegate* unmanaged[Cdecl])LoadFunction("CorsairRequestControl");
_corsairReleaseControl = (delegate* unmanaged[Cdecl])LoadFunction("CorsairReleaseControl");
_getDevicePropertyInfo = (delegate* unmanaged[Cdecl])LoadFunction("CorsairGetDevicePropertyInfo");
_readDeviceProperty = (delegate* unmanaged[Cdecl])LoadFunction("CorsairReadDeviceProperty");
}
private static nint LoadFunction(string function)
{
if (!NativeLibrary.TryGetExport(_handle, function, out nint ptr)) throw new RGBDeviceException($"Failed to load Corsair function '{function}'");
return ptr;
}
private static IEnumerable GetPossibleLibraryPaths()
{
IEnumerable possibleLibraryPaths;
if (OperatingSystem.IsWindows())
possibleLibraryPaths = Environment.Is64BitProcess ? CorsairDeviceProvider.PossibleX64NativePaths : CorsairDeviceProvider.PossibleX86NativePaths;
else
possibleLibraryPaths = [];
return possibleLibraryPaths.Select(Environment.ExpandEnvironmentVariables);
}
internal static void UnloadCUESDK()
{
if (_handle == 0) return;
_corsairConnectPtr = null;
_corsairGetSessionDetails = null;
_corsairDisconnect = null;
_corsairGetDevices = null;
_corsairGetDeviceInfo = null;
_corsairGetLedPositions = null;
_corsairSetLedColors = null;
_corsairSetLayerPriority = null;
_corsairGetLedLuidForKeyName = null;
_corsairRequestControl = null;
_corsairReleaseControl = null;
_getDevicePropertyInfo = null;
_readDeviceProperty = null;
NativeLibrary.Free(_handle);
_handle = 0;
}
#endregion
#region SDK-METHODS
#region Pointers
private static delegate* unmanaged[Cdecl] _corsairConnectPtr;
private static delegate* unmanaged[Cdecl] _corsairGetSessionDetails;
private static delegate* unmanaged[Cdecl] _corsairDisconnect;
private static delegate* unmanaged[Cdecl]<_CorsairDeviceFilter, int, nint, out int, CorsairError> _corsairGetDevices;
private static delegate* unmanaged[Cdecl] _corsairGetDeviceInfo;
private static delegate* unmanaged[Cdecl] _corsairGetLedPositions;
private static delegate* unmanaged[Cdecl] _corsairSetLedColors;
private static delegate* unmanaged[Cdecl] _corsairSetLayerPriority;
private static delegate* unmanaged[Cdecl] _corsairGetLedLuidForKeyName;
private static delegate* unmanaged[Cdecl] _corsairRequestControl;
private static delegate* unmanaged[Cdecl] _corsairReleaseControl;
private static delegate* unmanaged[Cdecl] _getDevicePropertyInfo;
private static delegate* unmanaged[Cdecl] _readDeviceProperty;
#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.");
return _corsairConnectPtr(SESSION_STATE_CHANGED_CALLBACK, 0);
}
internal static CorsairError CorsairGetSessionDetails(out _CorsairSessionDetails? details)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
nint sessionDetailPtr = Marshal.AllocHGlobal(Marshal.SizeOf<_CorsairSessionDetails>());
try
{
CorsairError error = _corsairGetSessionDetails(sessionDetailPtr);
details = Marshal.PtrToStructure<_CorsairSessionDetails>(sessionDetailPtr);
return error;
}
finally
{
Marshal.FreeHGlobal(sessionDetailPtr);
}
}
internal static CorsairError CorsairDisconnect()
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairDisconnect();
}
internal static CorsairError CorsairGetDevices(_CorsairDeviceFilter filter, out _CorsairDeviceInfo[] devices)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
int structSize = Marshal.SizeOf<_CorsairDeviceInfo>();
nint devicePtr = Marshal.AllocHGlobal(structSize * CORSAIR_DEVICE_COUNT_MAX);
try
{
CorsairError error = _corsairGetDevices(filter, CORSAIR_DEVICE_COUNT_MAX, devicePtr, out int size);
devices = devicePtr.ToArray<_CorsairDeviceInfo>(size);
return error;
}
finally
{
Marshal.FreeHGlobal(devicePtr);
}
}
internal static CorsairError CorsairGetDeviceInfo(string deviceId, _CorsairDeviceInfo deviceInfo)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairGetDeviceInfo(deviceId, deviceInfo);
}
internal static CorsairError CorsairGetLedPositions(string deviceId, out _CorsairLedPosition[] ledPositions)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
int structSize = Marshal.SizeOf<_CorsairLedPosition>();
nint ledPositionsPtr = Marshal.AllocHGlobal(structSize * CORSAIR_DEVICE_LEDCOUNT_MAX);
try
{
CorsairError error = _corsairGetLedPositions(deviceId, CORSAIR_DEVICE_LEDCOUNT_MAX, ledPositionsPtr, out int size);
ledPositions = ledPositionsPtr.ToArray<_CorsairLedPosition>(size);
return error;
}
finally
{
Marshal.FreeHGlobal(ledPositionsPtr);
}
}
internal static CorsairError CorsairSetLedColors(string deviceId, int ledCount, nint ledColorsPtr)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairSetLedColors(deviceId, ledCount, ledColorsPtr);
}
internal static CorsairError CorsairSetLayerPriority(uint priority)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairSetLayerPriority(priority);
}
internal static CorsairError CorsairGetLedLuidForKeyName(string deviceId, char keyName, out uint ledId)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairGetLedLuidForKeyName(deviceId, keyName, out ledId);
}
internal static CorsairError CorsairRequestControl(string deviceId, CorsairAccessLevel accessLevel)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairRequestControl(deviceId, accessLevel);
}
internal static CorsairError CorsairReleaseControl(string deviceId)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _corsairReleaseControl(deviceId);
}
internal static CorsairError GetDevicePropertyInfo(string deviceId, CorsairDevicePropertyId propertyId, uint index, out CorsairDataType dataType, out CorsairPropertyFlag flags)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
return _getDevicePropertyInfo(deviceId, propertyId, index, out dataType, out flags);
}
internal static CorsairError ReadDeviceProperty(string deviceId, CorsairDevicePropertyId propertyId, uint index, out _CorsairProperty? property)
{
if (!IsConnected) throw new RGBDeviceException("The Corsair-SDK is not connected.");
nint propertyPtr = Marshal.AllocHGlobal(Marshal.SizeOf<_CorsairProperty>());
try
{
CorsairError error = _readDeviceProperty(deviceId, propertyId, index, propertyPtr);
property = Marshal.PtrToStructure<_CorsairProperty>(propertyPtr);
return error;
}
finally
{
Marshal.FreeHGlobal(propertyPtr);
}
}
internal static int ReadDevicePropertySimpleInt32(string deviceId, CorsairDevicePropertyId propertyId, uint index = 0) => ReadDevicePropertySimple(deviceId, propertyId, CorsairDataType.Int32, index).int32;
internal static int[] ReadDevicePropertySimpleInt32Array(string deviceId, CorsairDevicePropertyId propertyId, uint index = 0)
{
_CorsairDataValue dataValue = ReadDevicePropertySimple(deviceId, propertyId, CorsairDataType.Int32Array, index);
return dataValue.int32Array.items.ToArray((int)dataValue.int32Array.count);
}
internal static _CorsairDataValue ReadDevicePropertySimple(string deviceId, CorsairDevicePropertyId propertyId, CorsairDataType expectedDataType, uint index = 0)
{
CorsairError errorCode = GetDevicePropertyInfo(deviceId, propertyId, index, out CorsairDataType dataType, out CorsairPropertyFlag flags);
if (errorCode != CorsairError.Success)
throw new RGBDeviceException($"Failed to read device-property-info '{propertyId}' for corsair device '{deviceId}'. (ErrorCode: {errorCode})");
if (dataType != expectedDataType)
throw new RGBDeviceException($"Failed to read device-property-info '{propertyId}' for corsair device '{deviceId}'. (Wrong data-type '{dataType}', expected: '{expectedDataType}')");
if (!flags.HasFlag(CorsairPropertyFlag.CanRead))
throw new RGBDeviceException($"Failed to read device-property-info '{propertyId}' for corsair device '{deviceId}'. (Not readable)");
errorCode = ReadDeviceProperty(deviceId, propertyId, index, out _CorsairProperty? property);
if (errorCode != CorsairError.Success)
throw new RGBDeviceException($"Failed to read device-property '{propertyId}' for corsair device '{deviceId}'. (ErrorCode: {errorCode})");
if (property == null)
throw new RGBDeviceException($"Failed to read device-property '{propertyId}' for corsair device '{deviceId}'. (Invalid return value)");
if (property.Value.type != expectedDataType)
throw new RGBDeviceException($"Failed to read device-property '{propertyId}' for corsair device '{deviceId}'. (Wrong data-type '{dataType}', expected: '{expectedDataType}')");
return property.Value.value;
}
#endregion
}