Skip to content

Commit 64574df

Browse files
Merge 9cd6cb7 into e77bde9
2 parents e77bde9 + 9cd6cb7 commit 64574df

10 files changed

Lines changed: 601 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 = true;
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: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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+
const unsigned int flushTimeMS = (EventRecordClass::isCoalesceable)?30:0;
35+
{ std::lock_guard lock(mtx);
36+
if(!EventRecordClass::isCoalesceable || m_eventRecords.empty()) {
37+
needsFlush = true;
38+
}
39+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Inserting new event");
40+
auto& recordVar = m_eventRecords.emplace_back(std::in_place_type_t<EventRecordClass>{}, args...);
41+
auto recordVarIter = m_eventRecords.end();
42+
recordVarIter--;
43+
auto& record = std::get<EventRecordClass>(recordVar);
44+
if constexpr(EventRecordClass::isCoalesceable) {
45+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Is a coalesceable event");
46+
auto coalescingKey = record.generateCoalescingKey();
47+
auto existingKeyIter = m_eventRecordsByKey.find(coalescingKey);
48+
if(existingKeyIter != m_eventRecordsByKey.end()) {
49+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: found existing event with same key");
50+
auto& [existingRecordVarIter,existingCoalesceCount] = existingKeyIter->second;
51+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: updating key and count to "<<(existingCoalesceCount+1));
52+
existingKeyIter->second = {recordVarIter, existingCoalesceCount + 1};
53+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: erasing old item");
54+
m_eventRecords.erase(existingRecordVarIter);
55+
} else {
56+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Adding key");
57+
m_eventRecordsByKey.insert_or_assign(coalescingKey, std::pair(recordVarIter, 1));
58+
}
59+
}
60+
}
61+
if(needsFlush) {
62+
LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: posting flush message");
63+
PostMessage(m_messageWindow, m_flushMessage, reinterpret_cast<WPARAM>(this), flushTimeMS);
64+
}
65+
return S_OK;
66+
}
67+
68+
HRESULT RateLimitedEventHandler::emitEvent(const AutomationEventRecord_t& record) const {
69+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitAutomationEvent called");
70+
if(!m_pExistingAutomationEventHandler) {
71+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitAutomationEvent: interface not supported.");
72+
return E_NOINTERFACE;
73+
}
74+
LOG_DEBUG(L"Emitting automationEvent for eventID "<<record.eventID);
75+
return m_pExistingAutomationEventHandler->HandleAutomationEvent(record.sender, record.eventID);
76+
}
77+
78+
HRESULT RateLimitedEventHandler::emitEvent(const FocusChangedEventRecord_t& record) const {
79+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitFocusChangedEvent called");
80+
if(!m_pExistingFocusChangedEventHandler) {
81+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitFocusChangedEvent: interface not supported.");
82+
return E_NOINTERFACE;
83+
}
84+
return m_pExistingFocusChangedEventHandler->HandleFocusChangedEvent(record.sender);
85+
}
86+
87+
HRESULT RateLimitedEventHandler::emitEvent(const PropertyChangedEventRecord_t& record) const {
88+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent called");
89+
if(!m_pExistingPropertyChangedEventHandler) {
90+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent: interface not supported.");
91+
return E_NOINTERFACE;
92+
}
93+
return m_pExistingPropertyChangedEventHandler->HandlePropertyChangedEvent(record.sender, record.propertyID, record.newValue);
94+
}
95+
96+
HRESULT RateLimitedEventHandler::emitEvent(const NotificationEventRecord_t& record) const {
97+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitNotificationEvent called");
98+
if(!m_pExistingNotificationEventHandler) {
99+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitNotificationChangedEvent: interface not supported.");
100+
return E_NOINTERFACE;
101+
}
102+
return m_pExistingNotificationEventHandler->HandleNotificationEvent(record.sender, record.notificationKind, record.notificationProcessing, record.displayString, record.activityID);
103+
}
104+
105+
HRESULT RateLimitedEventHandler::emitEvent(const ActiveTextPositionChangedEventRecord_t& record) const {
106+
LOG_DEBUG(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent called");
107+
if(!m_pExistingActiveTextPositionChangedEventHandler) {
108+
LOG_ERROR(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent: interface not supported.");
109+
return E_NOINTERFACE;
110+
}
111+
return m_pExistingActiveTextPositionChangedEventHandler->HandleActiveTextPositionChangedEvent(record.sender, record.range);
112+
}
113+
114+
RateLimitedEventHandler::~RateLimitedEventHandler() {
115+
LOG_DEBUG(L"RateLimitedUIAEventHandler::~RateLimitedUIAEventHandler called");
116+
}
117+
118+
RateLimitedEventHandler::RateLimitedEventHandler(IUnknown* pExistingHandler, HWND messageWindow, UINT flushMessage)
119+
: m_messageWindow(messageWindow), m_flushMessage(flushMessage), m_refCount(1), m_pExistingAutomationEventHandler(pExistingHandler), m_pExistingFocusChangedEventHandler(pExistingHandler), m_pExistingPropertyChangedEventHandler(pExistingHandler), m_pExistingNotificationEventHandler(pExistingHandler), m_pExistingActiveTextPositionChangedEventHandler(pExistingHandler) {
120+
LOG_DEBUG(L"RateLimitedUIAEventHandler::RateLimitedUIAEventHandler called");
121+
}
122+
123+
// IUnknown methods
124+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::AddRef() {
125+
return InterlockedIncrement(&m_refCount);
126+
}
127+
128+
ULONG STDMETHODCALLTYPE RateLimitedEventHandler::Release() {
129+
ULONG refCount = InterlockedDecrement(&m_refCount);
130+
if (refCount == 0) {
131+
delete this;
132+
}
133+
return refCount;
134+
}
135+
136+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::QueryInterface(REFIID riid, void** ppInterface) {
137+
if (riid == __uuidof(IUnknown)) {
138+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
139+
AddRef();
140+
return S_OK;
141+
} else if (riid == __uuidof(IUIAutomationEventHandler)) {
142+
*ppInterface = static_cast<IUIAutomationEventHandler*>(this);
143+
AddRef();
144+
return S_OK;
145+
} else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler)) {
146+
*ppInterface = static_cast<IUIAutomationFocusChangedEventHandler*>(this);
147+
AddRef();
148+
return S_OK;
149+
} else if (riid == __uuidof(IUIAutomationPropertyChangedEventHandler)) {
150+
*ppInterface = static_cast<IUIAutomationPropertyChangedEventHandler*>(this);
151+
AddRef();
152+
return S_OK;
153+
} else if (riid == __uuidof(IUIAutomationNotificationEventHandler)) {
154+
*ppInterface = static_cast<IUIAutomationNotificationEventHandler*>(this);
155+
AddRef();
156+
return S_OK;
157+
} else if (riid == __uuidof(IUIAutomationActiveTextPositionChangedEventHandler)) {
158+
*ppInterface = static_cast<IUIAutomationActiveTextPositionChangedEventHandler*>(this);
159+
AddRef();
160+
return S_OK;
161+
}
162+
*ppInterface = nullptr;
163+
return E_NOINTERFACE;
164+
}
165+
166+
// IUIAutomationEventHandler method
167+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID) {
168+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleAutomationEvent called");
169+
LOG_DEBUG(L"Queuing automationEvent for eventID "<<eventID);
170+
return queueEvent<AutomationEventRecord_t>(sender, eventID);
171+
}
172+
173+
// IUIAutomationFocusEventHandler method
174+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleFocusChangedEvent(IUIAutomationElement* sender) {
175+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleFocusChangedEvent called");
176+
return queueEvent<FocusChangedEventRecord_t>(sender);
177+
}
178+
179+
// IUIAutomationPropertyChangedEventHandler method
180+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandlePropertyChangedEvent(IUIAutomationElement* sender, PROPERTYID propertyID, VARIANT newValue) {
181+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandlePropertyChangedEvent called");
182+
return queueEvent<PropertyChangedEventRecord_t>(sender, propertyID, newValue);
183+
}
184+
185+
// IUIAutomationNotificationEventHandler method
186+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleNotificationEvent(IUIAutomationElement* sender, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityID) {
187+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleNotificationEvent called");
188+
return queueEvent<NotificationEventRecord_t>(sender, notificationKind, notificationProcessing, displayString, activityID);
189+
}
190+
191+
// IUIAutomationActiveTextPositionchangedEventHandler method
192+
HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleActiveTextPositionChangedEvent(IUIAutomationElement* sender, IUIAutomationTextRange* range) {
193+
LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleActiveTextPositionChangedEvent called");
194+
return queueEvent<ActiveTextPositionChangedEventRecord_t>(sender, range);
195+
}
196+
197+
void RateLimitedEventHandler::flush() {
198+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush called");
199+
decltype(m_eventRecords) eventRecordsCopy;
200+
decltype(m_eventRecordsByKey) eventRecordsByKeyCopy;
201+
{ std::lock_guard lock(mtx);
202+
eventRecordsCopy.swap(m_eventRecords);
203+
eventRecordsByKeyCopy.swap(m_eventRecordsByKey);
204+
}
205+
206+
// Emit events
207+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush: Emitting events...");
208+
for(const auto& recordVar: eventRecordsCopy) {
209+
std::visit([this](const auto& record) {
210+
this->emitEvent(record);
211+
}, recordVar);
212+
}
213+
/*
214+
unsigned int count = std::accumulate(eventRecordsByKeyCopy.begin(), eventRecordsByKeyCopy.end(), 0, [](const auto& acc, const auto& i) {
215+
auto count = i.second.second;
216+
if(count > 1) {
217+
return acc + count;
218+
}
219+
return acc;
220+
});
221+
if(count > 0) {
222+
Beep(440 + (count*10), 40);
223+
}
224+
*/
225+
LOG_DEBUG(L"RateLimitedUIAEventHandler::flush: done emitting events");
226+
227+
}

0 commit comments

Comments
 (0)