Skip to content
This repository was archived by the owner on Jul 24, 2025. It is now read-only.
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -366,17 +366,21 @@ private static Task SetValueAsync(CapabilityID id, int value)
Log.Instance.Trace($"Reading fan table data...");

var data = await WMI.LenovoFanTableData.ReadAsync().ConfigureAwait(false);
var mi = await Compatibility.GetMachineInformationAsync().ConfigureAwait(false);

var fanTableData = data
.Where(d => d.mode == (int)powerModeState + 1)
.Select(d =>
{
var type = (d.fanId, d.sensorId) switch
var type = (d.fanId, d.sensorId, mi.SmartFanVersion) switch
{
(1, 4) => FanTableType.CPU,
(1, 1) => FanTableType.CPUSensor,
(2, 5) => FanTableType.GPU,
(3, 5) => FanTableType.GPU2,
(1, 1, 8) => FanTableType.CPU,
(2, 5, 8) => FanTableType.GPU,
(4, 4, 8) => FanTableType.GPU2,
(1, 4, <= 8) => FanTableType.CPU,
(1, 1, <= 8) => FanTableType.CPUSensor,
(2, 5, <= 8) => FanTableType.GPU,
(3, 5, <= 8) => FanTableType.GPU2,
_ => FanTableType.Unknown,
};
return new FanTableData(type, d.fanId, d.sensorId, d.fanTableData, d.sensorTableData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace LenovoLegionToolkit.Lib.Controllers.Sensors;
public class SensorsController(
SensorsControllerV1 controllerV1,
SensorsControllerV2 controllerV2,
SensorsControllerV3 controllerV3)
SensorsControllerV3 controllerV3,
SensorsControllerV4 controllerV4)
: ISensorsController
{
private ISensorsController? _controller;
Expand Down Expand Up @@ -36,6 +37,9 @@ public async Task<SensorsData> GetDataAsync()
if (_controller is not null)
return _controller;

if (await controllerV4.IsSupportedAsync().ConfigureAwait(false))
return _controller = controllerV4;

if (await controllerV3.IsSupportedAsync().ConfigureAwait(false))
return _controller = controllerV3;

Expand Down
55 changes: 55 additions & 0 deletions LenovoLegionToolkit.Lib/Controllers/Sensors/SensorsControllerV4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.System.Management;
using LenovoLegionToolkit.Lib.Utils;

namespace LenovoLegionToolkit.Lib.Controllers.Sensors;

public class SensorsControllerV4(GPUController gpuController) : AbstractSensorsController(gpuController)
{
private const int CPU_SENSOR_ID = 1;
private const int GPU_SENSOR_ID = 5;
private const int CPU_FAN_ID = 1;
private const int GPU_FAN_ID = 2;

public override async Task<bool> IsSupportedAsync()
{
try
{
var result = await WMI.LenovoFanTableData.ExistsAsync(CPU_SENSOR_ID, CPU_FAN_ID).ConfigureAwait(false);
result &= await WMI.LenovoFanTableData.ExistsAsync(GPU_SENSOR_ID, GPU_FAN_ID).ConfigureAwait(false);

if (result)
_ = await GetDataAsync().ConfigureAwait(false);

return result;
}
catch (Exception ex)
{
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Error checking support. [type={GetType().Name}]", ex);

return false;
}
}

protected override async Task<int> GetCpuCurrentTemperatureAsync()
{
var value = await WMI.LenovoOtherMethod.GetFeatureValueAsync(CapabilityID.CpuCurrentTemperature).ConfigureAwait(false);
return value < 1 ? -1 : value;
}

protected override async Task<int> GetGpuCurrentTemperatureAsync()
{
var value = await WMI.LenovoOtherMethod.GetFeatureValueAsync(CapabilityID.GpuCurrentTemperature).ConfigureAwait(false);
return value < 1 ? -1 : value;
}

protected override Task<int> GetCpuCurrentFanSpeedAsync() => WMI.LenovoOtherMethod.GetFeatureValueAsync(CapabilityID.CpuCurrentFanSpeed);

protected override Task<int> GetGpuCurrentFanSpeedAsync() => WMI.LenovoOtherMethod.GetFeatureValueAsync(CapabilityID.GpuCurrentFanSpeed);

protected override Task<int> GetCpuMaxFanSpeedAsync() => WMI.LenovoFanMethod.GetCurrentFanMaxSpeedAsync(CPU_SENSOR_ID, CPU_FAN_ID);

protected override Task<int> GetGpuMaxFanSpeedAsync() => WMI.LenovoFanMethod.GetCurrentFanMaxSpeedAsync(GPU_SENSOR_ID, GPU_FAN_ID);
}
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/IoCModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ protected override void Load(ContainerBuilder builder)
builder.Register<SensorsControllerV1>(true);
builder.Register<SensorsControllerV2>(true);
builder.Register<SensorsControllerV3>(true);
builder.Register<SensorsControllerV4>(true);
builder.Register<SmartFnLockController>();
builder.Register<SpectrumKeyboardBacklightController>();
builder.Register<WindowsPowerModeController>();
Expand Down
21 changes: 21 additions & 0 deletions LenovoLegionToolkit.Lib/System/Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,27 @@ public static unsafe SafeFileHandle GetBattery(bool forceRefresh = false)
return _spectrumRgbKeyboard;
}

public static SafeFileHandle? GetSpectrumRGBKeyboard2(bool forceRefresh = false)
{
if (_spectrumRgbKeyboard is not null && !forceRefresh)
return _spectrumRgbKeyboard;

lock (Lock)
{
if (_spectrumRgbKeyboard is not null && !forceRefresh)
return _spectrumRgbKeyboard;

const ushort vendorId = 0x048D;
const ushort productIdMasked = 0xC100;
const ushort productIdMask = 0xFF00;
const ushort descriptorLength = 0x03C0;

_spectrumRgbKeyboard = FindHidDevice(vendorId, productIdMask, productIdMasked, descriptorLength);
}

return _spectrumRgbKeyboard;
}

private static unsafe SafeFileHandle? FindHidDevice(ushort vendorId, ushort productIdMask, ushort productIdMasked, ushort descriptorLength)
{
PInvoke.HidD_GetHidGuid(out var devClassHidGuid);
Expand Down
2 changes: 1 addition & 1 deletion LenovoLegionToolkit.Lib/Utils/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ private static bool GetSupportsGodModeV2(IEnumerable<PowerModeState> supportedPo
if (!supportedPowerModes.Contains(PowerModeState.GodMode))
return false;

return smartFanVersion is 6 or 7 || legionZoneVersion is 3 or 4;
return smartFanVersion is 6 or 7 or 8 || legionZoneVersion is 3 or 4 or 5;
}

private static async Task<bool> GetSupportsGSyncAsync()
Expand Down
143 changes: 97 additions & 46 deletions LenovoLegionToolkit.SpectrumTester/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ 2. Set the keyboard brightness to maximum.
");
Console.ReadKey();

var device = Devices.GetSpectrumRGBKeyboard();
var device = Devices.GetSpectrumRGBKeyboard2();

Console.WriteLine("Finding Spectrum keyboard...");

Expand All @@ -28,41 +28,95 @@ 2. Set the keyboard brightness to maximum.
Console.WriteLine("Spectrum keyboard found");
Console.WriteLine();

Console.WriteLine("Reading response for 0xD1...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownD1, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resD1);
Print(resD1.Bytes);
Console.WriteLine();
try
{
Console.WriteLine("Reading response for 0xD1...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownD1, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resD1);
Print(resD1.Bytes);

Console.WriteLine("Reading response for 0xC6...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC6, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC6);
Print(resC6.Bytes);
Console.WriteLine();
Console.WriteLine();
Console.WriteLine(resD1.Bytes[4] == 0 ? "Keyboard is RGB." : "Not compatible.");
Console.WriteLine();
}
catch
{
Console.WriteLine("Reading 0xD1 failed.");
}

Console.WriteLine("Reading response for 0x04...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.Unknown04, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE res04);
Print(res04.Bytes);
Console.WriteLine();
try
{
Console.WriteLine("Reading response for 0xC6...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC6, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC6);
Print(resC6.Bytes);
Console.WriteLine();
}
catch
{
Console.WriteLine("Reading 0xC6 failed.");
}

Console.WriteLine("Reading response for 0xC7...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC7, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC7);
Print(resC7.Bytes);
Console.WriteLine();
try
{
Console.WriteLine("Reading response for 0x04...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.Unknown04, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE res04);
Print(res04.Bytes);
Console.WriteLine();
}
catch
{
Console.WriteLine("Reading 0x04 failed.");
}

Console.WriteLine("Reading response for 0xC4 7...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC4, 7, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC47);
Print(resC47.Bytes);
Console.WriteLine();
try
{
Console.WriteLine("Reading response for 0xC7...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC7, 0, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC7);
Print(resC7.Bytes);
Console.WriteLine();
}
catch
{
Console.WriteLine("Reading 0xC7 failed.");
}

Console.WriteLine("Reading response for 0xC4 8...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC4, 8, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC48);
Print(resC48.Bytes);
Console.WriteLine();
try
{
Console.WriteLine("Reading response for 0xC4 7...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC4, 7, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC47);
Print(resC47.Bytes);
Console.WriteLine();

var spectrumLayout = (resC47.Bytes[6], resC47.Bytes[5]) switch
{
(22, 9) => "Full",
(20, 8) => "KeyboardAndFront",
(20, 7) => "KeyboardOnly",
_ => "Unknown"
};
Console.WriteLine($"Layout is {spectrumLayout}.");
}
catch
{
Console.WriteLine("Reading 0xC4 7 failed.");
}

try
{
Console.WriteLine("Reading response for 0xC4 8...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC4, 8, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC48);
Print(resC48.Bytes);
Console.WriteLine();
}
catch
{
Console.WriteLine("Reading 0xC4 8 failed.");
}

for (var i = 0; i < 10; i++)
{
Expand All @@ -73,22 +127,19 @@ 2. Set the keyboard brightness to maximum.
Console.WriteLine();
}

Console.WriteLine($"Reading response for 0xC5 8...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC5, 8, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC58);
Print(resC58.Bytes);
Console.WriteLine();

Console.WriteLine(resD1.Bytes[4] == 0 ? "Keyboard is RGB." : "Not compatible.");

var spectrumLayout = (resC47.Bytes[6], resC47.Bytes[5]) switch
try
{
Console.WriteLine($"Reading response for 0xC5 8...");
SetFeature(device, new LENOVO_SPECTRUM_GENERIC_REQUEST(LENOVO_SPECTRUM_OPERATION_TYPE.UnknownC5, 8, 0));
GetFeature(device, out LENOVO_SPECTRUM_GENERIC_RESPONSE resC58);
Print(resC58.Bytes);
Console.WriteLine();
}
catch
{
(22, 9) => "Full",
(20, 8) => "KeyboardAndFront",
(20, 7) => "KeyboardOnly",
_ => "Unknown"
};
Console.WriteLine($"Layout is {spectrumLayout}.");
Console.WriteLine("Reading 0xC5 8 failed.");
}

Console.WriteLine("Reading config complete.");
Console.WriteLine();
Console.ReadKey();
Expand Down