From a025f0eb80b7794836d79b5cfd572f96ca99ba68 Mon Sep 17 00:00:00 2001 From: Aytackydln Date: Tue, 9 Jul 2024 22:34:19 +0200 Subject: [PATCH] add Razer Mouse Battery node --- .../OnlineConfigs/Model/RazerDevices.cs | 14 ++ .../OnlineConfigs/OnlineConfigsRepository.cs | 8 + .../Project-Aurora/Modules/OnlineSettings.cs | 17 ++ .../Nodes/LocalPCInformation.cs | 1 + .../Nodes/Razer/RazerBatteryFetcher.cs | 148 ++++++++++++++++++ .../Project-Aurora/Nodes/RazerDevices.cs | 11 ++ .../Project-Aurora/Project-Aurora.csproj | 1 + .../Project-Aurora/packages.lock.json | 12 ++ 8 files changed, 212 insertions(+) create mode 100644 Project-Aurora/Project-Aurora/Modules/OnlineConfigs/Model/RazerDevices.cs create mode 100644 Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs create mode 100644 Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs diff --git a/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/Model/RazerDevices.cs b/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/Model/RazerDevices.cs new file mode 100644 index 000000000..f33104470 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/Model/RazerDevices.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace AuroraRgb.Modules.OnlineConfigs.Model; + +public class RazerDevices +{ + public Dictionary MouseHidInfos { get; set; } = new(); +} + +public class RazerMouseHidInfo +{ + public string Name { get; set; } = string.Empty; + public string TransactionId { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/OnlineConfigsRepository.cs b/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/OnlineConfigsRepository.cs index 34e8623d6..4b7fdb9b2 100644 --- a/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/OnlineConfigsRepository.cs +++ b/Project-Aurora/Project-Aurora/Modules/OnlineConfigs/OnlineConfigsRepository.cs @@ -15,6 +15,7 @@ namespace AuroraRgb.Modules.OnlineConfigs; [JsonSerializable(typeof(DeviceTooltips))] [JsonSerializable(typeof(ConflictingProcesses))] [JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(RazerDevices))] internal partial class OnlineSettingsSourceGenerationContext : JsonSerializerContext; public static class OnlineConfigsRepository @@ -22,10 +23,12 @@ public static class OnlineConfigsRepository private const string ConflictingProcesses = "ConflictingProcesses.json"; private const string DeviceTooltips = "DeviceInformations.json"; private const string OnlineSettings = "OnlineSettings.json"; + private const string RazerDevices = "RazerDevices.json"; private static readonly string ConflictingProcessLocalCache = Path.Combine(".", ConflictingProcesses); private static readonly string DeviceTooltipsLocalCache = Path.Combine(".", DeviceTooltips); private static readonly string OnlineSettingsLocalCache = Path.Combine(".", OnlineSettings); + private static readonly string RazerDeviceInfoLocalCache = Path.Combine(".", RazerDevices); private static readonly JsonSerializerOptions JsonSerializerOptions = new() { @@ -48,6 +51,11 @@ public static async Task GetOnlineSettingsLocal() return await ParseLocalJson(OnlineSettingsLocalCache); } + public static async Task GetRazerDeviceInfo() + { + return await ParseLocalJson(RazerDeviceInfoLocalCache); + } + public static async Task GetOnlineSettingsOnline() { var stream = await ReadOnlineJson(OnlineSettings); diff --git a/Project-Aurora/Project-Aurora/Modules/OnlineSettings.cs b/Project-Aurora/Project-Aurora/Modules/OnlineSettings.cs index 8f95a2aee..1519813ae 100644 --- a/Project-Aurora/Project-Aurora/Modules/OnlineSettings.cs +++ b/Project-Aurora/Project-Aurora/Modules/OnlineSettings.cs @@ -19,6 +19,8 @@ public sealed class OnlineSettings(Task runningProcessMon { public static Dictionary DeviceTooltips { get; private set; } = new(); + public static RazerDevices RazerDeviceInfo { get; private set; } = new(); + private Dictionary _shutdownProcesses = new(); private readonly TaskCompletionSource _layoutUpdateTaskSource = new(); @@ -88,6 +90,7 @@ private async Task DownloadAndExtract() if (commitDate <= localSettingsDate) { + // no update required return; } @@ -122,6 +125,15 @@ private async Task Refresh() { Global.logger.Error(e, "Failed to update device infos"); } + + try + { + await UpdateRazerMiceInfo(); + } + catch (Exception e) + { + Global.logger.Error(e, "Failed to update razer mice info"); + } } private async Task ExtractSettings() @@ -181,6 +193,11 @@ private static async Task UpdateDeviceInfos() DeviceTooltips = await OnlineConfigsRepository.GetDeviceTooltips(); } + private static async Task UpdateRazerMiceInfo() + { + RazerDeviceInfo = await OnlineConfigsRepository.GetRazerDeviceInfo(); + } + async Task WaitGithubAccess(TimeSpan timeout) { using var cancelSource = new CancellationTokenSource(); diff --git a/Project-Aurora/Project-Aurora/Nodes/LocalPCInformation.cs b/Project-Aurora/Project-Aurora/Nodes/LocalPCInformation.cs index d57472a5d..9310954f1 100644 --- a/Project-Aurora/Project-Aurora/Nodes/LocalPCInformation.cs +++ b/Project-Aurora/Project-Aurora/Nodes/LocalPCInformation.cs @@ -29,6 +29,7 @@ public class LocalPcInformation : Node public NETInfo NET => _netInfo ??= new NETInfo(); public Controllers Controllers { get; } = new(); + public RazerDevices RazerDevices { get; } = new(); #region Cursor Position diff --git a/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs new file mode 100644 index 000000000..f05f57209 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using System.Threading; +using AuroraRgb.Modules; +using AuroraRgb.Modules.OnlineConfigs.Model; +using LibUsbDotNet.LibUsb; +using LibUsbDotNet.Main; + +namespace AuroraRgb.Nodes.Razer; + +class RazerBatteryFetcher : IDisposable +{ + private const int HidReqSetReport = 0x09; + private const int HidReqGetReport = 0x01; // Add GET_REPORT request + private const int UsbTypeClass = 0x20; + private const int UsbRecipInterface = 0x01; + private const int UsbDirOut = 0x00; + private const int UsbDirIn = 0x80; // Direction IN for reading + private const int UsbTypeRequestOut = UsbTypeClass | UsbRecipInterface | UsbDirOut; + private const int UsbTypeRequestIn = UsbTypeClass | UsbRecipInterface | UsbDirIn; + private const int RazerUsbReportLen = 90; // Example length, set this according to actual length + + public double MouseBatteryPercentage { get; private set; } + private readonly Timer _timer; + + public RazerBatteryFetcher() + { + _timer = new Timer(_ => UpdateBattery(), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(20)); + } + + private byte[] GenerateMessage(RazerMouseHidInfo mouseHidInfo) + { + var tid = byte.Parse(mouseHidInfo.TransactionId.Split('x')[1], System.Globalization.NumberStyles.HexNumber); + var header = new byte[] { 0x00, tid, 0x00, 0x00, 0x00, 0x02, 0x07, 0x80 }; + + var crc = 0; + for (var i = 2; i < header.Length; i++) + { + crc ^= header[i]; + } + + var data = new byte[80]; + var crcData = new byte[] { (byte)crc, 0 }; + + return header.Concat(data).Concat(crcData).ToArray(); + } + + private void UpdateBattery() + { + const int vendorId = 0x1532; + + Mutex mutex = new(false, "Global\\RazerLinkReadWriteGuardMutex"); + + try + { + if (!mutex.WaitOne(TimeSpan.FromMilliseconds(2000), false)) + { + mutex.Dispose(); + return; + } + } + catch (AbandonedMutexException) + { + //continue + } + + var res = GetValue(vendorId); + mutex.Dispose(); + + if (res == null) + { + return; + } + + MouseBatteryPercentage = res[9]; + } + + private byte[]? GetValue(int vendorId) + { + var mouseDictionary = OnlineSettings.RazerDeviceInfo.MouseHidInfos; + + using var context = new UsbContext(); + var usbDevice = context.Find(d => + d.VendorId == vendorId && + mouseDictionary.ContainsKey(GetDeviceProductKeyString(d))); + if (usbDevice == null) + { + return null; + } + + var mouseHidInfo = mouseDictionary[GetDeviceProductKeyString(usbDevice)]; + var msg = GenerateMessage(mouseHidInfo); + + usbDevice.Open(); + RazerSendControlMsg(usbDevice, msg, 0x09, 200, 2000); + var res = RazerReadResponseMsg(usbDevice, 0x01); + usbDevice.Close(); + usbDevice.Dispose(); + return res; + } + + private static string GetDeviceProductKeyString(IUsbDevice device) + { + return "0x"+device.ProductId.ToString("X4"); + } + + private static void RazerSendControlMsg(IUsbDevice usbDev, byte[] data, uint reportIndex, int waitMin, int waitMax) + { + const ushort value = 0x300; + + var setupPacket = new UsbSetupPacket(UsbTypeRequestOut, HidReqSetReport, value, (ushort)reportIndex, (ushort)data.Length); + + // Send USB control message + var transferredLength = data.Length; + var ec = usbDev.ControlTransfer(setupPacket, data, 0, transferredLength); + if (ec == 0) + { + return; + } + + // Wait + var waitTime = new Random().Next(waitMin, waitMax); + Thread.Sleep(waitTime); + } + + private static byte[]? RazerReadResponseMsg(IUsbDevice usbDev, uint reportIndex) + { + const ushort value = 0x300; + var responseBuffer = new byte[RazerUsbReportLen]; + + var setupPacket = new UsbSetupPacket(UsbTypeRequestIn, HidReqGetReport, value, (ushort)reportIndex, (ushort)responseBuffer.Length); + + // Receive USB control message + var transferredLength = responseBuffer.Length; + var ec = usbDev.ControlTransfer(setupPacket, responseBuffer, 0, transferredLength); + if (ec == 0) + { + return null; + } + + return transferredLength != responseBuffer.Length ? null : responseBuffer; + } + + public void Dispose() + { + _timer.Dispose(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs b/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs new file mode 100644 index 000000000..e8ad48d76 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs @@ -0,0 +1,11 @@ +using AuroraRgb.Nodes.Razer; +using AuroraRgb.Utils; + +namespace AuroraRgb.Nodes; + +public class RazerDevices : Node +{ + private Temporary _razerBatteryFetcher = new(() => new RazerBatteryFetcher()); + + public double MouseBatteryPercentage => _razerBatteryFetcher.Value.MouseBatteryPercentage; +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Project-Aurora.csproj b/Project-Aurora/Project-Aurora/Project-Aurora.csproj index c06fb5e21..85caa2b81 100644 --- a/Project-Aurora/Project-Aurora/Project-Aurora.csproj +++ b/Project-Aurora/Project-Aurora/Project-Aurora.csproj @@ -61,6 +61,7 @@ + diff --git a/Project-Aurora/Project-Aurora/packages.lock.json b/Project-Aurora/Project-Aurora/packages.lock.json index 15b95bc61..f3899c278 100644 --- a/Project-Aurora/Project-Aurora/packages.lock.json +++ b/Project-Aurora/Project-Aurora/packages.lock.json @@ -99,6 +99,12 @@ "System.Management": "7.0.0" } }, + "LibUsbDotNet": { + "type": "Direct", + "requested": "[3.0.102-alpha, )", + "resolved": "3.0.102-alpha", + "contentHash": "OhZQNKqy4pso1YCWi3SjUCCDW05K8POwU0utIpZW2SsoHkCePSb1kAH3qBd8+sjHwRGa77U9fUn2/bXsdKOU2w==" + }, "MdXaml": { "type": "Direct", "requested": "[1.27.0, )", @@ -1501,6 +1507,12 @@ } }, "net8.0-windows10.0.19041/win-x64": { + "LibUsbDotNet": { + "type": "Direct", + "requested": "[3.0.102-alpha, )", + "resolved": "3.0.102-alpha", + "contentHash": "OhZQNKqy4pso1YCWi3SjUCCDW05K8POwU0utIpZW2SsoHkCePSb1kAH3qBd8+sjHwRGa77U9fUn2/bXsdKOU2w==" + }, "System.Management": { "type": "Direct", "requested": "[8.0.0, )",