Skip to content

Commit

Permalink
Fix #1018
Browse files Browse the repository at this point in the history
  • Loading branch information
BartoszCichecki committed Nov 6, 2023
1 parent 28abc93 commit 71d52eb
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 17 deletions.
91 changes: 91 additions & 0 deletions LenovoLegionToolkit.Lib/Controllers/SmartFnLockController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.Features;
using LenovoLegionToolkit.Lib.Settings;
using LenovoLegionToolkit.Lib.Utils;
using NeoSmart.AsyncLock;
using Windows.Win32;
using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;

namespace LenovoLegionToolkit.Lib.Controllers;

public class SmartFnLockController
{
private readonly AsyncLock _lock = new();

private readonly FnLockFeature _feature;
private readonly ApplicationSettings _settings;

private bool _ctrlDepressed;
private bool _shiftDepressed;
private bool _winDepressed;
private bool _altDepressed;
private bool _restoreFnLock;

public SmartFnLockController(FnLockFeature feature, ApplicationSettings settings)
{
_feature = feature ?? throw new ArgumentNullException(nameof(feature));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
}

public void OnKeyboardEvent(nuint wParam, KBDLLHOOKSTRUCT kbStruct)
{
if (!_settings.Store.SmartFnLock)
return;

Task.Run(async () =>
{
try
{
using (await _lock.LockAsync().ConfigureAwait(false))
await OnKeyboardEventAsync(wParam, kbStruct).ConfigureAwait(false);
}
catch (Exception ex)
{
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Failed to handle keyboard event.", ex);
}
});
}

private async Task OnKeyboardEventAsync(nuint wParam, KBDLLHOOKSTRUCT kbStruct)
{
if (IsModifierKeyPressed(wParam, kbStruct))
{
if (_restoreFnLock)
return;

var state = await _feature.GetStateAsync().ConfigureAwait(false);
if (state == FnLockState.Off)
return;

await _feature.SetStateAsync(FnLockState.Off).ConfigureAwait(false);
_restoreFnLock = true;
}
else if (_restoreFnLock)
{
await _feature.SetStateAsync(FnLockState.On).ConfigureAwait(false);
_restoreFnLock = false;
}
}

private bool IsModifierKeyPressed(nuint wParam, KBDLLHOOKSTRUCT kbStruct)
{
var isKeyDown = wParam == PInvoke.WM_KEYDOWN;
var vkKeyCode = (VIRTUAL_KEY)kbStruct.vkCode;

if (vkKeyCode is VIRTUAL_KEY.VK_LCONTROL or VIRTUAL_KEY.VK_RCONTROL)
_ctrlDepressed = isKeyDown;

if (vkKeyCode is VIRTUAL_KEY.VK_LSHIFT or VIRTUAL_KEY.VK_RSHIFT)
_shiftDepressed = isKeyDown;

if (vkKeyCode is VIRTUAL_KEY.VK_LWIN or VIRTUAL_KEY.VK_RWIN)
_winDepressed = isKeyDown;

_altDepressed = kbStruct.flags.HasFlag(KBDLLHOOKSTRUCT_FLAGS.LLKHF_ALTDOWN);

return _ctrlDepressed || _shiftDepressed || _winDepressed || _altDepressed;
}
}
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/IoCModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ protected override void Load(ContainerBuilder builder)
builder.Register<SensorsControllerV1>(true);
builder.Register<SensorsControllerV2>(true);
builder.Register<SensorsControllerV3>(true);
builder.Register<SmartFnLockController>();
builder.Register<SpectrumKeyboardBacklightController>();

builder.Register<UpdateChecker>();
Expand Down
42 changes: 25 additions & 17 deletions LenovoLegionToolkit.Lib/Listeners/NativeWindowsMessageListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using LenovoLegionToolkit.Lib.Controllers;
using LenovoLegionToolkit.Lib.Extensions;
using LenovoLegionToolkit.Lib.Features.Hybrid.Notify;
using LenovoLegionToolkit.Lib.Utils;
Expand All @@ -17,6 +18,7 @@ public class NativeWindowsMessageListener : NativeWindow, IListener<NativeWindow
{
private readonly IMainThreadDispatcher _mainThreadDispatcher;
private readonly DGPUNotify _dgpuNotify;
private readonly SmartFnLockController _smartFnLockController;

private readonly HOOKPROC _kbProc;

Expand All @@ -34,10 +36,11 @@ public class NativeWindowsMessageListener : NativeWindow, IListener<NativeWindow

public event EventHandler<NativeWindowsMessage>? Changed;

public NativeWindowsMessageListener(IMainThreadDispatcher mainThreadDispatcher, DGPUNotify dgpuNotify)
public NativeWindowsMessageListener(IMainThreadDispatcher mainThreadDispatcher, DGPUNotify dgpuNotify, SmartFnLockController smartFnLockController)
{
_mainThreadDispatcher = mainThreadDispatcher ?? throw new ArgumentNullException(nameof(mainThreadDispatcher));
_dgpuNotify = dgpuNotify ?? throw new ArgumentNullException(nameof(dgpuNotify));
_smartFnLockController = smartFnLockController ?? throw new ArgumentNullException(nameof(smartFnLockController));

_kbProc = LowLevelKeyboardProc;
}
Expand Down Expand Up @@ -252,25 +255,30 @@ private void OnDisplayDeviceArrival()
Changed?.Invoke(this, NativeWindowsMessage.OnDisplayDeviceArrival);
}

private static LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
private LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode == PInvoke.HC_ACTION && wParam.Value == PInvoke.WM_KEYUP)
{
var kbStruct = Marshal.PtrToStructure<KBDLLHOOKSTRUCT>(new IntPtr(lParam.Value));
if (nCode != PInvoke.HC_ACTION)
return PInvoke.CallNextHookEx(HHOOK.Null, nCode, wParam, lParam);

if (kbStruct.vkCode == (ulong)VIRTUAL_KEY.VK_CAPITAL)
{
var isOn = (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CAPITAL) & 0x1) != 0;
var type = isOn ? NotificationType.CapsLockOn : NotificationType.CapsLockOff;
MessagingCenter.Publish(new Notification(type));
}
var kbStruct = Marshal.PtrToStructure<KBDLLHOOKSTRUCT>(new IntPtr(lParam.Value));

if (kbStruct.vkCode == (ulong)VIRTUAL_KEY.VK_NUMLOCK)
{
var isOn = (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_NUMLOCK) & 0x1) != 0;
var type = isOn ? NotificationType.NumLockOn : NotificationType.NumLockOff;
MessagingCenter.Publish(new Notification(type));
}
_smartFnLockController.OnKeyboardEvent(wParam.Value, kbStruct);

if (wParam.Value != PInvoke.WM_KEYUP)
return PInvoke.CallNextHookEx(HHOOK.Null, nCode, wParam, lParam);

if (kbStruct.vkCode == (ulong)VIRTUAL_KEY.VK_CAPITAL)
{
var isOn = (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CAPITAL) & 0x1) != 0;
var type = isOn ? NotificationType.CapsLockOn : NotificationType.CapsLockOff;
MessagingCenter.Publish(new Notification(type));
}

if (kbStruct.vkCode == (ulong)VIRTUAL_KEY.VK_NUMLOCK)
{
var isOn = (PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_NUMLOCK) & 0x1) != 0;
var type = isOn ? NotificationType.NumLockOn : NotificationType.NumLockOff;
MessagingCenter.Publish(new Notification(type));
}

return PInvoke.CallNextHookEx(HHOOK.Null, nCode, wParam, lParam);
Expand Down
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
WM_COMMAND
WM_DESTROY
WM_DEVICECHANGE
WM_KEYDOWN
WM_KEYUP
WM_LBUTTONUP
WM_POWERBROADCAST
Expand Down
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/Settings/ApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class ApplicationSettingsStore
public List<Guid> SmartKeySinglePressActionList { get; set; } = new();
public List<Guid> SmartKeyDoublePressActionList { get; set; } = new();
public bool SynchronizeBrightnessToAllPowerPlans { get; set; }
public bool SmartFnLock { get; set; }
}

public ApplicationSettings() : base("settings.json")
Expand Down
11 changes: 11 additions & 0 deletions LenovoLegionToolkit.WPF/Pages/SettingsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@
Visibility="Hidden" />
</wpfui:CardControl>

<wpfui:CardControl Margin="0,0,0,8">
<wpfui:CardControl.Header>
<controls:CardHeaderControl Title="{x:Static resources:Resource.SettingsPage_SmartFnLock_Title}" Subtitle="{x:Static resources:Resource.SettingsPage_SmartFnLock_Message}" />
</wpfui:CardControl.Header>
<wpfui:ToggleSwitch
x:Name="_smartFnLockToggle"
Margin="0,0,0,8"
Click="SmartFnLockToggle_Click"
Visibility="Hidden" />
</wpfui:CardControl>

<wpfui:CardAction
x:Name="_smartKeySinglePressActionCard"
Margin="0,0,0,8"
Expand Down
12 changes: 12 additions & 0 deletions LenovoLegionToolkit.WPF/Pages/SettingsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ private async Task RefreshAsync()
_fnKeysCard.Visibility = fnKeysStatus != SoftwareStatus.NotFound ? Visibility.Visible : Visibility.Collapsed;
_fnKeysToggle.IsChecked = fnKeysStatus == SoftwareStatus.Disabled;

_smartFnLockToggle.IsChecked = _settings.Store.SmartFnLock;

_smartKeySinglePressActionCard.Visibility = fnKeysStatus != SoftwareStatus.Enabled ? Visibility.Visible : Visibility.Collapsed;
_smartKeyDoublePressActionCard.Visibility = fnKeysStatus != SoftwareStatus.Enabled ? Visibility.Visible : Visibility.Collapsed;

Expand All @@ -111,6 +113,7 @@ private async Task RefreshAsync()
_vantageToggle.Visibility = Visibility.Visible;
_legionZoneToggle.Visibility = Visibility.Visible;
_fnKeysToggle.Visibility = Visibility.Visible;
_smartFnLockToggle.Visibility = Visibility.Visible;
_synchronizeBrightnessToAllPowerPlansToggle.Visibility = Visibility.Visible;

_isRefreshing = false;
Expand Down Expand Up @@ -419,6 +422,15 @@ private async void FnKeysToggle_Click(object sender, RoutedEventArgs e)
_excludeRefreshRatesCard.Visibility = state.Value ? Visibility.Visible : Visibility.Collapsed;
}

private void SmartFnLockToggle_Click(object sender, RoutedEventArgs e)
{
if (_isRefreshing)
return;

_settings.Store.SmartFnLock = _smartFnLockToggle.IsChecked ?? false;
_settings.SynchronizeStore();
}

private void NotificationsCard_Click(object sender, RoutedEventArgs e)
{
if (_isRefreshing)
Expand Down
18 changes: 18 additions & 0 deletions LenovoLegionToolkit.WPF/Resources/Resource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions LenovoLegionToolkit.WPF/Resources/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1937,4 +1937,10 @@ Requires restart.</value>
<data name="AutomationPipelineControl_SubtitlePart_Preset" xml:space="preserve">
<value>Preset</value>
</data>
<data name="SettingsPage_SmartFnLock_Title" xml:space="preserve">
<value>Smart Fn Lock</value>
</data>
<data name="SettingsPage_SmartFnLock_Message" xml:space="preserve">
<value>Fn Lock will be temporarily disabled when Shift, Ctrl, Alt or Windows key is depressed.</value>
</data>
</root>

0 comments on commit 71d52eb

Please sign in to comment.