Skip to content

Commit 2dc3950

Browse files
Merge b94f4a1 into d1ce3bc
2 parents d1ce3bc + b94f4a1 commit 2dc3950

10 files changed

Lines changed: 652 additions & 64 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
This file is a part of the NVDA project.
3+
URL: http://github.com/nvaccess/nvda/
4+
Copyright 2023 NV Access Limited.
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License version 2.0, as published by
7+
the Free Software Foundation.
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
This license can be found at:
12+
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
*/
14+
15+
#include <windows.h>
16+
#include <common/log.h>
17+
#include "rateLimitedEventHandler.h"
18+
19+
HRESULT rateLimitedUIAEventHandler_create(IUnknown* pExistingHandler, RateLimitedEventHandler** ppRateLimitedEventHandler) {
20+
LOG_DEBUG(L"rateLimitedUIAEventHandler_create called");
21+
if(!pExistingHandler || !ppRateLimitedEventHandler) {
22+
LOG_ERROR(L"rateLimitedUIAEventHandler_create: one or more NULL arguments");
23+
return E_INVALIDARG;
24+
}
25+
26+
// Create the RateLimitedEventHandler instance
27+
*ppRateLimitedEventHandler = new RateLimitedEventHandler(pExistingHandler);
28+
if (!(*ppRateLimitedEventHandler)) {
29+
LOG_ERROR(L"rateLimitedUIAEventHandler_create: Could not create RateLimitedUIAEventHandler. Returning");
30+
return E_OUTOFMEMORY;
31+
}
32+
LOG_DEBUG(L"rateLimitedUIAEventHandler_create: done");
33+
return S_OK;
34+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
This file is a part of the NVDA project.
3+
URL: http://github.com/nvaccess/nvda/
4+
Copyright 2023 NV Access Limited.
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License version 2.0, as published by
7+
the Free Software Foundation.
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
This license can be found at:
12+
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
*/
14+
15+
#pragma once
16+
17+
#include <vector>
18+
#include <variant>
19+
#include <atlcomcli.h>
20+
#include <UIAutomation.h>
21+
#include "utils.h"
22+
23+
// The following structs are used to represent UI Automation event params.
24+
// A part from holding the params,
25+
// each struct also contains a method for generating a comparison key
26+
// which is used to detect and remove duplicate events.
27+
// The key is made up of the element's runtime ID,
28+
// plus any extra event params that make the vent unique,
29+
// E.g. event ID, property ID etc.
30+
31+
struct AutomationEventRecord_t {
32+
CComPtr<IUIAutomationElement> sender;
33+
EVENTID eventID;
34+
std::vector<int> generateCoalescingKey() const {
35+
auto key = getRuntimeIDFromElement(sender);
36+
key.push_back(eventID);
37+
return key;
38+
}
39+
};
40+
41+
struct PropertyChangedEventRecord_t {
42+
CComPtr<IUIAutomationElement> sender;
43+
PROPERTYID propertyID;
44+
CComVariant newValue;
45+
std::vector<int> generateCoalescingKey() const {
46+
auto key = getRuntimeIDFromElement(sender);
47+
key.push_back(UIA_AutomationPropertyChangedEventId);
48+
key.push_back(propertyID);
49+
return key;
50+
}
51+
};
52+
53+
struct FocusChangedEventRecord_t {
54+
CComPtr<IUIAutomationElement> sender;
55+
std::vector<int> generateCoalescingKey() const {
56+
auto key = getRuntimeIDFromElement(sender);
57+
key.push_back(UIA_AutomationFocusChangedEventId);
58+
return key;
59+
}
60+
};
61+
62+
struct NotificationEventRecord_t {
63+
CComPtr<IUIAutomationElement> sender;
64+
NotificationKind notificationKind;
65+
NotificationProcessing notificationProcessing;
66+
CComBSTR displayString;
67+
CComBSTR activityID;
68+
std::vector<int> generateCoalescingKey() const {
69+
auto key = getRuntimeIDFromElement(sender);
70+
key.push_back(UIA_NotificationEventId);
71+
key.push_back(notificationKind);
72+
key.push_back(notificationProcessing);
73+
// Include the activity ID in the key also,
74+
// by converting it to a sequence of ints.
75+
if(activityID.m_str) {
76+
for(int c: std::wstring_view(activityID.m_str)) {
77+
key.push_back(c);
78+
}
79+
} else {
80+
key.push_back(0);
81+
}
82+
return key;
83+
}
84+
};
85+
86+
struct ActiveTextPositionChangedEventRecord_t {
87+
CComPtr<IUIAutomationElement> sender;
88+
CComPtr<IUIAutomationTextRange> range;
89+
std::vector<int> generateCoalescingKey() const {
90+
auto key = getRuntimeIDFromElement(sender);
91+
key.push_back(UIA_ActiveTextPositionChangedEventId);
92+
return key;
93+
}
94+
};
95+
96+
// @brief a variant type that holds all possible UI Automation event records we support.
97+
using EventRecordVariant_t = std::variant<AutomationEventRecord_t, FocusChangedEventRecord_t, PropertyChangedEventRecord_t, NotificationEventRecord_t, ActiveTextPositionChangedEventRecord_t>;
98+
99+
// @brief A concept to be used with the above event record types
100+
// that ensures the type has a generateCoalescingKey method,
101+
// and that the type appears in the EventRecordVariant_t type.
102+
template<typename T>
103+
concept EventRecordConstraints = requires(T t) {
104+
{ t.generateCoalescingKey() } -> std::same_as<std::vector<int>>;
105+
// The type must be supported by the EventRecordVariant_t variant type
106+
requires supports_alternative<T, EventRecordVariant_t>;
107+
};
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
This file is a part of the NVDA project.
3+
URL: http://github.com/nvaccess/nvda/
4+
Copyright 2023 NV Access Limited.
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License version 2.0, as published by
7+
the Free Software Foundation.
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
This license can be found at:
12+
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
*/
14+
15+
#include <windows.h>
16+
#include <uiautomation.h>
17+
#include <numeric>
18+
#include <variant>
19+
#include <map>
20+
#include <vector>
21+
#include <functional>
22+
#include <mutex>
23+
#include <atomic>
24+
#include <atlcomcli.h>
25+
#include <comutil.h>
26+
#include <common/log.h>
27+
#include "eventRecord.h"
28+
#include "rateLimitedEventHandler.h"
29+
30+
template<EventRecordConstraints EventRecordClass, typename... EventRecordArgTypes>
31+
HRESULT RateLimitedEventHandler::queueEvent(EventRecordArgTypes&&... args) {
32+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent called");
33+
bool needsFlush = false;
34+
{ std::lock_guard lock(m_mtx);
35+
// work out whether we need to request a flush after inserting this event.
36+
needsFlush = m_eventRecords.empty();
37+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Inserting new event");
38+
auto& recordVar = m_eventRecords.emplace_back(std::in_place_type_t<EventRecordClass>{}, args...);
39+
auto recordVarIter = m_eventRecords.end();
40+
recordVarIter--;
41+
auto& record = std::get<EventRecordClass>(recordVar);
42+
auto coalescingKey = record.generateCoalescingKey();
43+
auto existingKeyIter = m_eventRecordsByKey.find(coalescingKey);
44+
if(existingKeyIter != m_eventRecordsByKey.end()) {
45+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: found existing event with same key");
46+
auto& [existingRecordVarIter,existingCoalesceCount] = existingKeyIter->second;
47+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: updating key and count to "<<(existingCoalesceCount+1));
48+
existingKeyIter->second = {recordVarIter, existingCoalesceCount + 1};
49+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: erasing old item");
50+
m_eventRecords.erase(existingRecordVarIter);
51+
} else {
52+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Adding key");
53+
m_eventRecordsByKey.insert_or_assign(coalescingKey, std::pair(recordVarIter, 1));
54+
}
55+
if(needsFlush) {
56+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: requesting flush");
57+
m_needsFlush = true;
58+
m_flushConditionVar.notify_one();
59+
}
60+
} // m_mtx released.
61+
return S_OK;
62+
}
63+
64+
void RateLimitedEventHandler::flusherThreadFunc(std::stop_token stopToken) {
65+
LOG_DEBUG(L"flusherThread started");
66+
// If this thread is requested to stop, we need to ensure we wake up.
67+
std::stop_callback stopCallback{stopToken, [this](){
68+
this->m_flushConditionVar.notify_all();
69+
}};
70+
do { // thread main loop
71+
LOG_DEBUG(L"flusherThreadFunc sleeping...");
72+
{ std::unique_lock lock(m_mtx);
73+
// sleep until a flush is needed or this thread should stop.
74+
m_flushConditionVar.wait(lock, [this, stopToken](){
75+
return this->m_needsFlush || stopToken.stop_requested();
76+
});
77+
LOG_DEBUG(L"flusherThread woke up");
78+
if(stopToken.stop_requested()) {
79+
LOG_DEBUG(L"flusherThread returning as stop requested");
80+
return;
81+
}
82+
m_needsFlush = false;
83+
} // m_mtx released here.
84+
flushEvents();
85+
} while(!stopToken.stop_requested());
86+
LOG_DEBUG(L"flusherThread returning");
87+
}
88+
89+
HRESULT RateLimitedEventHandler::emitEvent(const AutomationEventRecord_t& record) const {
90+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitAutomationEvent called");
91+
if(!m_pExistingAutomationEventHandler) {
92+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitAutomationEvent: interface not supported.");
93+
return E_NOINTERFACE;
94+
}
95+
LOG_DEBUG(L"Emitting automationEvent for eventID "<<record.eventID);
96+
return m_pExistingAutomationEventHandler->HandleAutomationEvent(record.sender, record.eventID);
97+
}
98+
99+
HRESULT RateLimitedEventHandler::emitEvent(const FocusChangedEventRecord_t& record) const {
100+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitFocusChangedEvent called");
101+
if(!m_pExistingFocusChangedEventHandler) {
102+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitFocusChangedEvent: interface not supported.");
103+
return E_NOINTERFACE;
104+
}
105+
LOG_DEBUG(L"Emitting focus changed event");
106+
return m_pExistingFocusChangedEventHandler->HandleFocusChangedEvent(record.sender);
107+
}
108+
109+
HRESULT RateLimitedEventHandler::emitEvent(const PropertyChangedEventRecord_t& record) const {
110+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent called");
111+
if(!m_pExistingPropertyChangedEventHandler) {
112+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent: interface not supported.");
113+
return E_NOINTERFACE;
114+
}
115+
LOG_DEBUG(L"Emitting property changed event for property "<<(record.propertyID));
116+
return m_pExistingPropertyChangedEventHandler->HandlePropertyChangedEvent(record.sender, record.propertyID, record.newValue);
117+
}
118+
119+
HRESULT RateLimitedEventHandler::emitEvent(const NotificationEventRecord_t& record) const {
120+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitNotificationEvent called");
121+
if(!m_pExistingNotificationEventHandler) {
122+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitNotificationChangedEvent: interface not supported.");
123+
return E_NOINTERFACE;
124+
}
125+
LOG_DEBUG(L"Emitting notification event");
126+
return m_pExistingNotificationEventHandler->HandleNotificationEvent(record.sender, record.notificationKind, record.notificationProcessing, record.displayString, record.activityID);
127+
}
128+
129+
HRESULT RateLimitedEventHandler::emitEvent(const ActiveTextPositionChangedEventRecord_t& record) const {
130+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent called");
131+
if(!m_pExistingActiveTextPositionChangedEventHandler) {
132+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent: interface not supported.");
133+
return E_NOINTERFACE;
134+
}
135+
LOG_DEBUG(L"Emitting active text position changed event");
136+
return m_pExistingActiveTextPositionChangedEventHandler->HandleActiveTextPositionChangedEvent(record.sender, record.range);
137+
}
138+
139+
RateLimitedEventHandler::~RateLimitedEventHandler() {
140+
LOG_DEBUG(L"RateLimitedUIAEventHandler::~RateLimitedUIAEventHandler called");
141+
}
142+
143+
RateLimitedEventHandler::RateLimitedEventHandler(IUnknown* pExistingHandler):
144+
m_pExistingAutomationEventHandler(pExistingHandler), m_pExistingFocusChangedEventHandler(pExistingHandler), m_pExistingPropertyChangedEventHandler(pExistingHandler), m_pExistingNotificationEventHandler(pExistingHandler), m_pExistingActiveTextPositionChangedEventHandler(pExistingHandler),
145+
m_flusherThread([this](std::stop_token st){ this->flusherThreadFunc(st); })
146+
{
147+
LOG_DEBUG(L"RateLimitedUIAEventHandler::RateLimitedUIAEventHandler called");
148+
}
149+
150+
// IUnknown methods
151+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::AddRef() {
152+
auto refCount = InterlockedIncrement(&m_refCount);
153+
LOG_DEBUG(L"AddRef: "<<refCount)
154+
return refCount;
155+
}
156+
157+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::Release() {
158+
auto refCount = InterlockedDecrement(&m_refCount);
159+
if (refCount == 0) {
160+
delete this;
161+
}
162+
LOG_DEBUG(L"Release: "<<refCount)
163+
return refCount;
164+
}
165+
166+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::QueryInterface(REFIID riid, void** ppInterface) {
167+
if (riid == __uuidof(IUnknown)) {
168+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
169+
AddRef();
170+
return S_OK;
171+
} else if (riid == __uuidof(IUIAutomationEventHandler)) {
172+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
173+
AddRef();
174+
return S_OK;
175+
} else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler)) {
176+
*ppInterface = static_cast<IUIAutomationFocusChangedEventHandler*>(this);
177+
AddRef();
178+
return S_OK;
179+
} else if (riid == __uuidof(IUIAutomationPropertyChangedEventHandler)) {
180+
*ppInterface = static_cast<IUIAutomationPropertyChangedEventHandler*>(this);
181+
AddRef();
182+
return S_OK;
183+
} else if (riid == __uuidof(IUIAutomationNotificationEventHandler)) {
184+
*ppInterface = static_cast<IUIAutomationNotificationEventHandler*>(this);
185+
AddRef();
186+
return S_OK;
187+
} else if (riid == __uuidof(IUIAutomationActiveTextPositionChangedEventHandler)) {
188+
*ppInterface = static_cast<IUIAutomationActiveTextPositionChangedEventHandler*>(this);
189+
AddRef();
190+
return S_OK;
191+
}
192+
*ppInterface = nullptr;
193+
return E_NOINTERFACE;
194+
}
195+
196+
// IUIAutomationEventHandler method
197+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID) {
198+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleAutomationEvent called");
199+
LOG_DEBUG(L"Queuing automationEvent for eventID "<<eventID);
200+
return queueEvent<AutomationEventRecord_t>(sender, eventID);
201+
}
202+
203+
// IUIAutomationFocusEventHandler method
204+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleFocusChangedEvent(IUIAutomationElement* sender) {
205+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleFocusChangedEvent called");
206+
return queueEvent<FocusChangedEventRecord_t>(sender);
207+
}
208+
209+
// IUIAutomationPropertyChangedEventHandler method
210+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandlePropertyChangedEvent(IUIAutomationElement* sender, PROPERTYID propertyID, VARIANT newValue) {
211+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandlePropertyChangedEvent called");
212+
return queueEvent<PropertyChangedEventRecord_t>(sender, propertyID, newValue);
213+
}
214+
215+
// IUIAutomationNotificationEventHandler method
216+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleNotificationEvent(IUIAutomationElement* sender, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityID) {
217+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleNotificationEvent called");
218+
return queueEvent<NotificationEventRecord_t>(sender, notificationKind, notificationProcessing, displayString, activityID);
219+
}
220+
221+
// IUIAutomationActiveTextPositionchangedEventHandler method
222+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleActiveTextPositionChangedEvent(IUIAutomationElement* sender, IUIAutomationTextRange* range) {
223+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleActiveTextPositionChangedEvent called");
224+
return queueEvent<ActiveTextPositionChangedEventRecord_t>(sender, range);
225+
}
226+
227+
void RateLimitedEventHandler::flushEvents() {
228+
LOG_DEBUG(L"RateLimitedEventHandler::flushEvents called");
229+
decltype(m_eventRecords) eventRecordsCopy;
230+
decltype(m_eventRecordsByKey) eventRecordsByKeyCopy;
231+
{ std::lock_guard lock{m_mtx};
232+
eventRecordsCopy.swap(m_eventRecords);
233+
eventRecordsByKeyCopy.swap(m_eventRecordsByKey);
234+
} // m_mtx released here.
235+
// Emit events
236+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flusherThreadFunc: Emitting events...");
237+
for(const auto& recordVar: eventRecordsCopy) {
238+
std::visit([this](const auto& record) {
239+
this->emitEvent(record);
240+
}, recordVar);
241+
}
242+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flusherThreadFunc: Done emitting events");
243+
}

0 commit comments

Comments
 (0)