using HidSharp;
using RGB.NET.Core;
using RGB.NET.HID;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace RGB.NET.Devices.Logitech.HID;
///
/// Represents a loaded for logitech HID-devices.
///
/// The type of the identifier leds are mapped to.
/// The type of the custom data added to the HID-device.
public sealed class LightspeedHIDLoader : IEnumerable>
where TLed : notnull
{
#region Constants
private const int LOGITECH_PROTOCOL_TIMEOUT = 300;
private const int VENDOR_ID = 0x046D;
// ReSharper disable once StaticMemberInGenericType - This is used like a const
private static readonly List RECEIVER_PIDS =
[
0xC539,
0xC53A,
0xC541,
0xC545,
0xC547
];
#endregion
#region Properties & Fields
private readonly Dictionary> _deviceDefinitions = [];
///
/// Gets the vendor id used for this loader.
///
public int VendorId => VENDOR_ID;
///
/// Gets or sets the filter used to determine which devices should be loaded.
///
public RGBDeviceType LoadFilter { get; set; } = RGBDeviceType.All;
#endregion
#region Methods
///
/// Adds a new to this loader.
///
/// The virtual product id of the HID-device.
/// The type of the device.
/// The name of the device.
/// The mapping of the leds of the device.
/// Some custom data to attach to the device.
public void Add(int virtualPid, RGBDeviceType deviceType, string name, LedMapping ledMapping, TData customData)
=> _deviceDefinitions.Add(virtualPid, new HIDDeviceDefinition(virtualPid, deviceType, name, ledMapping, customData));
///
/// Gets a enumerable containing all devices from the definition-list that are connected and match the .
///
/// The enumerable containing the connected devices.
public IEnumerable> GetConnectedDevices()
{
foreach (int device in Detect())
{
if (_deviceDefinitions.TryGetValue(device, out HIDDeviceDefinition? definition))
if (LoadFilter.HasFlag(definition.DeviceType))
yield return definition;
}
}
///
/// Gets a enumerable containing all the first device of each group of devices from the definition-list that are connected and match the .
/// The grouping is done by the specified function.
///
/// The type of the key used to group the devices.
/// The function grouping the devices.
/// The enumerable containing the selected devices.
public IEnumerable> GetConnectedDevices(Func, TKey> groupBy)
=> GetConnectedDevices().GroupBy(groupBy)
.Select(group => group.First());
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
///
public IEnumerator> GetEnumerator() => _deviceDefinitions.Values.GetEnumerator();
#endregion
#region Private Methods
private IEnumerable Detect() => RECEIVER_PIDS.SelectMany(Detect);
private IEnumerable Detect(int pid)
{
Dictionary deviceUsages = DeviceList.Local
.GetHidDevices(VendorId, pid)
.Where(d => d.DevicePath.Contains("mi_02"))
.Select(x => ((byte)x.GetUsage(), x))
.DistinctBy(x => x.Item1)
.ToDictionary(x => x.Item1, x => x.x);
foreach ((int wirelessPid, byte _) in GetWirelessDevices(deviceUsages))
yield return wirelessPid;
}
[SuppressMessage("ReSharper", "MustUseReturnValue")]
private Dictionary GetWirelessDevices(IReadOnlyDictionary deviceUsages)
{
const byte LOGITECH_RECEIVER_ADDRESS = 0xFF;
const byte LOGITECH_SET_REGISTER_REQUEST = 0x80;
const byte LOGITECH_GET_REGISTER_REQUEST = 0x81;
Dictionary map = [];
if (!deviceUsages.TryGetValue(1, out HidDevice? device) || !device.TryOpen(out HidStream stream))
return map;
int tries = 0;
const int MAX_TRIES = 5;
while (tries < MAX_TRIES)
{
try
{
stream.ReadTimeout = LOGITECH_PROTOCOL_TIMEOUT;
stream.WriteTimeout = LOGITECH_PROTOCOL_TIMEOUT;
FapResponse response = new();
FapShortRequest getConnectedDevices = new();
getConnectedDevices.Init(LOGITECH_RECEIVER_ADDRESS, LOGITECH_GET_REGISTER_REQUEST);
stream.Write(getConnectedDevices.AsSpan());
stream.ReadExactly(response.AsSpan());
bool wirelessNotifications = (response.Data01 & 1) == 1;
if (!wirelessNotifications)
{
response = new FapResponse();
getConnectedDevices.Init(LOGITECH_RECEIVER_ADDRESS, LOGITECH_SET_REGISTER_REQUEST);
getConnectedDevices.Data1 = 1;
stream.Write(getConnectedDevices.AsSpan());
stream.ReadExactly(response.AsSpan());
if (getConnectedDevices.FeatureIndex == 0x8f)
{
//error??
}
}
response = new FapResponse();
getConnectedDevices.Init(LOGITECH_RECEIVER_ADDRESS, LOGITECH_GET_REGISTER_REQUEST);
getConnectedDevices.FeatureCommand = 0x02;
stream.Write(getConnectedDevices.AsSpan());
stream.ReadExactly(response.AsSpan());
int deviceCount = response.Data01;
if (deviceCount <= 0)
return map;
//Add 1 to the device_count to include the receiver
deviceCount++;
getConnectedDevices.Init(LOGITECH_RECEIVER_ADDRESS, LOGITECH_SET_REGISTER_REQUEST);
getConnectedDevices.FeatureCommand = 0x02;
getConnectedDevices.Data0 = 0x02;
stream.Write(getConnectedDevices.AsSpan());
for (int i = 0; i < deviceCount; i++)
{
FapResponse devices = new();
stream.ReadExactly(devices.AsSpan());
int wirelessPid = (devices.Data02 << 8) | devices.Data01;
if (devices.DeviceIndex != 0xff)
map.Add(wirelessPid, devices.DeviceIndex);
}
break;
}
catch
{
tries++;
//This might timeout if LGS or GHUB interfere.
//Retry.
}
}
return map;
}
#endregion
}