muzika-gromche/MuzikaGromche/Via/Harness.cs

232 lines
6.2 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using HidSharp;
namespace MuzikaGromche.Via;
class FrameworkDeviceFactory : IDevicePoolFactory<ViaDeviceDelegate>
{
// Framework Vendor ID
private const int VID = 0x32AC;
internal static readonly int[] PIDs =
[
0x0012, // Framework Laptop 16 Keyboard Module - ANSI
0x0013, // Framework Laptop 16 RGB macropad module
];
// VIA/QMK Raw HID identifiers
private const ushort UsagePage = 0xFF60;
private const byte Usage = 0x61;
// In case of Framework Laptop 16 RGB Keyboard and Macropad,
// Usage Page & Usage are encoded at the start of the report descriptor.
private static readonly byte[] UsagePageAndUsageEncoded = [0x06, 0x60, 0xFF, 0x09, 0x61];
private const byte ExpectedVersion = 12;
private static bool DevicePredicate(HidDevice device)
{
if (VID != device.VendorID)
{
return false;
}
if (!PIDs.Contains(device.ProductID))
{
return false;
}
var report = device.GetRawReportDescriptor();
if (report == null)
{
return false;
}
if (!report.AsSpan().StartsWith(UsagePageAndUsageEncoded))
{
return false;
}
return true;
}
public ViaDeviceDelegate? Create(HidDevice hidDevice)
{
if (!DevicePredicate(hidDevice))
{
return null;
}
if (!hidDevice.TryOpen(out var stream))
{
return null;
}
var via = new ViaKeyboardApi(stream);
var version = via.GetProtocolVersion();
if (version != ExpectedVersion)
{
return null;
}
var brightness = via.GetRgbMatrixBrightness();
if (brightness == null)
{
return null;
}
var effect = via.GetRgbMatrixEffect();
if (effect == null)
{
return null;
}
var color = via.GetRgbMatrixColor();
if (color == null)
{
return null;
}
return new ViaDeviceDelegate(hidDevice, stream, via, brightness.Value, effect.Value, color.Value.Hue, color.Value.Saturation);
}
}
class ViaDeviceDelegate : IDevicePoolDelegate, ILightshow, IDisposable
{
// Objects
public readonly HidDevice device;
public readonly HidStream stream;
public readonly ViaKeyboardApi via;
private readonly AsyncEventProcessor<ViaDeviceState> asyncEventProcessor;
private readonly ViaDeviceState initialState;
private byte? brightnessOverride = null;
private const ViaEffectMode EFFECT = ViaEffectMode.Static;
private byte? effectOverride = null;
private ColorHSV? color = null;
public ViaDeviceDelegate(HidDevice device, HidStream stream, ViaKeyboardApi via, byte brightness, byte effect, byte hue, byte saturation)
{
// Objects
this.device = device;
this.stream = stream;
this.via = via;
// Initial values
initialState = new()
{
Brightness = brightness,
Effect = effect,
Hue = hue,
Saturation = saturation,
};
asyncEventProcessor = new AsyncEventProcessor<ViaDeviceState>(initialState, ProcessDataAsync);
Notify();
}
public void Restore()
{
brightnessOverride = null;
effectOverride = null;
color = null;
Notify();
}
public void Dispose()
{
// Simplified version of Restore()
brightnessOverride = null;
effectOverride = null;
color = null;
asyncEventProcessor.Notify(initialState, requestShutdown: true);
}
public void SetBrightnessOverride(byte? brightness)
{
brightnessOverride = brightness;
effectOverride = (byte)EFFECT;
Notify();
}
public void SetColor(byte hue, byte saturation, byte value)
{
color = new ColorHSV(hue, saturation, value);
effectOverride = (byte)EFFECT;
Notify();
}
private void Notify()
{
asyncEventProcessor.Notify(new()
{
Brightness = brightnessOverride ?? color?.Value ?? initialState.Brightness,
Effect = effectOverride ?? initialState.Effect,
Hue = color?.Hue ?? initialState.Hue,
Saturation = color?.Saturation ?? initialState.Saturation,
});
}
private async ValueTask ProcessDataAsync(ViaDeviceState oldData, ViaDeviceState newData, bool shutdown)
{
var cancellationToken = CancellationToken.None;
try
{
if (oldData.Brightness != newData.Brightness)
{
await via.SetRgbMatrixBrightnessAsync(newData.Brightness, cancellationToken);
}
if (oldData.Effect != newData.Effect)
{
await via.SetRgbMatrixEffectAsync(newData.Effect, cancellationToken);
}
if (oldData.Hue != newData.Hue || oldData.Saturation != newData.Saturation)
{
await via.SetRgbMatrixColorAsync(newData.Hue, newData.Saturation, cancellationToken);
}
}
catch (IOException)
{
}
catch (TimeoutException)
{
}
finally
{
if (shutdown)
{
stream.Close();
}
}
}
}
class DevicePoolLightshow<T> : ILightshow
where T: IDevicePoolDelegate, ILightshow
{
private readonly DevicePool<T> devicePool;
public DevicePoolLightshow(DevicePool<T> devicePool)
{
this.devicePool = devicePool;
}
public void SetBrightnessOverride(byte? brightness)
{
devicePool.ForEach(d =>
{
d.SetBrightnessOverride(brightness);
});
}
public void SetColor(byte hue, byte saturation, byte value)
{
devicePool.ForEach(d =>
{
d.SetColor(hue, saturation, value);
});
}
}
struct ViaDeviceState
{
public byte Brightness;
public byte Effect;
public byte Hue;
public byte Saturation;
}
record struct ColorHSV(byte Hue, byte Saturation, byte Value);