Skip to content

Commit 39f0f91

Browse files
Merge c1a436e into e77bde9
2 parents e77bde9 + c1a436e commit 39f0f91

10 files changed

Lines changed: 629 additions & 63 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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, HWND messageWindow, UINT flushMessage, RateLimitedEventHandler** ppRateLimitedEventHandler) {
20+
LOG_DEBUG(L"rateLimitedUIAEventHandler_create called");
21+
if(!pExistingHandler || !messageWindow || !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, messageWindow, flushMessage);
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+
}
35+
36+
HRESULT rateLimitedUIAEventHandler_flush(RateLimitedEventHandler* pRateLimitedEventHandler) {
37+
LOG_DEBUG(L"rateLimitedUIAEventHandler_flush called");
38+
if(!pRateLimitedEventHandler) {
39+
LOG_ERROR(L"rateLimitedUIAEventHandler_flush: invalid argument. Returning");
40+
return E_INVALIDARG;
41+
}
42+
pRateLimitedEventHandler->flush();
43+
return S_OK;
44+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
struct AutomationEventRecord_t {
24+
static const bool isCoalesceable = true;
25+
CComPtr<IUIAutomationElement> sender;
26+
EVENTID eventID;
27+
std::vector<int> generateCoalescingKey() const {
28+
auto key = getRuntimeIDFromElement(sender);
29+
key.push_back(eventID);
30+
return key;
31+
}
32+
};
33+
34+
struct PropertyChangedEventRecord_t {
35+
static const bool isCoalesceable = false;
36+
CComPtr<IUIAutomationElement> sender;
37+
PROPERTYID propertyID;
38+
CComVariant newValue;
39+
std::vector<int> generateCoalescingKey() const {
40+
auto key = getRuntimeIDFromElement(sender);
41+
key.push_back(UIA_AutomationPropertyChangedEventId);
42+
key.push_back(propertyID);
43+
return key;
44+
}
45+
};
46+
47+
struct FocusChangedEventRecord_t {
48+
static const bool isCoalesceable = false;
49+
CComPtr<IUIAutomationElement> sender;
50+
};
51+
52+
struct NotificationEventRecord_t {
53+
static const bool isCoalesceable = false;
54+
CComPtr<IUIAutomationElement> sender;
55+
NotificationKind notificationKind;
56+
NotificationProcessing notificationProcessing;
57+
CComBSTR displayString;
58+
CComBSTR activityID;
59+
};
60+
61+
struct ActiveTextPositionChangedEventRecord_t {
62+
static const bool isCoalesceable = false;
63+
CComPtr<IUIAutomationElement> sender;
64+
CComPtr<IUIAutomationTextRange> range;
65+
};
66+
67+
using EventRecordVariant_t = std::variant<AutomationEventRecord_t, FocusChangedEventRecord_t, PropertyChangedEventRecord_t, NotificationEventRecord_t, ActiveTextPositionChangedEventRecord_t>;
68+
69+
template<typename T>
70+
concept EventRecordConstraints = requires(T t) {
71+
// type must have a static const bool member called 'isCoalesceable'
72+
{ t.isCoalesceable } -> std::same_as<const bool&>;
73+
// if 'isCoaleseable' is true, then the type must have a 'generateCoalescingKey' method.
74+
requires (T::isCoalesceable == false) || requires(T t) {
75+
{ t.generateCoalescingKey() } -> std::same_as<std::vector<int>>;
76+
};
77+
// The type must be supported by the EventRecordVariant_t variant type
78+
requires supports_alternative<T, EventRecordVariant_t>;
79+
};
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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+
FlushRequest flushRequest = FlushRequest::none;
34+
unsigned int flushTimeMS = 0;
35+
{ std::lock_guard lock(mtx);
36+
// work out whether we need to request a flush after inserting this event.
37+
if(!EventRecordClass::isCoalesceable) {
38+
// not a coalesceable event so requires a quick flush
39+
// if a quick flush has not been requested already.
40+
if(lastFlushRequest < FlushRequest::quick) {
41+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: requesting quick flush after queuing");
42+
lastFlushRequest = flushRequest = FlushRequest::quick;
43+
} else {
44+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: quick flush needed, but quick flush already requested.");
45+
}
46+
} else if(m_eventRecords.empty()) {
47+
// It is a coalesceable event and its the first event since the last flush,
48+
// so requires a delayed flush
49+
// if a delayed or quick flush is not requested already.
50+
if(lastFlushRequest < FlushRequest::delayed) {
51+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: requesting delayed flush after queuing");
52+
lastFlushRequest = flushRequest = FlushRequest::delayed;
53+
flushTimeMS = 30;
54+
} else {
55+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: delayed flush needed, but quick flush already requested.");
56+
}
57+
}
58+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Inserting new event");
59+
auto& recordVar = m_eventRecords.emplace_back(std::in_place_type_t<EventRecordClass>{}, args...);
60+
auto recordVarIter = m_eventRecords.end();
61+
recordVarIter--;
62+
auto& record = std::get<EventRecordClass>(recordVar);
63+
if constexpr(EventRecordClass::isCoalesceable) {
64+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Is a coalesceable event");
65+
auto coalescingKey = record.generateCoalescingKey();
66+
auto existingKeyIter = m_eventRecordsByKey.find(coalescingKey);
67+
if(existingKeyIter != m_eventRecordsByKey.end()) {
68+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: found existing event with same key");
69+
auto& [existingRecordVarIter,existingCoalesceCount] = existingKeyIter->second;
70+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: updating key and count to "<<(existingCoalesceCount+1));
71+
existingKeyIter->second = {recordVarIter, existingCoalesceCount + 1};
72+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: erasing old item");
73+
m_eventRecords.erase(existingRecordVarIter);
74+
} else {
75+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Adding key");
76+
m_eventRecordsByKey.insert_or_assign(coalescingKey, std::pair(recordVarIter, 1));
77+
}
78+
}
79+
}
80+
if(flushRequest > FlushRequest::none) {
81+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: posting flush message with delay of "<<flushTimeMS);
82+
PostMessage(m_messageWindow, m_flushMessage, reinterpret_cast<WPARAM>(this), flushTimeMS);
83+
}
84+
return S_OK;
85+
}
86+
87+
HRESULT RateLimitedEventHandler::emitEvent(const AutomationEventRecord_t& record) const {
88+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitAutomationEvent called");
89+
if(!m_pExistingAutomationEventHandler) {
90+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitAutomationEvent: interface not supported.");
91+
return E_NOINTERFACE;
92+
}
93+
LOG_DEBUG(L"Emitting automationEvent for eventID "<<record.eventID);
94+
return m_pExistingAutomationEventHandler->HandleAutomationEvent(record.sender, record.eventID);
95+
}
96+
97+
HRESULT RateLimitedEventHandler::emitEvent(const FocusChangedEventRecord_t& record) const {
98+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitFocusChangedEvent called");
99+
if(!m_pExistingFocusChangedEventHandler) {
100+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitFocusChangedEvent: interface not supported.");
101+
return E_NOINTERFACE;
102+
}
103+
return m_pExistingFocusChangedEventHandler->HandleFocusChangedEvent(record.sender);
104+
}
105+
106+
HRESULT RateLimitedEventHandler::emitEvent(const PropertyChangedEventRecord_t& record) const {
107+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent called");
108+
if(!m_pExistingPropertyChangedEventHandler) {
109+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent: interface not supported.");
110+
return E_NOINTERFACE;
111+
}
112+
return m_pExistingPropertyChangedEventHandler->HandlePropertyChangedEvent(record.sender, record.propertyID, record.newValue);
113+
}
114+
115+
HRESULT RateLimitedEventHandler::emitEvent(const NotificationEventRecord_t& record) const {
116+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitNotificationEvent called");
117+
if(!m_pExistingNotificationEventHandler) {
118+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitNotificationChangedEvent: interface not supported.");
119+
return E_NOINTERFACE;
120+
}
121+
return m_pExistingNotificationEventHandler->HandleNotificationEvent(record.sender, record.notificationKind, record.notificationProcessing, record.displayString, record.activityID);
122+
}
123+
124+
HRESULT RateLimitedEventHandler::emitEvent(const ActiveTextPositionChangedEventRecord_t& record) const {
125+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent called");
126+
if(!m_pExistingActiveTextPositionChangedEventHandler) {
127+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent: interface not supported.");
128+
return E_NOINTERFACE;
129+
}
130+
return m_pExistingActiveTextPositionChangedEventHandler->HandleActiveTextPositionChangedEvent(record.sender, record.range);
131+
}
132+
133+
RateLimitedEventHandler::~RateLimitedEventHandler() {
134+
LOG_DEBUG(L"RateLimitedUIAEventHandler::~RateLimitedUIAEventHandler called");
135+
}
136+
137+
RateLimitedEventHandler::RateLimitedEventHandler(IUnknown* pExistingHandler, HWND messageWindow, UINT flushMessage)
138+
: m_messageWindow(messageWindow), m_flushMessage(flushMessage), m_refCount(1), m_pExistingAutomationEventHandler(pExistingHandler), m_pExistingFocusChangedEventHandler(pExistingHandler), m_pExistingPropertyChangedEventHandler(pExistingHandler), m_pExistingNotificationEventHandler(pExistingHandler), m_pExistingActiveTextPositionChangedEventHandler(pExistingHandler) {
139+
LOG_DEBUG(L"RateLimitedUIAEventHandler::RateLimitedUIAEventHandler called");
140+
}
141+
142+
// IUnknown methods
143+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::AddRef() {
144+
return InterlockedIncrement(&m_refCount);
145+
}
146+
147+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::Release() {
148+
ULONG refCount = InterlockedDecrement(&m_refCount);
149+
if (refCount == 0) {
150+
delete this;
151+
}
152+
return refCount;
153+
}
154+
155+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::QueryInterface(REFIID riid, void** ppInterface) {
156+
if (riid == __uuidof(IUnknown)) {
157+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
158+
AddRef();
159+
return S_OK;
160+
} else if (riid == __uuidof(IUIAutomationEventHandler)) {
161+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
162+
AddRef();
163+
return S_OK;
164+
} else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler)) {
165+
*ppInterface = static_cast<IUIAutomationFocusChangedEventHandler*>(this);
166+
AddRef();
167+
return S_OK;
168+
} else if (riid == __uuidof(IUIAutomationPropertyChangedEventHandler)) {
169+
*ppInterface = static_cast<IUIAutomationPropertyChangedEventHandler*>(this);
170+
AddRef();
171+
return S_OK;
172+
} else if (riid == __uuidof(IUIAutomationNotificationEventHandler)) {
173+
*ppInterface = static_cast<IUIAutomationNotificationEventHandler*>(this);
174+
AddRef();
175+
return S_OK;
176+
} else if (riid == __uuidof(IUIAutomationActiveTextPositionChangedEventHandler)) {
177+
*ppInterface = static_cast<IUIAutomationActiveTextPositionChangedEventHandler*>(this);
178+
AddRef();
179+
return S_OK;
180+
}
181+
*ppInterface = nullptr;
182+
return E_NOINTERFACE;
183+
}
184+
185+
// IUIAutomationEventHandler method
186+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID) {
187+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleAutomationEvent called");
188+
LOG_DEBUG(L"Queuing automationEvent for eventID "<<eventID);
189+
return queueEvent<AutomationEventRecord_t>(sender, eventID);
190+
}
191+
192+
// IUIAutomationFocusEventHandler method
193+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleFocusChangedEvent(IUIAutomationElement* sender) {
194+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleFocusChangedEvent called");
195+
return queueEvent<FocusChangedEventRecord_t>(sender);
196+
}
197+
198+
// IUIAutomationPropertyChangedEventHandler method
199+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandlePropertyChangedEvent(IUIAutomationElement* sender, PROPERTYID propertyID, VARIANT newValue) {
200+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandlePropertyChangedEvent called");
201+
return queueEvent<PropertyChangedEventRecord_t>(sender, propertyID, newValue);
202+
}
203+
204+
// IUIAutomationNotificationEventHandler method
205+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleNotificationEvent(IUIAutomationElement* sender, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityID) {
206+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleNotificationEvent called");
207+
return queueEvent<NotificationEventRecord_t>(sender, notificationKind, notificationProcessing, displayString, activityID);
208+
}
209+
210+
// IUIAutomationActiveTextPositionchangedEventHandler method
211+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleActiveTextPositionChangedEvent(IUIAutomationElement* sender, IUIAutomationTextRange* range) {
212+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleActiveTextPositionChangedEvent called");
213+
return queueEvent<ActiveTextPositionChangedEventRecord_t>(sender, range);
214+
}
215+
216+
void RateLimitedEventHandler::flush() {
217+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush called");
218+
decltype(m_eventRecords) eventRecordsCopy;
219+
decltype(m_eventRecordsByKey) eventRecordsByKeyCopy;
220+
{ std::lock_guard lock(mtx);
221+
eventRecordsCopy.swap(m_eventRecords);
222+
eventRecordsByKeyCopy.swap(m_eventRecordsByKey);
223+
lastFlushRequest = FlushRequest::none;
224+
}
225+
226+
// Emit events
227+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush: Emitting events...");
228+
for(const auto& recordVar: eventRecordsCopy) {
229+
std::visit([this](const auto& record) {
230+
this->emitEvent(record);
231+
}, recordVar);
232+
}
233+
/*
234+
unsigned int count = std::accumulate(eventRecordsByKeyCopy.begin(), eventRecordsByKeyCopy.end(), 0, [](const auto& acc, const auto& i) {
235+
auto count = i.second.second;
236+
if(count > 1) {
237+
return acc + count;
238+
}
239+
return acc;
240+
});
241+
if(count > 0) {
242+
Beep(440 + (count*10), 40);
243+
}
244+
*/
245+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush: done emitting events");
246+
}

0 commit comments

Comments
 (0)