|
1 | 1 | 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; |
8 | 2 |
|
9 | 3 | namespace AuthenticatorChooser.WindowOpening;
|
10 | 4 |
|
11 | 5 | public interface WindowOpeningListener: IDisposable {
|
12 | 6 |
|
13 | 7 | event EventHandler<SystemWindow>? windowOpened;
|
14 |
| - event EventHandler<AutomationElement>? automationElementMaybeOpened; |
15 |
| - |
16 |
| - Stopwatch mostRecentAutomationEventReceived { get; } |
17 |
| - |
18 |
| - void listenForOpenedChildAutomationElements(string parentClass); |
19 | 8 |
|
20 | 9 | }
|
21 | 10 |
|
22 | 11 | public class WindowOpeningListenerImpl: WindowOpeningListener {
|
23 | 12 |
|
24 |
| - private static readonly Logger LOGGER = LogManager.GetCurrentClassLogger(); |
25 |
| - |
26 |
| - private static readonly TimeSpan AUTOMATION_ELEMENT_THROTTLE = TimeSpan.FromMilliseconds(300); |
27 |
| - |
28 | 13 | public event EventHandler<SystemWindow>? windowOpened;
|
29 |
| - public event EventHandler<AutomationElement>? automationElementMaybeOpened; |
30 | 14 |
|
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(); |
36 | 16 |
|
37 | 17 | public WindowOpeningListenerImpl() {
|
38 |
| - shellHook.shellEvent += onWindowOpened; |
39 |
| - throttledChildStructureChanged = Throttler.Throttle<StructureChangedEventArgs>(onChildStructureChanged, AUTOMATION_ELEMENT_THROTTLE); |
| 18 | + shellHook.shellEvent += onWindowOpened; |
40 | 19 | }
|
41 | 20 |
|
42 | 21 | private void onWindowOpened(object? sender, ShellEventArgs args) {
|
43 | 22 | 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)); |
113 | 24 | }
|
114 | 25 | }
|
115 | 26 |
|
116 | 27 | public void Dispose() {
|
117 | 28 | shellHook.shellEvent -= onWindowOpened;
|
118 | 29 | 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 |
| - |
131 | 30 | GC.SuppressFinalize(this);
|
132 | 31 | }
|
133 | 32 |
|
|
0 commit comments