1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00

Added gRPC sdk client

This commit is contained in:
Diogo Trindade 2025-06-18 19:01:20 +01:00
parent c0828c1d01
commit f26b3c193a
No known key found for this signature in database
GPG Key ID: 4B5B48393B9F875F
14 changed files with 426 additions and 130 deletions

View File

@ -15,5 +15,8 @@ public enum WootingLayoutType
{
Unknown = -1,
ANSI = 0,
ISO = 1
ISO = 1,
JIS = 2,
ANSI_SPLIT_SPACEBAR = 3,
ISO_SPLIT_SPACEBAR = 4,
}

View File

@ -11,6 +11,10 @@ namespace RGB.NET.Devices.Wooting.Generic;
/// </summary>
internal static class WootingLedMappings
{
public const int ROWS = 6;
public const int COLUMNS = 21;
#region Properties & Fields
private static readonly Dictionary<LedId, (int row, int column)> TKL = new()

View File

@ -1,37 +1,44 @@
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Native;
using System.Collections.Generic;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum;
namespace RGB.NET.Devices.Wooting.Generic;
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
/// <inheritdoc cref="IWootingRGBDevice" />
/// <summary>
/// Represents a Wooting-device
/// </summary>
public abstract class WootingRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDeviceInfo>, IWootingRGBDevice
where TDeviceInfo : WootingRGBDeviceInfo
{
#region Properties & Fields
private readonly Dictionary<LedId, (int row, int column)> _mapping;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WootingRGBDevice{TDeviceInfo}"/> class.
/// </summary>
/// <param name="info">The generic information provided by Wooting for the device.</param>
/// <param name="updateQueue">The update queue used to update this device.</param>
protected WootingRGBDevice(TDeviceInfo info, IUpdateQueue updateQueue)
internal WootingRGBDevice(WootingDeviceType deviceType, TDeviceInfo info, IUpdateQueue updateQueue)
: base(info, updateQueue)
{
_mapping = WootingLedMappings.Mapping[deviceType];
InitializeLayout();
}
#endregion
#region Methods
private void InitializeLayout()
{
foreach (KeyValuePair<LedId, (int row, int column)> led in _mapping)
AddLed(led.Key, new Point(led.Value.column * 19, led.Value.row * 19), new Size(19, 19));
}
/// <inheritdoc />
protected override object GetLedCustomData(LedId ledId) => _mapping[ledId];
/// <inheritdoc />
public override void Dispose()
{
_WootingSDK.SelectDevice(DeviceInfo.WootingDeviceIndex);
_WootingSDK.Reset();
UpdateQueue.Dispose();
base.Dispose();
}

View File

@ -1,14 +1,8 @@
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Native;
namespace RGB.NET.Devices.Wooting.Generic;
/// <inheritdoc />
/// <summary>
/// Represents a generic information for a Wooting-<see cref="T:RGB.NET.Core.IRGBDevice" />.
/// </summary>
public class WootingRGBDeviceInfo : IRGBDeviceInfo
public abstract class WootingRGBDeviceInfo : IRGBDeviceInfo
{
#region Properties & Fields
@ -27,36 +21,15 @@ public class WootingRGBDeviceInfo : IRGBDeviceInfo
/// <inheritdoc />
public object? LayoutMetadata { get; set; }
/// <summary>
/// Gets the <see cref="Enum.WootingDeviceType"/> of the <see cref="WootingRGBDevice{TDeviceInfo}"/>.
/// </summary>
public WootingDeviceType WootingDeviceType { get; }
/// <summary>
/// Gets the <see cref="Enum.WootingLayoutType"/> of the <see cref="WootingRGBDevice{TDeviceInfo}"/>.
/// </summary>
public WootingLayoutType WootingLayoutType { get; }
public byte WootingDeviceIndex { get; }
#endregion
#region Constructors
/// <summary>
/// Internal constructor of managed <see cref="WootingRGBDeviceInfo"/>.
/// </summary>
/// <param name="deviceType">The type of the <see cref="IRGBDevice"/>.</param>
/// <param name="deviceInfo">The <see cref="_WootingDeviceInfo"/> of the <see cref="IRGBDevice"/>.</param>
internal WootingRGBDeviceInfo(RGBDeviceType deviceType, _WootingDeviceInfo deviceInfo, byte deviceIndex)
protected WootingRGBDeviceInfo(RGBDeviceType deviceType, string model, string name)
{
this.DeviceType = deviceType;
this.WootingDeviceType = deviceInfo.DeviceType;
this.WootingLayoutType = deviceInfo.LayoutType;
this.WootingDeviceIndex = deviceIndex;
Model = deviceInfo.Model;
DeviceName = DeviceHelper.CreateDeviceName(Manufacturer, Model);
this.Model = model;
this.DeviceName = name;
}
#endregion

View File

@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Grpc.Net.Client;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Keyboard;
using RGB.NET.Devices.Wooting.Keypad;
using WootingRgbSdk;
namespace RGB.NET.Devices.Wooting.Grpc;
/// <inheritdoc />
/// <summary>
/// Represents a device provider responsible for Wooting devices.
/// </summary>
public sealed class WootingGrpcDeviceProvider : AbstractRGBDeviceProvider
{
#region Properties & Fields
// ReSharper disable once InconsistentNaming
private static readonly Lock _lock = new();
private GrpcChannel? _channel;
private RgbSdkService.RgbSdkServiceClient? _client;
private static WootingGrpcDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="WootingGrpcDeviceProvider"/> instance.
/// </summary>
public static WootingGrpcDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new WootingGrpcDeviceProvider();
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WootingGrpcDeviceProvider"/> class.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public WootingGrpcDeviceProvider()
{
lock (_lock)
{
if (_instance != null)
throw new InvalidOperationException($"There can be only one instance of type {nameof(WootingGrpcDeviceProvider)}");
_instance = this;
}
}
#endregion
#region Methods
/// <inheritdoc />
protected override void InitializeSDK()
{
_channel = GrpcChannel.ForAddress("http://localhost:50051");
_client = new RgbSdkService.RgbSdkServiceClient(_channel);
}
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
ArgumentNullException.ThrowIfNull(_client, nameof(_client));
int i = 0;
foreach (RgbGetConnectedDevicesResponse.Types.RgbDevice? device in _client.GetConnectedDevices(new()).Devices)
{
if (device.DeviceType == RgbDeviceType.None)
continue; // Skip devices that are not supported
_client.Initialize(new RgbInitializeRequest { Id = device.Id });
WootingGrpcUpdateQueue updateQueue = new(GetUpdateTrigger(i++), device, _client);
WootingDeviceType deviceType = device.DeviceType switch
{
RgbDeviceType.Tkl => WootingDeviceType.KeyboardTKL,
RgbDeviceType.FullSize => WootingDeviceType.Keyboard,
RgbDeviceType.SixtyPercent => WootingDeviceType.KeyboardSixtyPercent,
RgbDeviceType.ThreeKey => WootingDeviceType.Keypad3Keys,
RgbDeviceType.EighyPercent => WootingDeviceType.KeyboardEightyPercent,
_ or RgbDeviceType.None => throw new ArgumentOutOfRangeException()
};
KeyboardLayoutType layoutType = device.LayoutType switch
{
RgbDeviceLayout.Ansi => KeyboardLayoutType.ANSI,
RgbDeviceLayout.Iso => KeyboardLayoutType.ISO,
RgbDeviceLayout.Jis => KeyboardLayoutType.JIS,
RgbDeviceLayout.AnsiSplitSpacebar => KeyboardLayoutType.ANSI,
RgbDeviceLayout.IsoSplitSpacebar => KeyboardLayoutType.ISO,
RgbDeviceLayout.Unknown => KeyboardLayoutType.Unknown,
_ => throw new ArgumentOutOfRangeException()
};
//NOTE: this model name ends up kind of ugly, since `ModelName` is like `Wooting 60HE`. We cannot remove the `Wooting` prefix,
//since that makes loadouts fail to load. The deviceName part, however, is fine to strip.
string model = device.ModelName;
string name =
DeviceHelper.CreateDeviceName("Wooting", $"{device.ModelName.Replace("Wooting", "").Trim()} ({device.SerialNumber})");
yield return deviceType switch
{
WootingDeviceType.Keypad3Keys => new WootingKeypadRGBDevice(deviceType, new(model, name), updateQueue),
_ => new WootingKeyboardRGBDevice(deviceType, new(layoutType, model, name), updateQueue)
};
}
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
lock (_lock)
{
base.Dispose(disposing);
try
{
_client = null;
_channel?.Dispose();
}
catch
{ /* at least we tried */
}
_instance = null;
}
}
#endregion
}

View File

@ -0,0 +1,97 @@
using System;
using System.Runtime.InteropServices;
using Google.Protobuf;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Generic;
using WootingRgbSdk;
namespace RGB.NET.Devices.Wooting.Grpc;
/// <inheritdoc />
/// <summary>
/// Represents the update-queue performing updates for cooler master devices.
/// </summary>
public sealed class WootingGrpcUpdateQueue : UpdateQueue
{
#region Properties & Fields
private readonly RgbSdkService.RgbSdkServiceClient _client;
private readonly RgbGetConnectedDevicesResponse.Types.RgbDevice _wootDevice;
private readonly WootingColor[] _colors;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WootingGrpcUpdateQueue"/> class.
/// </summary>
/// <param name="updateTrigger">The update trigger used by this queue.</param>
public WootingGrpcUpdateQueue(IDeviceUpdateTrigger updateTrigger, RgbGetConnectedDevicesResponse.Types.RgbDevice wootDevice,
RgbSdkService.RgbSdkServiceClient client)
: base(updateTrigger)
{
this._client = client;
this._wootDevice = wootDevice;
this._colors = new WootingColor[WootingLedMappings.COLUMNS * WootingLedMappings.ROWS];
}
#endregion
#region Methods
/// <inheritdoc />
protected override bool Update(ReadOnlySpan<(object key, Color color)> dataSet)
{
try
{
foreach ((object key, Color color) in dataSet)
{
(int row, int column) = ((int, int))key;
long index = (WootingLedMappings.COLUMNS * row) + column;
_colors[index] = new WootingColor(color.GetR(), color.GetG(), color.GetB());
}
_client.SetColors(new RgbSetColorsRequest
{
Id = _wootDevice.Id,
Colors = ByteString.CopyFrom(MemoryMarshal.AsBytes(_colors.AsSpan()))
});
return true;
}
catch (Exception ex)
{
WootingGrpcDeviceProvider.Instance.Throw(ex);
}
return false;
}
/// <inheritdoc />
public override void Dispose()
{
_client.Close(new RgbCloseRequest { Id = _wootDevice.Id });
base.Dispose();
}
#endregion
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct WootingColor
{
public byte r;
public byte g;
public byte b;
public byte a;
public WootingColor(byte r, byte g, byte b)
{
this.r = r;
this.g = g;
this.b = b;
this.a = 0; // Alpha is not used in Wooting devices
}
}

View File

@ -1,5 +1,5 @@
using System.Collections.Generic;
using RGB.NET.Core;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Generic;
namespace RGB.NET.Devices.Wooting.Keyboard;
@ -22,28 +22,12 @@ public sealed class WootingKeyboardRGBDevice : WootingRGBDevice<WootingKeyboardR
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Wooting.Keyboard.WootingKeyboardRGBDevice" /> class.
/// </summary>
/// <param name="deviceType">The type of the Wooting device.</param>
/// <param name="info">The specific information provided by Wooting for the keyboard</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal WootingKeyboardRGBDevice(WootingKeyboardRGBDeviceInfo info, IUpdateQueue updateQueue)
: base(info, updateQueue)
{
InitializeLayout();
}
#endregion
#region Methods
private void InitializeLayout()
{
Dictionary<LedId, (int row, int column)> mapping = WootingLedMappings.Mapping[DeviceInfo.WootingDeviceType];
foreach (KeyValuePair<LedId, (int row, int column)> led in mapping)
AddLed(led.Key, new Point(led.Value.column * 19, led.Value.row * 19), new Size(19, 19));
}
/// <inheritdoc />
protected override object GetLedCustomData(LedId ledId) => WootingLedMappings.Mapping[DeviceInfo.WootingDeviceType][ledId];
/// <param name="updateQueue">The update queue used to update this device.</param>
internal WootingKeyboardRGBDevice(WootingDeviceType deviceType, WootingKeyboardRGBDeviceInfo info, IUpdateQueue updateQueue)
: base(deviceType, info, updateQueue)
{ }
#endregion
}

View File

@ -23,16 +23,13 @@ public sealed class WootingKeyboardRGBDeviceInfo : WootingRGBDeviceInfo, IKeyboa
/// <summary>
/// Internal constructor of managed <see cref="T:RGB.NET.Devices.Wooting.WootingKeyboardRGBDeviceInfo" />.
/// </summary>
/// <param name="deviceInfo">The native <see cref="T:RGB.NET.Devices.Wooting.Native._WootingDeviceInfo" />.</param>
internal WootingKeyboardRGBDeviceInfo(_WootingDeviceInfo deviceInfo, byte deviceIndex)
: base(RGBDeviceType.Keyboard, deviceInfo, deviceIndex)
/// <param name="layout">The layout of the keyboard.</param>
/// <param name="model">The model of the keyboard.</param>
/// <param name="name">The name of the keyboard.</param>
internal WootingKeyboardRGBDeviceInfo(KeyboardLayoutType layout, string model, string name)
: base(RGBDeviceType.Keyboard, model, name)
{
Layout = WootingLayoutType switch
{
WootingLayoutType.ANSI => KeyboardLayoutType.ANSI,
WootingLayoutType.ISO => KeyboardLayoutType.ISO,
_ => KeyboardLayoutType.Unknown
};
Layout = layout;
}
#endregion

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Enum;
using RGB.NET.Devices.Wooting.Generic;
namespace RGB.NET.Devices.Wooting.Keypad;
@ -16,28 +17,12 @@ public sealed class WootingKeypadRGBDevice : WootingRGBDevice<WootingKeypadRGBDe
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Wooting.Keyboard.WootingKeypadRGBDevice" /> class.
/// </summary>
/// <param name="deviceType">The type of the Wooting device.</param>
/// <param name="info">The specific information provided by Wooting for the keyboard</param>
/// <param name="updateQueue">The update queue used to update this device.</param>
internal WootingKeypadRGBDevice(WootingKeypadRGBDeviceInfo info, IUpdateQueue updateQueue)
: base(info, updateQueue)
{
InitializeLayout();
}
#endregion
#region Methods
private void InitializeLayout()
{
Dictionary<LedId, (int row, int column)> mapping = WootingLedMappings.Mapping[DeviceInfo.WootingDeviceType];
foreach (KeyValuePair<LedId, (int row, int column)> led in mapping)
AddLed(led.Key, new Point(led.Value.column * 19, led.Value.row * 19), new Size(19, 19));
}
/// <inheritdoc />
protected override object GetLedCustomData(LedId ledId) => WootingLedMappings.Mapping[DeviceInfo.WootingDeviceType][ledId];
internal WootingKeypadRGBDevice(WootingDeviceType deviceType, WootingKeypadRGBDeviceInfo info, IUpdateQueue updateQueue)
: base(deviceType, info, updateQueue)
{ }
#endregion
}

View File

@ -9,9 +9,7 @@ namespace RGB.NET.Devices.Wooting.Keypad;
/// </summary>
public sealed class WootingKeypadRGBDeviceInfo : WootingRGBDeviceInfo
{
internal WootingKeypadRGBDeviceInfo(_WootingDeviceInfo deviceInfo, byte deviceIndex)
: base(RGBDeviceType.Keypad, deviceInfo, deviceIndex)
{
}
internal WootingKeypadRGBDeviceInfo(string model, string name)
: base(RGBDeviceType.Keypad, model, name)
{ }
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
@ -72,7 +72,8 @@ public sealed class WootingDeviceProvider : AbstractRGBDeviceProvider
{
lock (_lock)
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(WootingDeviceProvider)}");
if (_instance != null)
throw new InvalidOperationException($"There can be only one instance of type {nameof(WootingDeviceProvider)}");
_instance = this;
}
}
@ -99,18 +100,35 @@ public sealed class WootingDeviceProvider : AbstractRGBDeviceProvider
{
for (byte i = 0; i < _WootingSDK.GetDeviceCount(); i++)
{
WootingUpdateQueue updateQueue = new(GetUpdateTrigger(), i);
WootingNativeUpdateQueue updateQueue = new(GetUpdateTrigger(), i);
_WootingSDK.SelectDevice(i);
_WootingDeviceInfo nativeDeviceInfo = (_WootingDeviceInfo)Marshal.PtrToStructure(_WootingSDK.GetDeviceInfo(), typeof(_WootingDeviceInfo))!;
_WootingDeviceInfo nativeDeviceInfo =
(_WootingDeviceInfo)Marshal.PtrToStructure(_WootingSDK.GetDeviceInfo(), typeof(_WootingDeviceInfo))!;
//Uwu non-rgb returns zero here.
if (nativeDeviceInfo.MaxLedIndex == 0)
continue;
KeyboardLayoutType layoutType = nativeDeviceInfo.LayoutType switch
{
WootingLayoutType.Unknown => KeyboardLayoutType.Unknown,
WootingLayoutType.ANSI => KeyboardLayoutType.ANSI,
WootingLayoutType.ISO => KeyboardLayoutType.ISO,
WootingLayoutType.JIS => KeyboardLayoutType.JIS,
WootingLayoutType.ANSI_SPLIT_SPACEBAR => KeyboardLayoutType.ANSI,
WootingLayoutType.ISO_SPLIT_SPACEBAR => KeyboardLayoutType.ISO,
_ => throw new ArgumentOutOfRangeException()
};
//Note: we cannot change *any* of these here or the local database Artemis has will no longer match up and everything breaks.
string model = nativeDeviceInfo.Model;
string name = DeviceHelper.CreateDeviceName("Wooting", model);
yield return nativeDeviceInfo.DeviceType switch
{
WootingDeviceType.Keypad3Keys => new WootingKeypadRGBDevice(new WootingKeypadRGBDeviceInfo(nativeDeviceInfo, i), updateQueue),
_ => new WootingKeyboardRGBDevice(new WootingKeyboardRGBDeviceInfo(nativeDeviceInfo, i), updateQueue),
WootingDeviceType.Keypad3Keys => new WootingKeypadRGBDevice(nativeDeviceInfo.DeviceType, new(model, name),
updateQueue),
_ => new WootingKeyboardRGBDevice(nativeDeviceInfo.DeviceType, new(layoutType, model, name), updateQueue)
};
}
}
@ -127,7 +145,9 @@ public sealed class WootingDeviceProvider : AbstractRGBDeviceProvider
lock (_WootingSDK.SdkLock)
{
try { _WootingSDK.UnloadWootingSDK(); }
catch { /* at least we tried */ }
catch
{ /* at least we tried */
}
}
_instance = null;

View File

@ -1,14 +1,13 @@
using System;
using System;
using RGB.NET.Core;
using RGB.NET.Devices.Wooting.Native;
namespace RGB.NET.Devices.Wooting.Generic;
namespace RGB.NET.Devices.Wooting.Native;
/// <inheritdoc />
/// <summary>
/// Represents the update-queue performing updates for cooler master devices.
/// </summary>
public sealed class WootingUpdateQueue : UpdateQueue
public sealed class WootingNativeUpdateQueue : UpdateQueue
{
#region Properties & Fields
@ -22,7 +21,7 @@ public sealed class WootingUpdateQueue : UpdateQueue
/// Initializes a new instance of the <see cref="WootingUpdateQueue"/> class.
/// </summary>
/// <param name="updateTrigger">The update trigger used by this queue.</param>
public WootingUpdateQueue(IDeviceUpdateTrigger updateTrigger, byte deviceId)
public WootingNativeUpdateQueue(IDeviceUpdateTrigger updateTrigger, byte deviceId)
: base(updateTrigger)
{
this._deviceid = deviceId;
@ -60,5 +59,14 @@ public sealed class WootingUpdateQueue : UpdateQueue
return false;
}
/// <inheritdoc />
public override void Dispose()
{
_WootingSDK.SelectDevice(_deviceid);
_WootingSDK.Reset();
base.Dispose();
}
#endregion
}

View File

@ -64,4 +64,17 @@
<ItemGroup>
<ProjectReference Include="..\RGB.NET.Core\RGB.NET.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.31.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
<PackageReference Include="Grpc.Tools" Version="2.72.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="WootingRgb.proto" GrpcServices="Client" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,68 @@
syntax = "proto3";
package wooting_rgb_sdk;
//from C rgb_sdk
enum RgbDeviceType {
None = 0;
Tkl = 1;
FullSize = 2;
SixtyPercent = 3;
ThreeKey = 4;
EighyPercent = 5;
}
//from C rgb_sdk
enum RgbDeviceLayout {
ANSI = 0;
ISO = 1;
JIS = 2;
ANSI_SPLIT_SPACEBAR = 3;
ISO_SPLIT_SPACEBAR = 4;
Unknown = -1;
}
message RgbGetConnectedDevicesRequest {}
message RgbGetConnectedDevicesResponse {
message RgbDevice {
uint64 id = 1;
uint32 rows = 2;
uint32 columns = 3;
string model_name = 4;
string serial_number = 5;
RgbDeviceType device_type = 6;
RgbDeviceLayout layout_type = 7;
}
repeated RgbDevice devices = 1;
}
message RgbInitializeRequest {
uint64 id = 1;
}
message RgbInitializeResponse {
}
message RgbSetColorsRequest {
uint64 id = 1;
bytes colors = 2;
}
message RgbSetColorsResponse {
}
message RgbCloseRequest {
uint64 id = 1;
}
message RgbCloseResponse {
}
service RgbSdkService {
rpc GetConnectedDevices(RgbGetConnectedDevicesRequest) returns (RgbGetConnectedDevicesResponse);
rpc Initialize(RgbInitializeRequest) returns (RgbInitializeResponse);
rpc SetColors(RgbSetColorsRequest) returns (RgbSetColorsResponse);
rpc Close(RgbCloseRequest) returns (RgbCloseResponse);
}