Skip to content
This repository was archived by the owner on Jul 24, 2025. It is now read-only.
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
3 changes: 3 additions & 0 deletions LenovoLegionToolkit.Lib.Automation/AutomationEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class AutomationEnvironment
private const string USER_ACTIVE = "LLT_IS_USER_ACTIVE";
private const string WIFI_CONNECTED = "LLT_WIFI_CONNECTED";
private const string WIFI_SSID = "LLT_WIFI_SSID";
private const string SESSION_LOCKED = "LLT_SESSION_LOCKED";

private const string VALUE_TRUE = "TRUE";
private const string VALUE_FALSE = "FALSE";
Expand Down Expand Up @@ -98,6 +99,8 @@ public PowerModeState PowerMode

public string? WiFiSsid { set => _dictionary[WIFI_SSID] = value; }

public bool SessionLocked { set => _dictionary[SESSION_LOCKED] = value ? VALUE_TRUE : VALUE_FALSE; }

public Dictionary<string, string?> Dictionary => new(_dictionary);

private readonly Dictionary<string, string?> _dictionary = [];
Expand Down
8 changes: 8 additions & 0 deletions LenovoLegionToolkit.Lib.Automation/AutomationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class AutomationProcessor(
GodModeController godModeController,
GameAutoListener gameAutoListener,
ProcessAutoListener processAutoListener,
SessionLockUnlockListener sessionLockUnlockListener,
TimeAutoListener timeAutoListener,
UserInactivityAutoListener userInactivityAutoListener,
WiFiAutoListener wifiAutoListener)
Expand All @@ -48,6 +49,7 @@ public async Task InitializeAsync()
powerStateListener.Changed += PowerStateListener_Changed;
powerModeListener.Changed += PowerModeListener_Changed;
godModeController.PresetChanged += GodModeController_PresetChanged;
sessionLockUnlockListener.Changed += SessionLockUnlockListener_Changed;

_pipelines = [.. settings.Store.Pipelines];

Expand Down Expand Up @@ -277,6 +279,12 @@ private async void ProcessAutoListener_Changed(object? sender, ProcessAutoListen
await ProcessEvent(e).ConfigureAwait(false);
}

private async void SessionLockUnlockListener_Changed(object? sender, SessionLockUnlockListener.ChangedEventArgs args)
{
var e = new SessionLockUnlockAutomationEvent(args.Locked);
await ProcessEvent(e).ConfigureAwait(false);
}

private async void TimeAutoListener_Changed(object? sender, TimeAutoListener.ChangedEventArgs args)
{
var e = new TimeAutomationEvent(args.Time, args.Day);
Expand Down
5 changes: 5 additions & 0 deletions LenovoLegionToolkit.Lib.Automation/IAutomationEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public readonly struct ProcessAutomationEvent(ProcessEventInfoType type, Process
public ProcessInfo ProcessInfo { get; } = processInfo;
}

public readonly struct SessionLockUnlockAutomationEvent(bool locked) : IAutomationEvent
{
public bool Locked { get; } = locked;
}

public readonly struct TimeAutomationEvent(Time time, DayOfWeek day) : IAutomationEvent
{
public Time Time { get; } = time;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public interface IProcessesAutomationPipelineTrigger : IAutomationPipelineTrigge
IProcessesAutomationPipelineTrigger DeepCopy(ProcessInfo[] processes);
}

public interface ISessionLockPipelineTrigger : IDisallowDuplicatesAutomationPipelineTrigger;

public interface ISessionUnlockPipelineTrigger : IDisallowDuplicatesAutomationPipelineTrigger;

public interface ITimeAutomationPipelineTrigger : IAutomationPipelineTrigger
{
bool IsSunrise { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.Automation.Resources;
using LenovoLegionToolkit.Lib.Listeners;
using Newtonsoft.Json;

namespace LenovoLegionToolkit.Lib.Automation.Pipeline.Triggers;

public class SessionLockAutomationPipelineTrigger : ISessionLockPipelineTrigger
{
[JsonIgnore]
public string DisplayName => Resource.SessionLockAutomationPipelineTrigger_DisplayName;

public Task<bool> IsMatchingEvent(IAutomationEvent automationEvent)
{
var result = automationEvent is SessionLockUnlockAutomationEvent { Locked: true };
return Task.FromResult(result);
}

public Task<bool> IsMatchingState()
{
var listener = IoCContainer.Resolve<SessionLockUnlockListener>();
var result = listener.IsLocked;
return Task.FromResult(result.HasValue && result.Value);
}

public void UpdateEnvironment(AutomationEnvironment environment) => environment.SessionLocked = true;

public IAutomationPipelineTrigger DeepCopy() => new SessionLockAutomationPipelineTrigger();

public override bool Equals(object? obj) => obj is SessionLockAutomationPipelineTrigger;

public override int GetHashCode() => HashCode.Combine(DisplayName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.Automation.Resources;
using LenovoLegionToolkit.Lib.Listeners;
using Newtonsoft.Json;

namespace LenovoLegionToolkit.Lib.Automation.Pipeline.Triggers;

public class SessionUnlockAutomationPipelineTrigger : ISessionUnlockPipelineTrigger
{
[JsonIgnore]
public string DisplayName => Resource.SessionUnlockAutomationPipelineTrigger_DisplayName;

public Task<bool> IsMatchingEvent(IAutomationEvent automationEvent)
{
var result = automationEvent is SessionLockUnlockAutomationEvent { Locked: false };
return Task.FromResult(result);
}

public Task<bool> IsMatchingState()
{
var listener = IoCContainer.Resolve<SessionLockUnlockListener>();
var result = listener.IsLocked;
return Task.FromResult(result.HasValue && !result.Value);
}

public void UpdateEnvironment(AutomationEnvironment environment) => environment.SessionLocked = false;

public IAutomationPipelineTrigger DeepCopy() => new SessionUnlockAutomationPipelineTrigger();

public override bool Equals(object? obj) => obj is SessionUnlockAutomationPipelineTrigger;

public override int GetHashCode() => HashCode.Combine(DisplayName);
}
18 changes: 18 additions & 0 deletions LenovoLegionToolkit.Lib.Automation/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.Lib.Automation/Resources/Resource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,10 @@
<data name="DeviceConnectedAutomationPipelineTrigger_DisplayName" xml:space="preserve">
<value>When device is connected</value>
</data>
<data name="SessionLockAutomationPipelineTrigger_DisplayName" xml:space="preserve">
<value>Session locked</value>
</data>
<data name="SessionUnlockAutomationPipelineTrigger_DisplayName" xml:space="preserve">
<value>Session unlocked</value>
</data>
</root>
1 change: 1 addition & 0 deletions LenovoLegionToolkit.Lib/IoCModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ protected override void Load(ContainerBuilder builder)
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();
Expand Down
74 changes: 74 additions & 0 deletions LenovoLegionToolkit.Lib/Listeners/SessionLockUnlockListener.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using LenovoLegionToolkit.Lib.Utils;
using Microsoft.Win32;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.RemoteDesktop;

namespace LenovoLegionToolkit.Lib.Listeners;

public class SessionLockUnlockListener : IListener<SessionLockUnlockListener.ChangedEventArgs>
{
public class ChangedEventArgs(bool locked) : EventArgs
{
public bool Locked { get; } = locked;
}

public event EventHandler<ChangedEventArgs>? Changed;

public bool? IsLocked { get; private set; }

public Task StartAsync()
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
return Task.CompletedTask;
}

public Task StopAsync()
{
SystemEvents.SessionSwitch -= SystemEvents_SessionSwitch;
return Task.CompletedTask;
}

private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
if (e.Reason != SessionSwitchReason.SessionLock && e.Reason != SessionSwitchReason.SessionUnlock)
return;

var flags = GetActiveConsoleSessionFlags();
if (flags == PInvoke.WTS_SESSIONSTATE_UNKNOWN)
{
IsLocked = null;
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Unknown error occured when getting active console session flags.");
return;
}
var locked = (flags == PInvoke.WTS_SESSIONSTATE_LOCK);
if (Log.Instance.IsTraceEnabled)
Log.Instance.Trace($"Session lock unlock state switched. [locked={locked}]");
IsLocked = locked;
Changed?.Invoke(this, new(locked));
}

private unsafe uint GetActiveConsoleSessionFlags()
{
WTS_INFO_CLASS wtsic = WTS_INFO_CLASS.WTSSessionInfoEx;
var dwSessionId = PInvoke.WTSGetActiveConsoleSessionId();
var sessionFlags = PInvoke.WTS_SESSIONSTATE_UNKNOWN;
if (PInvoke.WTSQuerySessionInformation(HANDLE.WTS_CURRENT_SERVER_HANDLE, dwSessionId, wtsic, out var ppBuffer, out var pBytesReturned))
{
if (pBytesReturned > 0)
{
WTSINFOEXW info = Marshal.PtrToStructure<WTSINFOEXW>((IntPtr)ppBuffer.Value);
if (info.Level == 1)
{
sessionFlags = (uint)info.Data.WTSInfoExLevel1.SessionFlags;
}
}
PInvoke.WTSFreeMemory(ppBuffer);
}
return sessionFlags;
}
}
10 changes: 10 additions & 0 deletions LenovoLegionToolkit.Lib/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,13 @@ ShowWindow
UpdateLayeredWindow
WINDOW_STYLE
WINDOW_EX_STYLE

WTSINFOEXW
WTS_INFO_CLASS
WTS_SESSIONSTATE_UNKNOWN
WTSGetActiveConsoleSessionId
WTSQuerySessionInformation
WTS_CURRENT_SERVER_HANDLE
WTSFreeMemory
WTS_SESSIONSTATE_LOCK
WTS_SESSIONSTATE_UNLOCK
9 changes: 9 additions & 0 deletions LenovoLegionToolkit.WPF/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,15 @@ public async Task ShutdownAsync()
}
catch { /* Ignored. */ }

try
{
if (IoCContainer.TryResolve<SessionLockUnlockListener>() is { } sessionLockUnlockListener)
{
await sessionLockUnlockListener.StopAsync();
}
}
catch { /* Ignored. */ }

try
{
if (IoCContainer.TryResolve<HWiNFOIntegration>() is { } hwinfoIntegration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public static class AutomationPipelineTriggerExtensions
IHDRPipelineTrigger => SymbolRegular.Hdr24,
IProcessesAutomationPipelineTrigger => SymbolRegular.WindowConsole20,
IUserInactivityPipelineTrigger => SymbolRegular.ClockAlarm24,
ISessionLockPipelineTrigger => SymbolRegular.LockClosed24,
ISessionUnlockPipelineTrigger => SymbolRegular.LockOpen24,
ITimeAutomationPipelineTrigger => SymbolRegular.HourglassHalf24,
IDeviceAutomationPipelineTrigger => SymbolRegular.UsbPlug24,
INativeWindowsMessagePipelineTrigger => SymbolRegular.Desktop24,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public partial class CreateAutomationPipelineWindow
new ProcessesStopRunningAutomationPipelineTrigger([]),
new UserInactivityAutomationPipelineTrigger(TimeSpan.Zero),
new UserInactivityAutomationPipelineTrigger(TimeSpan.FromMinutes(1)),
new SessionLockAutomationPipelineTrigger(),
new SessionUnlockAutomationPipelineTrigger(),
new LidOpenedAutomationPipelineTrigger(),
new LidClosedAutomationPipelineTrigger(),
new DisplayOnAutomationPipelineTrigger(),
Expand Down