Skip to content

Commit df43f73

Browse files
committed
Eagerly exit process when logging out of Windows (including for computer shutdown or restart) to try to avoid crashing and showing an error dialog box. Resolve environment variables in the --log=filename path, in case the shell didn't resolve them before starting the process. Removed unused UI Automation logic in WindowOpeningListener that was only used in the chromium branch.
1 parent 13997b9 commit df43f73

File tree

6 files changed

+33
-127
lines changed

6 files changed

+33
-127
lines changed

AuthenticatorChooser/AuthenticatorChooser.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
3232
<PackageReference Include="mwinapi" Version="0.3.0.5" />
3333
<PackageReference Include="NLog" Version="5.5.0" />
34-
<PackageReference Include="System.Management" Version="9.0.5" />
34+
<PackageReference Include="System.Management" Version="9.0.6" />
3535
<PackageReference Include="ThrottleDebounce" Version="3.0.0-beta3" />
3636
<PackageReference Include="Unfucked.Windows" Version="0.0.1-beta.2" />
3737
<PackageReference Include="Workshell.PE.Resources" Version="4.0.0.147" />

AuthenticatorChooser/Logging.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ internal static class Logging {
1212
private static readonly SimpleLayout MESSAGE_FORMAT = new(
1313
" ${level:format=FirstCharacter:lowercase=true} | ${date:format=yyyy-MM-dd HH\\:mm\\:ss.fff} | ${logger:shortName=true:padding=-26} | ${message:withException=true:exceptionSeparator=\n}");
1414

15-
private static readonly LogLevel LOG_LEVEL = LogLevel.Trace;
15+
private static readonly LogLevel LOG_LEVEL = LogLevel.Debug;
1616

1717
public static void initialize(bool enableFileAppender, string? logFilename) {
18-
logFilename ??= Path.Combine(Path.GetTempPath(), Path.ChangeExtension(nameof(AuthenticatorChooser), ".log"));
18+
logFilename = logFilename != null ? Environment.ExpandEnvironmentVariables(logFilename) : Path.Combine(Path.GetTempPath(), Path.ChangeExtension(nameof(AuthenticatorChooser), ".log"));
1919

2020
LoggingConfiguration logConfig = new();
2121
ServiceRepository services = logConfig.LogFactory.ServiceRepository;

AuthenticatorChooser/Startup.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public int OnExecute() {
6060

6161
using Mutex singleInstanceLock = new(true, $@"Local\{PROGRAM_NAME}_{WindowsIdentity.GetCurrent().User?.Value}", out bool isOnlyInstance);
6262
if (!isOnlyInstance) {
63-
logger.Warn("Another older instance of {program} is already running for this user, this new instance is exiting now.", PROGRAM_NAME);
63+
logger.Warn("Another instance of {program} is already running for this user, this instance is exiting now.", PROGRAM_NAME);
6464
return 2;
6565
}
6666

@@ -88,6 +88,8 @@ public int OnExecute() {
8888
Application.Exit();
8989
};
9090

91+
SystemEvents.SessionEnding += onWindowsLogoff;
92+
9193
Application.Run();
9294
} finally {
9395
singleInstanceLock.ReleaseMutex();
@@ -139,4 +141,10 @@ Press Ctrl+C to copy this message.
139141
""", $"{PROGRAM_NAME} {PROGRAM_VERSION} usage", MessageBoxButtons.OK, MessageBoxIcon.Information);
140142
}
141143

144+
private static void onWindowsLogoff(object sender, SessionEndingEventArgs args) {
145+
logger?.Info("Exiting due to Windows session ending for {0}", args.Reason);
146+
SystemEvents.SessionEnding -= onWindowsLogoff;
147+
Application.Exit();
148+
}
149+
142150
}

AuthenticatorChooser/WindowOpening/WindowOpeningListener.cs

Lines changed: 3 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,32 @@
11
using ManagedWinapi.Windows;
2-
using NLog;
3-
using System.Collections.Concurrent;
4-
using System.Diagnostics;
5-
using System.Windows.Automation;
6-
using ThrottleDebounce;
7-
using Unfucked;
82

93
namespace AuthenticatorChooser.WindowOpening;
104

115
public interface WindowOpeningListener: IDisposable {
126

137
event EventHandler<SystemWindow>? windowOpened;
14-
event EventHandler<AutomationElement>? automationElementMaybeOpened;
15-
16-
Stopwatch mostRecentAutomationEventReceived { get; }
17-
18-
void listenForOpenedChildAutomationElements(string parentClass);
198

209
}
2110

2211
public class WindowOpeningListenerImpl: WindowOpeningListener {
2312

24-
private static readonly Logger LOGGER = LogManager.GetCurrentClassLogger();
25-
26-
private static readonly TimeSpan AUTOMATION_ELEMENT_THROTTLE = TimeSpan.FromMilliseconds(300);
27-
2813
public event EventHandler<SystemWindow>? windowOpened;
29-
public event EventHandler<AutomationElement>? automationElementMaybeOpened;
3014

31-
public Stopwatch mostRecentAutomationEventReceived { get; } = new();
32-
33-
private readonly ShellHook shellHook = new ShellHookImpl();
34-
private readonly ConcurrentDictionary<string, HashSet<AutomationElement>> watchedParentsByClass = new();
35-
private readonly RateLimitedAction<StructureChangedEventArgs> throttledChildStructureChanged;
15+
private readonly ShellHook shellHook = new ShellHookImpl();
3616

3717
public WindowOpeningListenerImpl() {
38-
shellHook.shellEvent += onWindowOpened;
39-
throttledChildStructureChanged = Throttler.Throttle<StructureChangedEventArgs>(onChildStructureChanged, AUTOMATION_ELEMENT_THROTTLE);
18+
shellHook.shellEvent += onWindowOpened;
4019
}
4120

4221
private void onWindowOpened(object? sender, ShellEventArgs args) {
4322
if (args.shellEvent == ShellEventArgs.ShellEvent.HSHELL_WINDOWCREATED) {
44-
SystemWindow window = new(args.windowHandle);
45-
windowOpened?.Invoke(this, window);
46-
47-
if (window.ClassName is { } className && watchedParentsByClass.TryGetValue(className, out HashSet<AutomationElement>? parents) && window.ToAutomationElement() is { } windowEl) {
48-
lock (parents) {
49-
parents.Add(windowEl);
50-
}
51-
listenForOpenedChildAutomationElements(windowEl);
52-
}
53-
}
54-
}
55-
56-
public void listenForOpenedChildAutomationElements(string parentClass) {
57-
mostRecentAutomationEventReceived.Restart();
58-
foreach (SystemWindow parent in SystemWindow.FilterToplevelWindows(window => window.ClassName == parentClass)) {
59-
if (parent.ToAutomationElement() is not { } parentEl) continue;
60-
61-
listenForOpenedChildAutomationElements(parentEl);
62-
watchedParentsByClass.GetOrAdd(parentClass, []).Add(parentEl);
63-
64-
foreach (AutomationElement child in parentEl.Children()) {
65-
automationElementMaybeOpened?.Invoke(this, child);
66-
}
67-
}
68-
}
69-
70-
private void listenForOpenedChildAutomationElements(AutomationElement parent) {
71-
Automation.AddStructureChangedEventHandler(parent, TreeScope.Descendants, onChildStructureChanged);
72-
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, parent, TreeScope.Element, onWindowClosed);
73-
}
74-
75-
private void unlistenForOpenedChildAutomationEvents(AutomationElement parent) {
76-
Automation.RemoveStructureChangedEventHandler(parent, onChildStructureChanged);
77-
Automation.RemoveAutomationEventHandler(WindowPattern.WindowClosedEvent, parent, onWindowClosed);
78-
}
79-
80-
private void onChildStructureChanged(object sender, AutomationEventArgs e) {
81-
mostRecentAutomationEventReceived.Restart();
82-
LOGGER.Trace("Received {change} event", (e as StructureChangedEventArgs)?.StructureChangeType);
83-
// For some reason Chromium 134 stopped emitting ChildAdded events and only fires a ChildrenReordered event when the FIDO dialog appears, not sure why
84-
if (e is StructureChangedEventArgs { StructureChangeType: StructureChangeType.ChildAdded or StructureChangeType.ChildrenBulkAdded or StructureChangeType.ChildrenReordered } args) {
85-
throttledChildStructureChanged.Invoke(args);
86-
}
87-
}
88-
89-
private void onChildStructureChanged(StructureChangedEventArgs e) {
90-
// This is stupid. UIA doesn't tell us what element was actually added when the structure changed, so we must rescan all the parents.
91-
foreach (ISet<AutomationElement> parents in watchedParentsByClass.Values) {
92-
lock (parents) {
93-
foreach (AutomationElement parent in parents) {
94-
foreach (AutomationElement child in parent.Children()) {
95-
automationElementMaybeOpened?.Invoke(this, child);
96-
}
97-
}
98-
}
99-
}
100-
}
101-
102-
private void onWindowClosed(object sender, AutomationEventArgs e) {
103-
if (e is WindowClosedEventArgs args) {
104-
foreach (HashSet<AutomationElement> parents in watchedParentsByClass.Values) {
105-
lock (parents) {
106-
foreach (AutomationElement closedParent in parents.Where(parent => parent.GetRuntimeId().SequenceEqual(args.GetRuntimeId())).ToList()) {
107-
parents.Remove(closedParent);
108-
unlistenForOpenedChildAutomationEvents(closedParent);
109-
LOGGER.Trace("Window closed, stopping listening for it opening child windows");
110-
}
111-
}
112-
}
23+
windowOpened?.Invoke(this, new SystemWindow(args.windowHandle));
11324
}
11425
}
11526

11627
public void Dispose() {
11728
shellHook.shellEvent -= onWindowOpened;
11829
shellHook.Dispose();
119-
throttledChildStructureChanged.Dispose();
120-
121-
foreach (ISet<AutomationElement> parents in watchedParentsByClass.Values) {
122-
lock (parents) {
123-
foreach (AutomationElement parent in parents) {
124-
unlistenForOpenedChildAutomationEvents(parent);
125-
}
126-
parents.Clear();
127-
}
128-
}
129-
watchedParentsByClass.Clear();
130-
13130
GC.SuppressFinalize(this);
13231
}
13332

AuthenticatorChooser/packages.lock.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
},
2929
"System.Management": {
3030
"type": "Direct",
31-
"requested": "[9.0.5, )",
32-
"resolved": "9.0.5",
33-
"contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==",
31+
"requested": "[9.0.6, )",
32+
"resolved": "9.0.6",
33+
"contentHash": "pOc5bCurWL3qVDsPP0iycUCRfXBhI/fVe44SiAlVqoZDIbHP080CLyCzfXV1UbdGKN0hQSLSHqr7OI3BhLBRbA==",
3434
"dependencies": {
35-
"System.CodeDom": "9.0.5"
35+
"System.CodeDom": "9.0.6"
3636
}
3737
},
3838
"ThrottleDebounce": {
@@ -69,8 +69,8 @@
6969
},
7070
"System.CodeDom": {
7171
"type": "Transitive",
72-
"resolved": "9.0.5",
73-
"contentHash": "cuzLM2MWutf9ZBEMPYYfd0DXwYdvntp7VCT6a/wvbKCa2ZuvGmW74xi+YBa2mrfEieAXqM4TNKlMmSnfAfpUoQ=="
72+
"resolved": "9.0.6",
73+
"contentHash": "9u1pyEykc0bWBHf1cIVwRoMqrEtxtXdC2ss1K02pvrkwHPcyYmy1glO+kLbyqPO9ehCTl+2dFyUuTUNl1Fde5g=="
7474
},
7575
"System.ComponentModel.Annotations": {
7676
"type": "Transitive",
@@ -99,11 +99,11 @@
9999
"net8.0-windows7.0/win-arm64": {
100100
"System.Management": {
101101
"type": "Direct",
102-
"requested": "[9.0.5, )",
103-
"resolved": "9.0.5",
104-
"contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==",
102+
"requested": "[9.0.6, )",
103+
"resolved": "9.0.6",
104+
"contentHash": "pOc5bCurWL3qVDsPP0iycUCRfXBhI/fVe44SiAlVqoZDIbHP080CLyCzfXV1UbdGKN0hQSLSHqr7OI3BhLBRbA==",
105105
"dependencies": {
106-
"System.CodeDom": "9.0.5"
106+
"System.CodeDom": "9.0.6"
107107
}
108108
},
109109
"Microsoft.Win32.SystemEvents": {
@@ -123,11 +123,11 @@
123123
"net8.0-windows7.0/win-x64": {
124124
"System.Management": {
125125
"type": "Direct",
126-
"requested": "[9.0.5, )",
127-
"resolved": "9.0.5",
128-
"contentHash": "n6o9PZm9p25+zAzC3/48K0oHnaPKTInRrxqFq1fi/5TPbMLjuoCm/h//mS3cUmSy+9AO1Z+qsC/Ilt/ZFatv5Q==",
126+
"requested": "[9.0.6, )",
127+
"resolved": "9.0.6",
128+
"contentHash": "pOc5bCurWL3qVDsPP0iycUCRfXBhI/fVe44SiAlVqoZDIbHP080CLyCzfXV1UbdGKN0hQSLSHqr7OI3BhLBRbA==",
129129
"dependencies": {
130-
"System.CodeDom": "9.0.5"
130+
"System.CodeDom": "9.0.6"
131131
}
132132
},
133133
"Microsoft.Win32.SystemEvents": {

Readme.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,15 @@ Even if this program doesn't click the Next button (because an extra choice was
5151
## Requirements
5252

5353
- Windows 11 [23H2](https://support.microsoft.com/en-us/topic/october-31-2023-kb5031455-os-builds-22621-2506-and-22631-2506-preview-6513c5ec-c5a2-4aaf-97f5-44c13d29e0d4) or later, or [22H2 Moment 4](https://support.microsoft.com/en-us/topic/september-26-2023-kb5030310-os-build-22621-2361-preview-363ac1ae-6ea8-41b3-b3cc-22a2a5682faf)
54-
- [.NET Desktop Runtime 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) or later
55-
- This program is compatible with **x64** and **ARM64** CPU architectures and .NET runtimes.
54+
- [.NET Desktop Runtime 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) or later, either x64 or arm64
5655

5756
## Installation
5857

5958
1. [Download the latest release ZIP archive for your CPU architecture.](https://github.com/Aldaviva/AuthenticatorChooser/releases/latest)
6059
1. Extract the `AuthenticatorChooser.exe` file from the ZIP archive to a directory of your choice, like `C:\Program Files\AuthenticatorChooser\`.
6160
1. Run the program by double-clicking `AuthenticatorChooser.exe`.
6261
- Nothing will appear because it's a background program with no UI, but you can tell it's running by searching for `AuthenticatorChooser` in Task Manager.
63-
1. Register the program to run automatically on user logon with **any one** of the following techniques. Be sure to change the example path below if you chose a different installation directory in step 2. If you'd like to specify additional command-line arguments like `--skip-all-non-security-key-options`, you can do that here too.
62+
1. Register the program to run automatically on user logon with **any one** of the following techniques. Be sure to change the example path below if you chose a different installation directory in step 2. If you'd like to specify additional [command-line arguments](https://github.com/Aldaviva/AuthenticatorChooser/wiki/Command%E2%80%90line-arguments) like `--skip-all-non-security-key-options`, you can do that here too.
6463
- Run this program once with the `--autostart-on-logon` argument
6564
```ps1
6665
.\AuthenticatorChooser --autostart-on-logon
@@ -100,9 +99,9 @@ If you want to build this application yourself instead of downloading precompile
10099
```ps1
101100
cd .\AuthenticatorChooser\AuthenticatorChooser\
102101
```
103-
1. Choose one of the [version tags](https://github.com/Aldaviva/AuthenticatorChooser/tags) to build, or skip this step to use the latest commit.
102+
1. Choose one of the [version tags](https://github.com/Aldaviva/AuthenticatorChooser/tags) to build, or skip this step to use the head commit on the master branch.
104103
```ps1
105-
git checkout 0.3.0
104+
git checkout 0.3.1
106105
```
107106
1. Build the program.
108107
```ps1

0 commit comments

Comments
 (0)