mirror of
https://github.com/jkocon/g-helper.git
synced 2026-02-23 13:00:52 +01:00
Added ASUS Mouse Control Protocol + Implementation for Chakram X (Dongle and Wired)
This commit is contained in:
870
app/Peripherals/Mouse/AsusMouse.cs
Normal file
870
app/Peripherals/Mouse/AsusMouse.cs
Normal file
@@ -0,0 +1,870 @@
|
||||
using GHelper.AnimeMatrix.Communication;
|
||||
using GHelper.AnimeMatrix.Communication.Platform;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace GHelper.Peripherals.Mouse
|
||||
{
|
||||
public enum PowerOffSetting
|
||||
{
|
||||
Minutes1 = 0,
|
||||
Minutes2 = 1,
|
||||
Minutes3 = 2,
|
||||
Minutes5 = 3,
|
||||
Minutes10 = 4,
|
||||
Never = 0xFF
|
||||
}
|
||||
|
||||
public enum LiftOffDistance
|
||||
{
|
||||
Low = 0,
|
||||
High = 1
|
||||
}
|
||||
public enum AnimationDirection
|
||||
{
|
||||
Clockwise = 0x0,
|
||||
CounterClockwise = 0x1
|
||||
}
|
||||
|
||||
public enum LightingSpeed
|
||||
{
|
||||
Slow = 0x9,
|
||||
Medium = 0x7,
|
||||
Fast = 0x5
|
||||
}
|
||||
public enum LightingMode
|
||||
{
|
||||
Static = 0x0,
|
||||
Breathing = 0x1,
|
||||
Colorwheel = 0x2,
|
||||
Rainbow = 0x3,
|
||||
React = 0x4,
|
||||
Comet = 0x5,
|
||||
BatteryState = 0x6
|
||||
}
|
||||
|
||||
public class LightingSetting
|
||||
{
|
||||
public LightingSetting()
|
||||
{
|
||||
//Some Sane defaults
|
||||
LightingMode = LightingMode.Static;
|
||||
LightingSpeed = LightingSpeed.Medium;
|
||||
AnimationDirection = AnimationDirection.Clockwise;
|
||||
RandomColor = false;
|
||||
Brightness = 25;
|
||||
RGBColor = Color.Red;
|
||||
}
|
||||
|
||||
public LightingMode LightingMode { get; set; }
|
||||
public int Brightness { get; set; }
|
||||
public Color RGBColor { get; set; }
|
||||
public bool RandomColor { get; set; }
|
||||
public LightingSpeed LightingSpeed { get; set; }
|
||||
|
||||
public AnimationDirection AnimationDirection { get; set; }
|
||||
|
||||
public override string? ToString()
|
||||
{
|
||||
return "LightingMode: " + LightingMode + ", Color (" + RGBColor.R + ", " + RGBColor.G + ", " + RGBColor.B
|
||||
+ "), Brightness: " + Brightness + "%, LightingSpeed: " + LightingSpeed + ", RandomColor:" + RandomColor + ", AnimationDirection:" + AnimationDirection;
|
||||
}
|
||||
}
|
||||
|
||||
public class AsusMouseDPI
|
||||
{
|
||||
public AsusMouseDPI()
|
||||
{
|
||||
Color = Color.Red;
|
||||
DPI = 800;
|
||||
}
|
||||
public Color Color { get; set; }
|
||||
public uint DPI { get; set; }
|
||||
public override string? ToString()
|
||||
{
|
||||
return "DPI: " + DPI + ", Color (" + Color.R + ", " + Color.G + ", " + Color.B + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AsusMouse : Device
|
||||
{
|
||||
internal const int ASUS_MOUSE_PACKET_SIZE = 65;
|
||||
|
||||
public event EventHandler? Disconnect;
|
||||
|
||||
private readonly string path;
|
||||
|
||||
public bool MouseIsReady { get; protected set; }
|
||||
public bool Wireless { get; protected set; }
|
||||
public int Battery { get; protected set; }
|
||||
public bool Charging { get; protected set; }
|
||||
public LightingSetting? LightingSetting { get; protected set; }
|
||||
public int LowBatteryWarning { get; protected set; }
|
||||
public PowerOffSetting PowerOffSetting { get; protected set; }
|
||||
public LiftOffDistance LiftOffDistance { get; protected set; }
|
||||
public int DpiProfile { get; protected set; }
|
||||
public AsusMouseDPI[] DpiSettings { get; protected set; }
|
||||
public int Profile { get; protected set; }
|
||||
public int PollingRate1 { get; protected set; }
|
||||
public bool AngleSnapping { get; protected set; }
|
||||
public short AngleAdjustmentDegrees { get; protected set; }
|
||||
|
||||
public AsusMouse(ushort vendorId, ushort productId, string path, bool wireless) : base(vendorId, productId)
|
||||
{
|
||||
this.path = path;
|
||||
this.Wireless = wireless;
|
||||
DpiSettings = new AsusMouseDPI[1];
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not AsusMouse item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.VendorID().Equals(item.VendorID())
|
||||
&& this.ProductID().Equals(item.ProductID())
|
||||
&& this.path.Equals(item.path);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 23;
|
||||
hash = hash * 31 + VendorID();
|
||||
hash = hash * 31 + ProductID();
|
||||
hash = hash * 31 + path.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void Connect()
|
||||
{
|
||||
SetProvider();
|
||||
HidSharp.DeviceList.Local.Changed += Device_Changed;
|
||||
}
|
||||
|
||||
private void Device_Changed(object? sender, HidSharp.DeviceListChangedEventArgs e)
|
||||
{
|
||||
//Use this to validate whether the device is still connected.
|
||||
//If not, this will also initiate the disconnect and cleanup sequence.
|
||||
ReadBattery();
|
||||
}
|
||||
|
||||
public bool IsDeviceConnected()
|
||||
{
|
||||
try
|
||||
{
|
||||
HidSharp.DeviceList.Local.GetHidDevices(VendorID(), ProductID())
|
||||
.First(x => x.DevicePath.Contains(path));
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetProvider()
|
||||
{
|
||||
_usbProvider = new WindowsUsbProvider(_vendorId, _productId, path);
|
||||
}
|
||||
|
||||
protected virtual void OnDisconnect()
|
||||
{
|
||||
if (Disconnect is not null)
|
||||
{
|
||||
Disconnect(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.Synchronized)]
|
||||
protected virtual byte[]? WriteForResponse(byte[] packet)
|
||||
{
|
||||
byte[] response = new byte[ASUS_MOUSE_PACKET_SIZE];
|
||||
|
||||
try
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Sending packet: " + ByteArrayToString(packet));
|
||||
Write(packet);
|
||||
|
||||
Read(response);
|
||||
Logger.WriteLine(GetDisplayName() + ": Read packet: " + ByteArrayToString(response));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Failed to read packet " + e.Message);
|
||||
OnDisconnect();
|
||||
return null;
|
||||
}
|
||||
catch (System.TimeoutException e)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Timeout reading packet " + e.Message);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
public abstract string GetDisplayName();
|
||||
|
||||
|
||||
public virtual void SynchronizeMouse()
|
||||
{
|
||||
DpiSettings = new AsusMouseDPI[DPIProfileCount()];
|
||||
ReadBattery();
|
||||
if (Wireless && Battery == 0 && Charging == false)
|
||||
{
|
||||
//Likely only the dongle connected and the mouse is either sleeping or turned off.
|
||||
//The mouse will not respond with proper data, but empty responses at this point
|
||||
MouseIsReady = false;
|
||||
return;
|
||||
}
|
||||
MouseIsReady = true;
|
||||
|
||||
ReadProfile();
|
||||
ReadDPI();
|
||||
ReadLightingSetting();
|
||||
ReadLiftOffDistance();
|
||||
ReadPollingRate();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Battery
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
protected virtual bool HasEnergySettings()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetBatteryReportPacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x07 };
|
||||
}
|
||||
|
||||
protected virtual int ParseBattery(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x07)
|
||||
{
|
||||
return packet[5];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
protected virtual bool ParseChargingState(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x07)
|
||||
{
|
||||
return packet[10] > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual PowerOffSetting ParsePowerOffSetting(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x07)
|
||||
{
|
||||
return (PowerOffSetting)packet[6];
|
||||
}
|
||||
|
||||
return PowerOffSetting.Never;
|
||||
}
|
||||
protected virtual int ParseLowBatteryWarning(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x07)
|
||||
{
|
||||
return packet[7];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void ReadBattery()
|
||||
{
|
||||
byte[]? response = WriteForResponse(GetBatteryReportPacket());
|
||||
if (response is null) return;
|
||||
|
||||
Battery = ParseBattery(response);
|
||||
Charging = ParseChargingState(response);
|
||||
PowerOffSetting = ParsePowerOffSetting(response);
|
||||
LowBatteryWarning = ParseLowBatteryWarning(response);
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Got Battery Percentage " + Battery + "% - Charging:" + Charging);
|
||||
Logger.WriteLine(GetDisplayName() + ": Got Auto Power Off: " + PowerOffSetting + " - Low Battery Warnning at: " + LowBatteryWarning + "%");
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Profiles
|
||||
// ------------------------------------------------------------------------------
|
||||
public abstract int ProfileCount();
|
||||
|
||||
protected virtual bool HasProfiles()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual int ParseProfile(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x00 && packet[3] == 0x00)
|
||||
{
|
||||
return packet[11];
|
||||
}
|
||||
Logger.WriteLine(GetDisplayName() + ": Failed to decode active profile");
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected virtual int ParseDPIProfile(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x00 && packet[3] == 0x00)
|
||||
{
|
||||
return packet[12];
|
||||
}
|
||||
Logger.WriteLine(GetDisplayName() + ": Failed to decode active profile");
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadProfilePacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x00 };
|
||||
}
|
||||
|
||||
protected virtual byte[] GetUpdateProfilePacket(int profile)
|
||||
{
|
||||
return new byte[] { 0x00, 0x50, 0x02, (byte)profile };
|
||||
}
|
||||
|
||||
public void ReadProfile()
|
||||
{
|
||||
if (!HasProfiles())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? response = WriteForResponse(GetReadProfilePacket());
|
||||
if (response is null) return;
|
||||
|
||||
Profile = ParseProfile(response);
|
||||
if (DPIProfileCount() > 1)
|
||||
{
|
||||
|
||||
DpiProfile = ParseDPIProfile(response);
|
||||
}
|
||||
Logger.WriteLine(GetDisplayName() + ": Active Profile " + (Profile + 1)
|
||||
+ ((DPIProfileCount() > 1 ? ", Active DPI Profile: " + DpiProfile : "")));
|
||||
}
|
||||
|
||||
public void SetProfile(int profile)
|
||||
{
|
||||
if (!HasProfiles())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile > ProfileCount() || profile < 0)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Profile:" + profile + " is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdateProfilePacket(profile));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Profile set to " + profile);
|
||||
this.Profile = profile;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Polling Rate and Angle Snapping
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
public int PollingRate()
|
||||
{
|
||||
return PollingRate1;
|
||||
}
|
||||
|
||||
public virtual bool HasAngleSnapping()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract string PollingRateDisplayString(int pollingRate);
|
||||
public abstract int PollingRateCount();
|
||||
|
||||
|
||||
protected virtual bool CanSetPollingRate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadPollingRatePacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x04, 0x00 };
|
||||
}
|
||||
|
||||
protected virtual byte[] GetUpdatePollingRatePacket(int pollingRate)
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x04, 0x00, (byte)pollingRate };
|
||||
}
|
||||
protected virtual byte[] GetUpdateAngleSnappingPacket(bool angleSnappin)
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x06, 0x00, (byte)(AngleSnapping ? 0x01 : 0x00) };
|
||||
}
|
||||
protected virtual byte[] GetUpdateAngleAdjustmentPacket(short angleAdjustment)
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x0B, 0x00, (byte)(angleAdjustment & 0xFF), (byte)((angleAdjustment >> 8) & 0xFF) };
|
||||
}
|
||||
|
||||
protected virtual int ParsePollingRate(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x04 && packet[3] == 0x00)
|
||||
{
|
||||
return packet[13];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected virtual bool ParseAngleSnapping(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x04 && packet[3] == 0x00)
|
||||
{
|
||||
return packet[17] == 0x01;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual short ParseAngleAdjustment(byte[] packet)
|
||||
{
|
||||
if (packet[1] == 0x12 && packet[2] == 0x04 && packet[3] == 0x00)
|
||||
{
|
||||
return (short)(packet[19] << 8 | packet[20]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void ReadPollingRate()
|
||||
{
|
||||
if (!CanSetPollingRate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? response = WriteForResponse(GetReadPollingRatePacket());
|
||||
if (response is null) return;
|
||||
|
||||
PollingRate1 = ParsePollingRate(response);
|
||||
Logger.WriteLine(GetDisplayName() + ": Pollingrate: " + PollingRateDisplayString(PollingRate1) + " (" + PollingRate1 + ")");
|
||||
|
||||
if (HasAngleSnapping())
|
||||
{
|
||||
AngleSnapping = ParseAngleSnapping(response);
|
||||
AngleAdjustmentDegrees = ParseAngleAdjustment(response);
|
||||
Logger.WriteLine(GetDisplayName() + ": Angle Snapping enabled: " + AngleSnapping + ", Angle Adjustment: " + AngleAdjustmentDegrees + "°");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPollingRate(int pollingRate)
|
||||
{
|
||||
if (!CanSetPollingRate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (pollingRate > PollingRateCount() || pollingRate < 1)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Pollingrate:" + pollingRate + " is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdatePollingRatePacket(pollingRate));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Pollingrate set to " + PollingRateDisplayString(pollingRate));
|
||||
this.PollingRate1 = pollingRate;
|
||||
}
|
||||
|
||||
public void SetAngleSnapping(bool angleSnapping)
|
||||
{
|
||||
if (!HasAngleSnapping())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdateAngleSnappingPacket(angleSnapping));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Angle Snapping set to " + angleSnapping);
|
||||
this.AngleSnapping = angleSnapping;
|
||||
}
|
||||
|
||||
public void SetAngleAdjustment(short angleAdjustment)
|
||||
{
|
||||
if (!HasAngleSnapping())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (angleAdjustment < -20 || angleAdjustment > 20)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Angle Adjustment:" + angleAdjustment + " is outside of range [-20;20].");
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdateAngleAdjustmentPacket(angleAdjustment));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Angle Adjustment set to " + angleAdjustment);
|
||||
this.AngleAdjustmentDegrees = angleAdjustment;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// DPI
|
||||
// ------------------------------------------------------------------------------
|
||||
protected abstract int DPIProfileCount();
|
||||
protected virtual bool HasDPIColors()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool CanChangeDPIProfile()
|
||||
{
|
||||
return DPIProfileCount() > 1;
|
||||
}
|
||||
|
||||
protected virtual int MaxDPI()
|
||||
{
|
||||
return 2000;
|
||||
}
|
||||
protected virtual int MinDPI()
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetChangeDPIProfilePacket(int profile)
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x09, 0x00, (byte)profile };
|
||||
}
|
||||
|
||||
//There is no API to read this out from the mouse. Armoury Crate also doesn't read it.
|
||||
//You can however get the HID button event when you change the profile via a mouse button which tells you the profile it was set to
|
||||
public void SetDPIProfile(int profile)
|
||||
{
|
||||
if (!CanChangeDPIProfile())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile >= DPIProfileCount() || profile < 0)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": DPI Profile:" + profile + " is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
//The first DPI profile is 1
|
||||
WriteForResponse(GetChangeDPIProfilePacket(profile + 1));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": DPI Profile set to " + profile);
|
||||
this.DpiProfile = profile;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadDPIPacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x04, 0x02 };
|
||||
}
|
||||
|
||||
protected virtual byte[]? GetUpdateDPIPacket(int profile)
|
||||
{
|
||||
AsusMouseDPI dpi = DpiSettings[profile];
|
||||
if (dpi is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (dpi.DPI > MaxDPI() || dpi.DPI < MinDPI())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ushort dpiEncoded = (ushort)((dpi.DPI - 50) / 50);
|
||||
|
||||
if (HasDPIColors())
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x00, (byte)(dpiEncoded & 0xFF), (byte)((dpiEncoded >> 8) & 0xFF), (byte)profile, dpi.Color.R, dpi.Color.G, dpi.Color.B };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x31, 0x00, (byte)(dpiEncoded & 0xFF), (byte)((dpiEncoded >> 8) & 0xFF), (byte)profile };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected virtual void ParseDPI(byte[] packet)
|
||||
{
|
||||
if (packet[1] != 0x12 || packet[2] != 0x04 || packet[3] != 0x02)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < DPIProfileCount(); ++i)
|
||||
{
|
||||
if (DpiSettings[i] is null)
|
||||
{
|
||||
DpiSettings[i] = new AsusMouseDPI();
|
||||
}
|
||||
|
||||
int offset = 4 + (i * 4);
|
||||
|
||||
DpiSettings[i].DPI = (uint)(packet[offset] << 8 | packet[offset + 1]) * 50 + 50;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadDPIColorsPacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x04, 0x03 };
|
||||
}
|
||||
|
||||
protected virtual void ParseDPIColors(byte[] packet)
|
||||
{
|
||||
if (packet[1] != 0x12 || packet[2] != 0x04 || packet[3] != 0x03)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < DPIProfileCount(); ++i)
|
||||
{
|
||||
if (DpiSettings[i] is null)
|
||||
{
|
||||
DpiSettings[i] = new AsusMouseDPI();
|
||||
}
|
||||
|
||||
int offset = 5 + (i * 3);
|
||||
|
||||
DpiSettings[i].Color = Color.FromArgb(packet[offset], packet[offset + 1], packet[offset + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadDPI()
|
||||
{
|
||||
byte[]? response = WriteForResponse(GetReadDPIPacket());
|
||||
if (response is null) return;
|
||||
ParseDPI(response);
|
||||
|
||||
if (HasDPIColors())
|
||||
{
|
||||
response = WriteForResponse(GetReadDPIColorsPacket());
|
||||
if (response is null) return;
|
||||
ParseDPIColors(response);
|
||||
}
|
||||
|
||||
for (int i = 0; i < DPIProfileCount(); ++i)
|
||||
{
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Read DPI Setting " + (i + 1) + ": " + DpiSettings[i].ToString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetDPIForProfile(int profile)
|
||||
{
|
||||
if (profile >= DPIProfileCount() || profile < 0)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": DPI Profile:" + profile + " is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? packet = GetUpdateDPIPacket(profile);
|
||||
if (packet == null)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": DPI setting for profile " + profile + " does not exist or is invalid.");
|
||||
return;
|
||||
}
|
||||
WriteForResponse(packet);
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": DPI for profile " + profile + " set to " + DpiSettings[profile].DPI);
|
||||
this.DpiProfile = profile;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Lift-off Distance
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
protected virtual bool HasLiftOffSetting()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadLiftOffDistancePacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x06 };
|
||||
}
|
||||
|
||||
//This also resets the "calibration" to default. There is no seperate command to only set the lift off distance
|
||||
protected virtual byte[] GetUpdateLiftOffDistancePacket(LiftOffDistance liftOffDistance)
|
||||
{
|
||||
return new byte[] { 0x00, 0x51, 0x35, 0xFF, 0x00, 0xFF, ((byte)liftOffDistance) };
|
||||
}
|
||||
|
||||
protected virtual LiftOffDistance ParseLiftOffDistance(byte[] packet)
|
||||
{
|
||||
if (packet[1] != 0x12 || packet[2] != 0x06)
|
||||
{
|
||||
return LiftOffDistance.Low;
|
||||
}
|
||||
|
||||
return (LiftOffDistance)packet[8];
|
||||
}
|
||||
|
||||
public void ReadLiftOffDistance()
|
||||
{
|
||||
if (!HasLiftOffSetting())
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[]? response = WriteForResponse(GetReadLiftOffDistancePacket());
|
||||
if (response is null) return;
|
||||
|
||||
LiftOffDistance = ParseLiftOffDistance(response);
|
||||
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Read Lift Off Setting: " + LiftOffDistance);
|
||||
}
|
||||
|
||||
public void SetLiftOffDistance(LiftOffDistance liftOffDistance)
|
||||
{
|
||||
if (!HasLiftOffSetting())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdateLiftOffDistancePacket(liftOffDistance));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Set Liftoff Distance to " + liftOffDistance);
|
||||
this.LiftOffDistance = liftOffDistance;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// RGB
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
protected virtual bool HasRGB()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Override to remap lighting mode IDs.
|
||||
//From OpenRGB code it looks like some mice have different orders of the modes or do not support some modes at all.
|
||||
protected virtual byte IndexForLightingMode(LightingMode lightingMode)
|
||||
{
|
||||
return ((byte)lightingMode);
|
||||
}
|
||||
|
||||
//Also override this for the reverse mapping
|
||||
protected virtual LightingMode LightingModeForIndex(byte lightingMode)
|
||||
{
|
||||
return ((LightingMode)lightingMode);
|
||||
}
|
||||
|
||||
//And this if not all modes are supported
|
||||
protected virtual bool IsLightingModeSupported(LightingMode lightingMode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual byte[] GetReadLightingModePacket()
|
||||
{
|
||||
return new byte[] { 0x00, 0x12, 0x03 };
|
||||
}
|
||||
|
||||
protected virtual byte[] GetUpdateLightingModePacket(LightingSetting lightingSetting)
|
||||
{
|
||||
if (lightingSetting.Brightness < 0 || lightingSetting.Brightness > 100)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Brightness " + lightingSetting.Brightness + " is out of range [0;100]. Setting to 25.");
|
||||
lightingSetting.Brightness = 25;
|
||||
}
|
||||
if (!IsLightingModeSupported(lightingSetting.LightingMode))
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Lighting Mode " + lightingSetting.LightingMode + " is not supported. Setting to Rainbow ;)");
|
||||
lightingSetting.LightingMode = LightingMode.Rainbow;
|
||||
}
|
||||
|
||||
return new byte[] { 0x00, 0x51, 0x28, 0x03, 0x00,
|
||||
IndexForLightingMode(lightingSetting.LightingMode),
|
||||
(byte)lightingSetting.Brightness,
|
||||
lightingSetting.RGBColor.R, lightingSetting.RGBColor.G, lightingSetting.RGBColor.B,
|
||||
(byte)lightingSetting.AnimationDirection,
|
||||
(byte)(lightingSetting.RandomColor ? 0x01: 0x00),
|
||||
(byte)lightingSetting.LightingSpeed
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual LightingSetting? ParseLightingSetting(byte[] packet)
|
||||
{
|
||||
if (packet[1] != 0x12 || packet[2] != 0x03)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
LightingSetting setting = new LightingSetting();
|
||||
|
||||
setting.LightingMode = LightingModeForIndex(packet[5]);
|
||||
setting.Brightness = packet[6];
|
||||
|
||||
setting.RGBColor = Color.FromArgb(packet[7], packet[8], packet[9]);
|
||||
setting.AnimationDirection = (AnimationDirection)packet[10];
|
||||
setting.RandomColor = packet[11] == 0x01;
|
||||
setting.LightingSpeed = (LightingSpeed)packet[12];
|
||||
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
public void ReadLightingSetting()
|
||||
{
|
||||
if (!HasRGB())
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[]? response = WriteForResponse(GetReadLightingModePacket());
|
||||
if (response is null) return;
|
||||
|
||||
LightingSetting = ParseLightingSetting(response);
|
||||
|
||||
if (LightingSetting is not null)
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Read RGB Setting" + LightingSetting.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.WriteLine(GetDisplayName() + ": Failed to read RGB Setting");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLightingSettinge(LightingSetting lightingSetting)
|
||||
{
|
||||
if (!HasRGB() || lightingSetting is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteForResponse(GetUpdateLightingModePacket(lightingSetting));
|
||||
|
||||
Logger.WriteLine(GetDisplayName() + ": Set RGB Setting " + lightingSetting.ToString());
|
||||
this.LightingSetting = lightingSetting;
|
||||
}
|
||||
|
||||
public override string? ToString()
|
||||
{
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static string ByteArrayToString(byte[] packet)
|
||||
{
|
||||
StringBuilder hex = new StringBuilder(packet.Length * 2);
|
||||
foreach (byte b in packet)
|
||||
hex.AppendFormat("{0:x2} ", b);
|
||||
return hex.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user