Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions RGB.NET.Devices.Wooting/Enum/WootingLayoutType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
4 changes: 4 additions & 0 deletions RGB.NET.Devices.Wooting/Generic/WootingLedMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
43 changes: 25 additions & 18 deletions RGB.NET.Devices.Wooting/Generic/WootingRGBDevice.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
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();
}

#endregion
}
}
37 changes: 5 additions & 32 deletions RGB.NET.Devices.Wooting/Generic/WootingRGBDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -27,37 +21,16 @@ 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
}
}
138 changes: 138 additions & 0 deletions RGB.NET.Devices.Wooting/Grpc/WootingGrpcDeviceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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://127.0.0.1:50051");
_client = new RgbSdkService.RgbSdkServiceClient(_channel);
}

/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
ArgumentNullException.ThrowIfNull(_client, nameof(_client));

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(device.SerialNumber.GetHashCode()), 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
}
96 changes: 96 additions & 0 deletions RGB.NET.Devices.Wooting/Grpc/WootingGrpcUpdateQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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;
int 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 readonly struct WootingColor
{
public readonly byte r;
public readonly byte g;
public readonly byte b;
public readonly 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
}
}
Loading
Loading