Skip to content
Merged
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
14 changes: 14 additions & 0 deletions LenovoLegionToolkit.Lib.Macro/MacroController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,26 @@ public void SetSequences(Dictionary<MacroIdentifier, MacroSequence> sequences)

public void Start()
{
// Don't install a system-wide keyboard hook unless macros are enabled. When disabled (the
// default) the hook would otherwise route every keystroke through managed code for nothing.
if (!IsEnabled)
return;

if (_kbHook != default)
return;

_kbHook = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_KEYBOARD_LL, _kbProc, HINSTANCE.Null, 0);
}

public void Stop()
{
if (_kbHook == default)
return;

PInvoke.UnhookWindowsHookEx(_kbHook);
_kbHook = default;
}

public void StartRecording(MacroRecorderSettings settings = MacroRecorderSettings.Keyboard) => _recorder.StartRecording(settings);

public void StopRecording() => _recorder.StopRecording();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Autofac;
using Autofac.Builder;
using LenovoLegionToolkit.Lib.Listeners;
using LenovoLegionToolkit.Lib.Utils;

namespace LenovoLegionToolkit.Lib.Extensions;

Expand All @@ -11,4 +12,17 @@ public static void AutoActivateListener<T>(this IRegistrationBuilder<IListener<T
{
registration.OnActivating(e => e.Instance.StartAsync().AsValueTask()).AutoActivate();
}

/// <summary>
/// Auto-activates the listener only on Lenovo-compatible hardware. On unsupported machines the
/// listener is still registered (so it can be resolved on demand) but is never started, so it
/// never subscribes to its Lenovo-only WMI/driver event source and consumes no resources.
/// </summary>
public static void AutoActivateLenovoListener<T>(this IRegistrationBuilder<IListener<T>, ConcreteReflectionActivatorData, SingleRegistrationStyle> registration) where T : EventArgs
{
if (!Compatibility.IsBasicCompatible)
return;

registration.OnActivating(e => e.Instance.StartAsync().AsValueTask()).AutoActivate();
}
}
18 changes: 11 additions & 7 deletions LenovoLegionToolkit.Lib/IoCModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,23 @@ protected override void Load(ContainerBuilder builder)
builder.Register<DGPUFeatureFlagsNotify>(true);
builder.Register<DGPUGamezoneNotify>(true);

// Generic listeners: work on any laptop, always auto-started.
builder.Register<DisplayBrightnessListener>().AutoActivateListener();
builder.Register<DisplayConfigurationListener>().AutoActivateListener();
builder.Register<DriverKeyListener>().AutoActivateListener();
builder.Register<LightingChangeListener>().AutoActivateListener();
builder.Register<NativeWindowsMessageListener>().AutoActivateListener();
builder.Register<PowerModeListener>().AutoActivateListener();
builder.Register<PowerStateListener>().AutoActivateListener();
builder.Register<RGBKeyboardBacklightListener>().AutoActivateListener();
builder.Register<SessionLockUnlockListener>().AutoActivateListener();
builder.Register<SpecialKeyListener>().AutoActivateListener();
builder.Register<SystemThemeListener>().AutoActivateListener();
builder.Register<ThermalModeListener>().AutoActivateListener();
builder.Register<WinKeyListener>().AutoActivateListener();

// Lenovo-only listeners: subscribe to Lenovo WMI/driver event sources. Auto-started only on
// compatible hardware so they consume zero resources on unsupported machines.
builder.Register<DriverKeyListener>().AutoActivateLenovoListener();
builder.Register<LightingChangeListener>().AutoActivateLenovoListener();
builder.Register<PowerModeListener>().AutoActivateLenovoListener();
builder.Register<RGBKeyboardBacklightListener>().AutoActivateLenovoListener();
builder.Register<SpecialKeyListener>().AutoActivateLenovoListener();
builder.Register<ThermalModeListener>().AutoActivateLenovoListener();
builder.Register<WinKeyListener>().AutoActivateLenovoListener();

builder.Register<GameAutoListener>();
builder.Register<InstanceStartedEventAutoAutoListener>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ public async Task StartStopIfNeededAsync()
{
await StopAsync().ConfigureAwait(false);

if (_refreshTask != null)
return;

if (_cts is not null)
await _cts.CancelAsync().ConfigureAwait(false);

_cts = new CancellationTokenSource();

var token = _cts.Token;
Expand Down
7 changes: 7 additions & 0 deletions LenovoLegionToolkit.Lib/Utils/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ public static partial class Compatibility
private static MachineInformation? _machineInformation;
private static bool? _basicCompatibility;

/// <summary>
/// Synchronous view of the basic (Lenovo hardware) compatibility result.
/// Returns <c>false</c> until <see cref="CheckBasicCompatibilityAsync"/> has run at least once.
/// Used to gate Lenovo-only background components so they consume no resources on unsupported machines.
/// </summary>
public static bool IsBasicCompatible => _basicCompatibility ?? false;

public static async Task<bool> CheckBasicCompatibilityAsync() => _basicCompatibility ??= await WMI.LenovoGameZoneData.ExistsAsync().ConfigureAwait(false);

public static async Task<(bool isCompatible, MachineInformation machineInformation)> IsCompatibleAsync()
Expand Down
31 changes: 25 additions & 6 deletions LenovoLegionToolkit.WPF/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ private async void Application_Startup(object sender, StartupEventArgs e)
WinFormsApp.SetHighDpiMode(WinFormsHighDpiMode.PerMonitorV2);
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;

// Determine basic (Lenovo hardware) compatibility BEFORE building the IoC container, so that
// Lenovo-only listeners are not auto-activated on unsupported machines.
_isCompatible = await Compatibility.CheckBasicCompatibilityAsync();

IoCContainer.Initialize(
new Lib.IoCModule(),
new Lib.Automation.IoCModule(),
Expand All @@ -125,25 +129,31 @@ private async void Application_Startup(object sender, StartupEventArgs e)

AutomationPage.EnableHybridModeAutomation = flags.EnableHybridModeAutomation;

_isCompatible = await Compatibility.CheckBasicCompatibilityAsync();
if (_isCompatible)
{
await LogSoftwareStatusAsync();
await InitBatteryFeatureAsync();
await InitRgbKeyboardControllerAsync();
await InitSpectrumKeyboardControllerAsync();
await InitGpuOverclockControllerAsync();

// Lenovo-only features: only initialize on compatible hardware.
await InitHybridModeAsync();
InitMacroController();

await IoCContainer.Resolve<AIController>().StartIfNeededAsync();
await IoCContainer.Resolve<BatteryDischargeRateMonitorService>().StartStopIfNeededAsync();

// HWiNFO integration exposes Lenovo sensor readings, so it is only meaningful here.
await IoCContainer.Resolve<HWiNFOIntegration>().StartStopIfNeededAsync();
}

// Cross-platform features (Windows power overlay fallback, processor TDP automation, CLI/IPC,
// discrete-GPU power management): available on any supported laptop.
await InitPowerModeFeatureAsync();
await InitHybridModeAsync();
await InitAutomationProcessorAsync();
InitMacroController();

await IoCContainer.Resolve<AIController>().StartIfNeededAsync();
await IoCContainer.Resolve<HWiNFOIntegration>().StartStopIfNeededAsync();
await IoCContainer.Resolve<IpcServer>().StartStopIfNeededAsync();
await IoCContainer.Resolve<BatteryDischargeRateMonitorService>().StartStopIfNeededAsync();
await IoCContainer.Resolve<GPUKeepOffMonitorService>().StartStopIfNeededAsync();

#if !DEBUG
Expand Down Expand Up @@ -276,6 +286,15 @@ public async Task ShutdownAsync()
}
catch { /* Ignored. */ }

try
{
if (IoCContainer.TryResolve<MacroController>() is { } macroController)
{
macroController.Stop();
}
}
catch { /* Ignored. */ }

Shutdown();
}

Expand Down
10 changes: 9 additions & 1 deletion LenovoLegionToolkit.WPF/Pages/MacroPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ private void MacroPage_Initialized(object? sender, EventArgs e)

private void EnableMacroToggle_Click(object sender, RoutedEventArgs e)
{
_controller.SetEnabled(_enableMacroToggle.IsChecked ?? false);
var enabled = _enableMacroToggle.IsChecked ?? false;
_controller.SetEnabled(enabled);

// Install/remove the low-level keyboard hook live. This runs on the UI thread, which owns the
// message loop the hook callback is dispatched on, so the hook only exists while macros are on.
if (enabled)
_controller.Start();
else
_controller.Stop();
}

private void NumberPadButton_Click(object sender, RoutedEventArgs e)
Expand Down
Loading