Skip to content

Commit

Permalink
add Razer Mouse Battery node
Browse files Browse the repository at this point in the history
  • Loading branch information
Aytackydln committed Jul 9, 2024
1 parent 7710197 commit a025f0e
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace AuroraRgb.Modules.OnlineConfigs.Model;

public class RazerDevices
{
public Dictionary<string, RazerMouseHidInfo> MouseHidInfos { get; set; } = new();
}

public class RazerMouseHidInfo
{
public string Name { get; set; } = string.Empty;
public string TransactionId { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ namespace AuroraRgb.Modules.OnlineConfigs;
[JsonSerializable(typeof(DeviceTooltips))]
[JsonSerializable(typeof(ConflictingProcesses))]
[JsonSerializable(typeof(Dictionary<string, DeviceTooltips>))]
[JsonSerializable(typeof(RazerDevices))]
internal partial class OnlineSettingsSourceGenerationContext : JsonSerializerContext;

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()
{
Expand All @@ -48,6 +51,11 @@ public static async Task<OnlineSettingsMeta> GetOnlineSettingsLocal()
return await ParseLocalJson<OnlineSettingsMeta>(OnlineSettingsLocalCache);
}

public static async Task<RazerDevices> GetRazerDeviceInfo()
{
return await ParseLocalJson<RazerDevices>(RazerDeviceInfoLocalCache);
}

public static async Task<OnlineSettingsMeta> GetOnlineSettingsOnline()
{
var stream = await ReadOnlineJson(OnlineSettings);
Expand Down
17 changes: 17 additions & 0 deletions Project-Aurora/Project-Aurora/Modules/OnlineSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public sealed class OnlineSettings(Task<RunningProcessMonitor> runningProcessMon
{
public static Dictionary<string, DeviceTooltips> DeviceTooltips { get; private set; } = new();

public static RazerDevices RazerDeviceInfo { get; private set; } = new();

private Dictionary<string, ShutdownProcess> _shutdownProcesses = new();
private readonly TaskCompletionSource _layoutUpdateTaskSource = new();

Expand Down Expand Up @@ -88,6 +90,7 @@ private async Task DownloadAndExtract()

if (commitDate <= localSettingsDate)
{
// no update required
return;
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions Project-Aurora/Project-Aurora/Nodes/LocalPCInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
148 changes: 148 additions & 0 deletions Project-Aurora/Project-Aurora/Nodes/Razer/RazerBatteryFetcher.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
11 changes: 11 additions & 0 deletions Project-Aurora/Project-Aurora/Nodes/RazerDevices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using AuroraRgb.Nodes.Razer;
using AuroraRgb.Utils;

namespace AuroraRgb.Nodes;

public class RazerDevices : Node
{
private Temporary<RazerBatteryFetcher> _razerBatteryFetcher = new(() => new RazerBatteryFetcher());

public double MouseBatteryPercentage => _razerBatteryFetcher.Value.MouseBatteryPercentage;
}
1 change: 1 addition & 0 deletions Project-Aurora/Project-Aurora/Project-Aurora.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
<PackageReference Include="JsonSubTypes" Version="2.0.1"/>
<PackageReference Include="LibreHardwareMonitorLib" Version="0.9.2"/>
<PackageReference Include="LibUsbDotNet" Version="3.0.102-alpha" />
<PackageReference Include="MdXaml" Version="1.27.0"/>
<PackageReference Include="Microsoft.Experimental.Collections" Version="1.0.6-e190117-3"/>
<PackageReference Include="NAudio.Core" Version="2.2.1"/>
Expand Down
12 changes: 12 additions & 0 deletions Project-Aurora/Project-Aurora/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -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, )",
Expand Down Expand Up @@ -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, )",
Expand Down

0 comments on commit a025f0e

Please sign in to comment.