From 663c25bab0c3916dc48dfdcf128a7646cf6406ed Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Thu, 15 Feb 2024 07:36:47 +1000 Subject: [PATCH] Revert pr #14888 and pr #15838 --- nvdaHelper/local/UIAEventLimiter/api.cpp | 34 --- .../local/UIAEventLimiter/eventRecord.h | 107 -------- .../rateLimitedEventHandler.cpp | 248 ------------------ .../UIAEventLimiter/rateLimitedEventHandler.h | 91 ------- nvdaHelper/local/UIAEventLimiter/utils.cpp | 43 --- nvdaHelper/local/UIAEventLimiter/utils.h | 43 --- nvdaHelper/local/nvdaHelperLocal.def | 1 - nvdaHelper/local/sconscript | 3 - source/NVDAObjects/UIA/__init__.py | 34 --- source/UIAHandler/__init__.py | 164 +++++------- source/config/configSpec.py | 1 - source/gui/settingsDialogs.py | 13 - user_docs/en/changes.t2t | 6 +- user_docs/en/userGuide.t2t | 10 - 14 files changed, 70 insertions(+), 728 deletions(-) delete mode 100644 nvdaHelper/local/UIAEventLimiter/api.cpp delete mode 100644 nvdaHelper/local/UIAEventLimiter/eventRecord.h delete mode 100644 nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.cpp delete mode 100644 nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.h delete mode 100644 nvdaHelper/local/UIAEventLimiter/utils.cpp delete mode 100644 nvdaHelper/local/UIAEventLimiter/utils.h diff --git a/nvdaHelper/local/UIAEventLimiter/api.cpp b/nvdaHelper/local/UIAEventLimiter/api.cpp deleted file mode 100644 index ba857690bc4..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/api.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include -#include -#include "rateLimitedEventHandler.h" - -HRESULT rateLimitedUIAEventHandler_create(IUnknown* pExistingHandler, RateLimitedEventHandler** ppRateLimitedEventHandler) { - LOG_DEBUG(L"rateLimitedUIAEventHandler_create called"); - if(!pExistingHandler || !ppRateLimitedEventHandler) { - LOG_ERROR(L"rateLimitedUIAEventHandler_create: one or more NULL arguments"); - return E_INVALIDARG; - } - - // Create the RateLimitedEventHandler instance - *ppRateLimitedEventHandler = new RateLimitedEventHandler(pExistingHandler); - if (!(*ppRateLimitedEventHandler)) { - LOG_ERROR(L"rateLimitedUIAEventHandler_create: Could not create RateLimitedUIAEventHandler. Returning"); - return E_OUTOFMEMORY; - } - LOG_DEBUG(L"rateLimitedUIAEventHandler_create: done"); - return S_OK; -} diff --git a/nvdaHelper/local/UIAEventLimiter/eventRecord.h b/nvdaHelper/local/UIAEventLimiter/eventRecord.h deleted file mode 100644 index 3570666a63d..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/eventRecord.h +++ /dev/null @@ -1,107 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#pragma once - -#include -#include -#include -#include -#include "utils.h" - -// The following structs are used to represent UI Automation event params. -// Apart from holding the params, -// each struct also contains a method for generating a comparison key -// which is used to detect and remove duplicate events. -// The key is made up of the element's runtime ID, -// plus any extra event params that make the event unique, -// E.g. event ID, property ID etc. - -struct AutomationEventRecord_t { - CComPtr sender; - EVENTID eventID; - std::vector generateCoalescingKey() const { - auto key = getRuntimeIDFromElement(sender); - key.push_back(eventID); - return key; - } -}; - -struct PropertyChangedEventRecord_t { - CComPtr sender; - PROPERTYID propertyID; - CComVariant newValue; - std::vector generateCoalescingKey() const { - auto key = getRuntimeIDFromElement(sender); - key.push_back(UIA_AutomationPropertyChangedEventId); - key.push_back(propertyID); - return key; - } -}; - -struct FocusChangedEventRecord_t { - CComPtr sender; - std::vector generateCoalescingKey() const { - auto key = getRuntimeIDFromElement(sender); - key.push_back(UIA_AutomationFocusChangedEventId); - return key; - } -}; - -struct NotificationEventRecord_t { - CComPtr sender; - NotificationKind notificationKind; - NotificationProcessing notificationProcessing; - CComBSTR displayString; - CComBSTR activityID; - std::vector generateCoalescingKey() const { - auto key = getRuntimeIDFromElement(sender); - key.push_back(UIA_NotificationEventId); - key.push_back(notificationKind); - key.push_back(notificationProcessing); - // Include the activity ID in the key also, - // by converting it to a sequence of ints. - if(activityID.m_str) { - for(int c: std::wstring_view(activityID.m_str)) { - key.push_back(c); - } - } else { - key.push_back(0); - } - return key; - } -}; - -struct ActiveTextPositionChangedEventRecord_t { - CComPtr sender; - CComPtr range; - std::vector generateCoalescingKey() const { - auto key = getRuntimeIDFromElement(sender); - key.push_back(UIA_ActiveTextPositionChangedEventId); - return key; - } -}; - -// @brief a variant type that holds all possible UI Automation event records we support. -using EventRecordVariant_t = std::variant; - -// @brief A concept to be used with the above event record types -// that ensures the type has a generateCoalescingKey method, -// and that the type appears in the EventRecordVariant_t type. -template -concept EventRecordConstraints = requires(T t) { - { t.generateCoalescingKey() } -> std::same_as>; - // The type must be supported by the EventRecordVariant_t variant type - requires supports_alternative; -}; diff --git a/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.cpp b/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.cpp deleted file mode 100644 index a2b96f527ca..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "eventRecord.h" -#include "rateLimitedEventHandler.h" - -template -HRESULT RateLimitedEventHandler::queueEvent(EventRecordArgTypes&&... args) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent called"); - bool needsFlush = false; - { // scoped lock - std::lock_guard lock(m_mtx); - // work out whether we need to request a flush after inserting this event. - needsFlush = m_eventRecords.empty(); - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Inserting new event"); - auto& recordVar = m_eventRecords.emplace_back(std::in_place_type_t{}, args...); - auto recordVarIter = m_eventRecords.end(); - recordVarIter--; - auto& record = std::get(recordVar); - auto coalescingKey = record.generateCoalescingKey(); - auto existingKeyIter = m_eventRecordsByKey.find(coalescingKey); - if(existingKeyIter != m_eventRecordsByKey.end()) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: found existing event with same key"); - auto& [existingRecordVarIter,existingCoalesceCount] = existingKeyIter->second; - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: updating key and count to "<<(existingCoalesceCount+1)); - existingKeyIter->second = {recordVarIter, existingCoalesceCount + 1}; - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: erasing old item"); - m_eventRecords.erase(existingRecordVarIter); - } else { - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: Adding key"); - m_eventRecordsByKey.insert_or_assign(coalescingKey, std::pair(recordVarIter, 1)); - } - if(needsFlush) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::queueEvent: requesting flush"); - m_needsFlush = true; - m_flushConditionVar.notify_one(); - } - } // m_mtx released. - return S_OK; -} - -void RateLimitedEventHandler::flusherThreadFunc(std::stop_token stopToken) { - LOG_DEBUG(L"flusherThread started"); - // If this thread is requested to stop, we need to ensure we wake up. - std::stop_callback stopCallback{stopToken, [this](){ - this->m_flushConditionVar.notify_all(); - }}; - do { // thread main loop - LOG_DEBUG(L"flusherThreadFunc sleeping..."); - { std::unique_lock lock(m_mtx); - // sleep until a flush is needed or this thread should stop. - m_flushConditionVar.wait(lock, [this, stopToken](){ - return this->m_needsFlush || stopToken.stop_requested(); - }); - LOG_DEBUG(L"flusherThread woke up"); - if(stopToken.stop_requested()) { - LOG_DEBUG(L"flusherThread returning as stop requested"); - return; - } - m_needsFlush = false; - } // m_mtx released here. - flushEvents(); - } while(!stopToken.stop_requested()); - LOG_DEBUG(L"flusherThread returning"); -} - -HRESULT RateLimitedEventHandler::emitEvent(const AutomationEventRecord_t& record) const { - LOG_DEBUG(L"RateLimitedUIAEventHandler::emitAutomationEvent called"); - if(!m_pExistingAutomationEventHandler) { - LOG_ERROR(L"RateLimitedUIAEventHandler::emitAutomationEvent: interface not supported."); - return E_NOINTERFACE; - } - LOG_DEBUG(L"Emitting automationEvent for eventID "<HandleAutomationEvent(record.sender, record.eventID); -} - -HRESULT RateLimitedEventHandler::emitEvent(const FocusChangedEventRecord_t& record) const { - LOG_DEBUG(L"RateLimitedUIAEventHandler::emitFocusChangedEvent called"); - if(!m_pExistingFocusChangedEventHandler) { - LOG_ERROR(L"RateLimitedUIAEventHandler::emitFocusChangedEvent: interface not supported."); - return E_NOINTERFACE; - } - LOG_DEBUG(L"Emitting focus changed event"); - return m_pExistingFocusChangedEventHandler->HandleFocusChangedEvent(record.sender); -} - -HRESULT RateLimitedEventHandler::emitEvent(const PropertyChangedEventRecord_t& record) const { - LOG_DEBUG(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent called"); - if(!m_pExistingPropertyChangedEventHandler) { - LOG_ERROR(L"RateLimitedUIAEventHandler::emitPropertyChangedEvent: interface not supported."); - return E_NOINTERFACE; - } - LOG_DEBUG(L"Emitting property changed event for property "<<(record.propertyID)); - return m_pExistingPropertyChangedEventHandler->HandlePropertyChangedEvent(record.sender, record.propertyID, record.newValue); -} - -HRESULT RateLimitedEventHandler::emitEvent(const NotificationEventRecord_t& record) const { - LOG_DEBUG(L"RateLimitedUIAEventHandler::emitNotificationEvent called"); - if(!m_pExistingNotificationEventHandler) { - LOG_ERROR(L"RateLimitedUIAEventHandler::emitNotificationChangedEvent: interface not supported."); - return E_NOINTERFACE; - } - LOG_DEBUG(L"Emitting notification event"); - return m_pExistingNotificationEventHandler->HandleNotificationEvent(record.sender, record.notificationKind, record.notificationProcessing, record.displayString, record.activityID); -} - -HRESULT RateLimitedEventHandler::emitEvent(const ActiveTextPositionChangedEventRecord_t& record) const { - LOG_DEBUG(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent called"); - if(!m_pExistingActiveTextPositionChangedEventHandler) { - LOG_ERROR(L"RateLimitedUIAEventHandler::emitActiveTextPositionChangedEvent: interface not supported."); - return E_NOINTERFACE; - } - LOG_DEBUG(L"Emitting active text position changed event"); - return m_pExistingActiveTextPositionChangedEventHandler->HandleActiveTextPositionChangedEvent(record.sender, record.range); -} - -RateLimitedEventHandler::~RateLimitedEventHandler() { - LOG_DEBUG(L"RateLimitedUIAEventHandler::~RateLimitedUIAEventHandler called"); -} - -RateLimitedEventHandler::RateLimitedEventHandler(IUnknown* pExistingHandler): - m_pExistingAutomationEventHandler(pExistingHandler), - m_pExistingFocusChangedEventHandler(pExistingHandler), - m_pExistingPropertyChangedEventHandler(pExistingHandler), - m_pExistingNotificationEventHandler(pExistingHandler), - m_pExistingActiveTextPositionChangedEventHandler(pExistingHandler), - m_flusherThread([this](std::stop_token st){ this->flusherThreadFunc(st); }) -{ - LOG_DEBUG(L"RateLimitedUIAEventHandler::RateLimitedUIAEventHandler called"); -} - -// IUnknown methods -ULONG STDMETHODCALLTYPE RateLimitedEventHandler::AddRef() { - auto refCount = InterlockedIncrement(&m_refCount); - LOG_DEBUG(L"AddRef: "<(this); - AddRef(); - return S_OK; - } else if (riid == __uuidof(IUIAutomationEventHandler)) { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } else if (riid == __uuidof(IUIAutomationFocusChangedEventHandler)) { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } else if (riid == __uuidof(IUIAutomationPropertyChangedEventHandler)) { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } else if (riid == __uuidof(IUIAutomationNotificationEventHandler)) { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } else if (riid == __uuidof(IUIAutomationActiveTextPositionChangedEventHandler)) { - *ppInterface = static_cast(this); - AddRef(); - return S_OK; - } - *ppInterface = nullptr; - return E_NOINTERFACE; -} - -// IUIAutomationEventHandler method -HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleAutomationEvent called"); - LOG_DEBUG(L"Queuing automationEvent for eventID "<(sender, eventID); -} - -// IUIAutomationFocusEventHandler method -HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleFocusChangedEvent(IUIAutomationElement* sender) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleFocusChangedEvent called"); - return queueEvent(sender); -} - -// IUIAutomationPropertyChangedEventHandler method -HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandlePropertyChangedEvent(IUIAutomationElement* sender, PROPERTYID propertyID, VARIANT newValue) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::HandlePropertyChangedEvent called"); - return queueEvent(sender, propertyID, newValue); -} - -// IUIAutomationNotificationEventHandler method -HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleNotificationEvent(IUIAutomationElement* sender, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityID) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleNotificationEvent called"); - return queueEvent(sender, notificationKind, notificationProcessing, displayString, activityID); -} - -// IUIAutomationActiveTextPositionchangedEventHandler method -HRESULT STDMETHODCALLTYPE RateLimitedEventHandler::HandleActiveTextPositionChangedEvent(IUIAutomationElement* sender, IUIAutomationTextRange* range) { - LOG_DEBUG(L"RateLimitedUIAEventHandler::HandleActiveTextPositionChangedEvent called"); - return queueEvent(sender, range); -} - -void RateLimitedEventHandler::flushEvents() { - LOG_DEBUG(L"RateLimitedEventHandler::flushEvents called"); - decltype(m_eventRecords) eventRecordsCopy; - decltype(m_eventRecordsByKey) eventRecordsByKeyCopy; - { std::lock_guard lock{m_mtx}; - eventRecordsCopy.swap(m_eventRecords); - eventRecordsByKeyCopy.swap(m_eventRecordsByKey); - } // m_mtx released here. - // Emit events - LOG_DEBUG(L"RateLimitedUIAEventHandler::flusherThreadFunc: Emitting events..."); - for(const auto& recordVar: eventRecordsCopy) { - std::visit([this](const auto& record) { - this->emitEvent(record); - }, recordVar); - } - LOG_DEBUG(L"RateLimitedUIAEventHandler::flusherThreadFunc: Done emitting events"); -} diff --git a/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.h b/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.h deleted file mode 100644 index b429e004d33..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/rateLimitedEventHandler.h +++ /dev/null @@ -1,91 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include -#include -#include -#include -#include -#include "eventRecord.h" - - -// @brief a class that listens for various UI Automation events, -// stores them in in an internal queue (removing any duplicates), -// and sends them onto an existing UI Automation event handler in a separate thread. -// This ensures that UI Automation core is never blocked while sending events to this class. -class RateLimitedEventHandler: - public IUIAutomationEventHandler, - public IUIAutomationFocusChangedEventHandler, - public IUIAutomationPropertyChangedEventHandler, - public IUIAutomationNotificationEventHandler, - public IUIAutomationActiveTextPositionChangedEventHandler -{ -private: - unsigned long m_refCount = 1; - CComQIPtr m_pExistingAutomationEventHandler; - CComQIPtr m_pExistingFocusChangedEventHandler; - CComQIPtr m_pExistingPropertyChangedEventHandler; - CComQIPtr m_pExistingNotificationEventHandler; - CComQIPtr m_pExistingActiveTextPositionChangedEventHandler; - std::list m_eventRecords; - std::map, std::pair> m_eventRecordsByKey; - bool m_needsFlush = false; - std::mutex m_mtx; - std::condition_variable m_flushConditionVar; - std::jthread m_flusherThread; - - /// @brief a thread function that wakes and flushes the event queue when one or more events have been added. - /// @param stopToken used to check if the thread should stop. - void flusherThreadFunc(std::stop_token stopToken); - - /// @brief a template function that queues a UI Automation event. - /// @tparam EventRecordClass the type of event record representing a UI Automation event. - /// @tparam ...EventRecordArgTypes the argument types required to construct the event record - /// @param ...args the arguments to construct the event record. - /// @return S_OK on success or a failure code otherwise. - template HRESULT queueEvent(EventRecordArgTypes&&... args); - - /// @brief Emits a UI Automation event to its existing handler. - /// @param record the event record representing the UI automation event. - /// @return S_OK on success or a failure code otherwise. - HRESULT emitEvent(const AutomationEventRecord_t& record) const; - HRESULT emitEvent(const FocusChangedEventRecord_t& record) const; - HRESULT emitEvent(const PropertyChangedEventRecord_t& record) const; - HRESULT emitEvent(const NotificationEventRecord_t& record) const; - HRESULT emitEvent(const ActiveTextPositionChangedEventRecord_t& record) const; - - /// @brief Removes all events from the queue, sending them on to the existing UI automation event handler. - void flushEvents(); - - ~RateLimitedEventHandler(); - - public: - - /// @brief class constructor. - /// @param pExistingHandler a pointer to an existing UI Automation event handler where events should be sent after they are flushed from the queue. - RateLimitedEventHandler(IUnknown* pExistingHandler); - - // IUnknown methods - ULONG STDMETHODCALLTYPE AddRef(); - ULONG STDMETHODCALLTYPE Release(); - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppInterface); - - // IUIAutomationEventHandler methods - HRESULT STDMETHODCALLTYPE HandleAutomationEvent(IUIAutomationElement* sender, EVENTID eventID); - HRESULT STDMETHODCALLTYPE HandleFocusChangedEvent(IUIAutomationElement* sender); - HRESULT STDMETHODCALLTYPE HandlePropertyChangedEvent(IUIAutomationElement* sender, PROPERTYID propertyID, VARIANT newValue); - HRESULT STDMETHODCALLTYPE HandleNotificationEvent(IUIAutomationElement* sender, NotificationKind notificationKind, NotificationProcessing notificationProcessing, BSTR displayString, BSTR activityID); - HRESULT STDMETHODCALLTYPE HandleActiveTextPositionChangedEvent(IUIAutomationElement* sender, IUIAutomationTextRange* range); - -}; diff --git a/nvdaHelper/local/UIAEventLimiter/utils.cpp b/nvdaHelper/local/UIAEventLimiter/utils.cpp deleted file mode 100644 index ae43af60fa1..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/utils.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include -#include -#include -#include "utils.h" - -std::vector SafeArrayToVector(SAFEARRAY* pSafeArray) { - std::vector vec; - int* data; - HRESULT hr = SafeArrayAccessData(pSafeArray, (void**)&data); - if (SUCCEEDED(hr)) { - LONG lowerBound, upperBound; - SafeArrayGetLBound(pSafeArray, 1, &lowerBound); - SafeArrayGetUBound(pSafeArray, 1, &upperBound); - vec.assign(data, data + (upperBound - lowerBound + 1)); - SafeArrayUnaccessData(pSafeArray); - } - return vec; -} - -std::vector getRuntimeIDFromElement(IUIAutomationElement* pElement) { - SAFEARRAY* runtimeIdArray; - HRESULT hr = pElement->GetRuntimeId(&runtimeIdArray); - if (FAILED(hr)) { - return {}; - } - std::vector runtimeID = SafeArrayToVector(runtimeIdArray); - SafeArrayDestroy(runtimeIdArray); - return runtimeID; -} diff --git a/nvdaHelper/local/UIAEventLimiter/utils.h b/nvdaHelper/local/UIAEventLimiter/utils.h deleted file mode 100644 index 2e55486e627..00000000000 --- a/nvdaHelper/local/UIAEventLimiter/utils.h +++ /dev/null @@ -1,43 +0,0 @@ -/* -This file is a part of the NVDA project. -URL: http://github.com/nvaccess/nvda/ -Copyright 2023 NV Access Limited. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2.0, as published by - the Free Software Foundation. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -This license can be found at: -http://www.gnu.org/licenses/old-licenses/gpl-2.0.html -*/ - -#include -#include -#include -#include -#include -#include -#include - -/// @brief creates a vector of ints from a SAFEARRAY. -/// @param pSafeArray -/// @return the vector of ints. -std::vector SafeArrayToVector(SAFEARRAY* pSafeArray); - -/// @brief Fetches the runtimeID from a given uI Automation element. -/// @param pElement the UI Automation element whose runtime ID should be fetched. -/// @return the runtime ID from the element. -std::vector getRuntimeIDFromElement(IUIAutomationElement* pElement); - -// @brief a helper template function for the supports_alternative concept. -template -constexpr bool supports_alternative_impl(std::index_sequence) { - return (std::same_as> || ...); -} - -// @brief a concept that checks if a given type can be held by a given variant type. -// @tparam T the type to check -// @tparam V the variant type to check -template -concept supports_alternative = supports_alternative_impl(std::make_index_sequence>{}); diff --git a/nvdaHelper/local/nvdaHelperLocal.def b/nvdaHelper/local/nvdaHelperLocal.def index 265c48a0bae..71c4922274f 100644 --- a/nvdaHelper/local/nvdaHelperLocal.def +++ b/nvdaHelper/local/nvdaHelperLocal.def @@ -69,7 +69,6 @@ EXPORTS getOleClipboardText _nvdaControllerInternal_reportLiveRegion _nvdaControllerInternal_openConfigDirectory - rateLimitedUIAEventHandler_create wasPlay_create wasPlay_destroy wasPlay_open diff --git a/nvdaHelper/local/sconscript b/nvdaHelper/local/sconscript index 35f319b75a2..53624842b40 100644 --- a/nvdaHelper/local/sconscript +++ b/nvdaHelper/local/sconscript @@ -89,9 +89,6 @@ localLib=env.SharedLibrary( "nvdaHelperLocal.def", "textUtils.cpp", "UIAUtils.cpp", - "UIAEventLimiter/api.cpp", - "UIAEventLimiter/rateLimitedEventHandler.cpp", - "UIAEventLimiter/utils.cpp", "mixer.cpp", "oleUtils.cpp", "wasapi.cpp", diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index 842562f6e9e..f8bbdf05382 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -25,7 +25,6 @@ import UIAHandler.customAnnotations import controlTypes from controlTypes import TextPosition, TextAlign -import inputCore import config import speech import api @@ -52,7 +51,6 @@ ) from NVDAObjects.behaviors import ( ProgressBar, - EditableTextBase, EditableTextWithoutAutoSelectDetection, EditableTextWithAutoSelectDetection, Dialog, @@ -1258,9 +1256,6 @@ def findOverlayClasses(self, clsList): # NOQA: C901 # Add editableText support if UIA supports a text pattern if self.TextInfo == UIATextInfo: - if self.UIAFrameworkId == 'XAML': - # This UIA element is being exposed by the XAML framework. - clsList.append(XamlEditableText) if UIAHandler.autoSelectDetectionAvailable: clsList.append(EditableTextWithAutoSelectDetection) else: @@ -1593,13 +1588,6 @@ def _get_UIAAutomationId(self): # #11445: due to timing errors, elements will be instantiated with no automation Id present. return "" - def _get_UIAFrameworkId(self) -> str: - try: - return self._getUIACacheablePropertyValue(UIAHandler.UIA_FrameworkIdPropertyId) - except COMError: - log.debugWarning("Could not fetch framework ID", exc_info=True) - return "" - #: Typing info for auto property _get_name() name: str @@ -2213,28 +2201,6 @@ def event_UIA_dropTargetEffect(self): ui.message(dropTargetEffect) -class XamlEditableText(EditableTextBase, UIA): - """ a UIA element with editable text exposed by the XAML framework.""" - - # XAML fires UIA textSelectionChange events before the caret position change is reflected - # in the related UIA text pattern. - # This means that, apart from deleting text, NVDA cannot rely on textSelectionChange (caret) events in XAML - # to detect if the caret has moved, as it occurs too early. - caretMovementDetectionUsesEvents = False - - def _backspaceScriptHelper(self, unit: str, gesture: inputCore.InputGesture): - """As UIA text range objects from XAML don't mutate with backspace, - comparing a text range copied from before backspace with a text range fetched after backspace - isn't reliable, as the ranges compare equal. - Therefore, we must always rely on events for caret change detection in this case. - """ - self.caretMovementDetectionUsesEvents = True - try: - super()._backspaceScriptHelper(unit, gesture) - finally: - self.caretMovementDetectionUsesEvents = False - - class TreeviewItem(UIA): def _get_value(self): diff --git a/source/UIAHandler/__init__.py b/source/UIAHandler/__init__.py index 041976b4fea..8369b35b4c3 100644 --- a/source/UIAHandler/__init__.py +++ b/source/UIAHandler/__init__.py @@ -9,9 +9,6 @@ from ctypes import ( oledll, windll, - POINTER, - CFUNCTYPE, - c_voidp, ) import comtypes.client @@ -22,7 +19,6 @@ byref, CLSCTX_INPROC_SERVER, CoCreateInstance, - IUnknown, ) import threading @@ -50,7 +46,6 @@ from typing import Dict from queue import Queue import aria -import NVDAHelper from . import remote as UIARemote @@ -448,7 +443,7 @@ def terminate(self): ) ) self.MTAThreadQueue.put_nowait(None) - # Wait for the MTA thread to die (while still message pumping) + #Wait for the MTA thread to die (while still message pumping) if windll.user32.MsgWaitForMultipleObjects(1,byref(MTAThreadHandle),False,200,0)!=0: log.debugWarning("Timeout or error while waiting for UIAHandler MTA thread") windll.kernel32.CloseHandle(MTAThreadHandle) @@ -513,17 +508,9 @@ def MTAThreadFunc(self): self.rootElement=self.clientObject.getRootElementBuildCache(self.baseCacheRequest) self.reservedNotSupportedValue=self.clientObject.ReservedNotSupportedValue self.ReservedMixedAttributeValue=self.clientObject.ReservedMixedAttributeValue - if config.conf["UIA"]["enhancedEventProcessing"]: - handler = pRateLimitedEventHandler = POINTER(IUnknown)() - NVDAHelper.localLib.rateLimitedUIAEventHandler_create( - self._com_pointers_[IUnknown._iid_], - byref(pRateLimitedEventHandler) - ) - else: - handler = self if utils._shouldSelectivelyRegister(): - self._createLocalEventHandlerGroup(handler) - self._registerGlobalEventHandlers(handler) + self._createLocalEventHandlerGroup() + self._registerGlobalEventHandlers() if winVersion.getWinVer() >= winVersion.WIN11: UIARemote.initialize(True, self.clientObject) except Exception as e: @@ -540,12 +527,9 @@ def MTAThreadFunc(self): else: break self.clientObject.RemoveAllEventHandlers() - del self.localEventHandlerGroup - del self.localEventHandlerGroupWithTextChanges - del self.globalEventHandlerGroup - def _registerGlobalEventHandlers(self, handler: "UIAHandler"): - self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest, handler) + def _registerGlobalEventHandlers(self): + self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest, self) if isinstance(self.clientObject, UIA.IUIAutomation6): self.globalEventHandlerGroup = self.clientObject.CreateEventHandlerGroup() else: @@ -553,7 +537,7 @@ def _registerGlobalEventHandlers(self, handler: "UIAHandler"): self.globalEventHandlerGroup.AddPropertyChangedEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, - handler, + self, *self.clientObject.IntSafeArrayToNativeArray( globalEventHandlerGroupUIAPropertyIds if utils._shouldSelectivelyRegister() @@ -569,7 +553,7 @@ def _registerGlobalEventHandlers(self, handler: "UIAHandler"): eventId, UIA.TreeScope_Subtree, self.baseCacheRequest, - handler + self ) if ( not utils._shouldSelectivelyRegister() @@ -580,24 +564,24 @@ def _registerGlobalEventHandlers(self, handler: "UIAHandler"): UIA.UIA_Text_TextChangedEventId, UIA.TreeScope_Subtree, self.baseCacheRequest, - handler + self ) # #7984: add support for notification event (IUIAutomation5, part of Windows 10 build 16299 and later). if isinstance(self.clientObject, UIA.IUIAutomation5): self.globalEventHandlerGroup.AddNotificationEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, - handler + self ) if isinstance(self.clientObject, UIA.IUIAutomation6): self.globalEventHandlerGroup.AddActiveTextPositionChangedEventHandler( UIA.TreeScope_Subtree, self.baseCacheRequest, - handler + self ) self.addEventHandlerGroup(self.rootElement, self.globalEventHandlerGroup) - def _createLocalEventHandlerGroup(self, handler: "UIAHandler"): + def _createLocalEventHandlerGroup(self): if isinstance(self.clientObject, UIA.IUIAutomation6): self.localEventHandlerGroup = self.clientObject.CreateEventHandlerGroup() self.localEventHandlerGroupWithTextChanges = self.clientObject.CreateEventHandlerGroup() @@ -607,13 +591,13 @@ def _createLocalEventHandlerGroup(self, handler: "UIAHandler"): self.localEventHandlerGroup.AddPropertyChangedEventHandler( UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, - handler, + self, *self.clientObject.IntSafeArrayToNativeArray(localEventHandlerGroupUIAPropertyIds) ) self.localEventHandlerGroupWithTextChanges.AddPropertyChangedEventHandler( UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, - handler, + self, *self.clientObject.IntSafeArrayToNativeArray(localEventHandlerGroupUIAPropertyIds) ) for eventId in localEventHandlerGroupUIAEventIds: @@ -621,19 +605,19 @@ def _createLocalEventHandlerGroup(self, handler: "UIAHandler"): eventId, UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, - handler + self ) self.localEventHandlerGroupWithTextChanges.AddAutomationEventHandler( eventId, UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, - handler + self ) self.localEventHandlerGroupWithTextChanges.AddAutomationEventHandler( UIA.UIA_Text_TextChangedEventId, UIA.TreeScope_Ancestors | UIA.TreeScope_Element, self.baseCacheRequest, - handler + self ) def addEventHandlerGroup(self, element, eventHandlerGroup): @@ -747,7 +731,6 @@ def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID): if _isDebug(): log.debugWarning(f"HandleAutomationEvent: Don't know how to handle event {eventID}") return - obj = None focus = api.getFocusObject() import NVDAObjects.UIA if ( @@ -755,48 +738,38 @@ def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID): and self.clientObject.compareElements(focus.UIAElement, sender) ): if _isDebug(): - log.debug( - "handleAutomationEvent: element matches focus. " - f"Redirecting event to focus NVDAObject {focus}" - ) - obj = focus + log.debug("handleAutomationEvent: element matches focus") + pass elif not self.isNativeUIAElement(sender): if _isDebug(): log.debug( f"HandleAutomationEvent: Ignoring event {NVDAEventName} for non native element" ) return - window = obj.windowHandle if obj else self.getNearestWindowHandle(sender) - if window: + window = self.getNearestWindowHandle(sender) + if window and not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): if _isDebug(): log.debug( - f"Checking if should accept NVDA event {NVDAEventName} " - f"with window {self.getWindowHandleDebugString(window)}" + f"HandleAutomationEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" ) - if not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): - if _isDebug(): - log.debug( - f"HandleAutomationEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" - ) - return - if not obj: - try: - obj = NVDAObjects.UIA.UIA(windowHandle=window, UIAElement=sender) - except Exception: - if _isDebug(): - log.debugWarning( - f"HandleAutomationEvent: Exception while creating object for event {NVDAEventName}", - exc_info=True - ) - return - if not obj: - if _isDebug(): - log.debug("handleAutomationEvent: No NVDAObject could be created") - return + return + try: + obj = NVDAObjects.UIA.UIA(UIAElement=sender) + except Exception: if _isDebug(): - log.debug( - f"handleAutomationEvent: created object {obj} " + log.debugWarning( + f"HandleAutomationEvent: Exception while creating object for event {NVDAEventName}", + exc_info=True ) + return + if not obj: + if _isDebug(): + log.debug("handleAutomationEvent: No NVDAObject could be created") + return + if _isDebug(): + log.debug( + f"handleAutomationEvent: created object {obj} " + ) if ( (NVDAEventName == "gainFocus" and not obj.shouldAllowUIAFocusEvent) or (NVDAEventName=="liveRegionChange" and not obj._shouldAllowUIALiveRegionChangeEvent) @@ -807,6 +780,10 @@ def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID): f"Ignoring event {NVDAEventName} because ignored by object itself" ) return + if obj==focus: + if _isDebug(): + log.debug("handleAutomationEvent: redirecting event to focus") + obj=focus if _isDebug(): log.debug( f"handleAutomationEvent: queuing NVDA event {NVDAEventName} " @@ -871,7 +848,7 @@ def IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent(self,sender): ) return try: - obj = NVDAObjects.UIA.UIA(windowHandle=window, UIAElement=sender) + obj = NVDAObjects.UIA.UIA(UIAElement=sender) except Exception: if _isDebug(): log.debugWarning( @@ -933,7 +910,6 @@ def IUIAutomationPropertyChangedEventHandler_HandlePropertyChangedEvent(self,sen if _isDebug(): log.debugWarning(f"HandlePropertyChangedEvent: Don't know how to handle property {propertyId}") return - obj = None focus = api.getFocusObject() import NVDAObjects.UIA if ( @@ -941,48 +917,42 @@ def IUIAutomationPropertyChangedEventHandler_HandlePropertyChangedEvent(self,sen and self.clientObject.compareElements(focus.UIAElement, sender) ): if _isDebug(): - log.debug( - "propertyChange event is for focus. " - f"Redirecting event to focus NVDAObject {focus}" - ) - obj = focus + log.debug("propertyChange event is for focus") + pass elif not self.isNativeUIAElement(sender): if _isDebug(): log.debug( f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} for non native element" ) return - window = obj.windowHandle if obj else self.getNearestWindowHandle(sender) - if window: + window = self.getNearestWindowHandle(sender) + if window and not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): if _isDebug(): log.debug( - f"Checking if should accept NVDA event {NVDAEventName} " - f"with window {self.getWindowHandleDebugString(window)}" + f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" ) - if not eventHandler.shouldAcceptEvent(NVDAEventName, windowHandle=window): - if _isDebug(): - log.debug( - f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} for shouldAcceptEvent=False" - ) - return - if not obj: - try: - obj = NVDAObjects.UIA.UIA(windowHandle=window, UIAElement=sender) - except Exception: - if _isDebug(): - log.debugWarning( - f"HandlePropertyChangedEvent: Exception while creating object for event {NVDAEventName}", - exc_info=True - ) - return - if not obj: - if _isDebug(): - log.debug(f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} because no object") - return + return + try: + obj = NVDAObjects.UIA.UIA(UIAElement=sender) + except Exception: if _isDebug(): - log.debug( - f"handlePropertyChangeEvent: created object {obj} " + log.debugWarning( + f"HandlePropertyChangedEvent: Exception while creating object for event {NVDAEventName}", + exc_info=True ) + return + if not obj: + if _isDebug(): + log.debug(f"HandlePropertyChangedEvent: Ignoring event {NVDAEventName} because no object") + return + if _isDebug(): + log.debug( + f"handlePropertyChangeEvent: created object {obj} " + ) + if obj==focus: + if _isDebug(): + log.debug("handlePropertyChangeEvent: redirecting to focus") + obj=focus if _isDebug(): log.debug( f"handlePropertyChangeEvent: queuing NVDA {NVDAEventName} event " diff --git a/source/config/configSpec.py b/source/config/configSpec.py index 87d44a30fe9..e9847da4f66 100644 --- a/source/config/configSpec.py +++ b/source/config/configSpec.py @@ -250,7 +250,6 @@ allowInChromium = integer(0, 3, default=0) # 0:default (where suitable), 1:Only when necessary, 2: where suitable, 3: always allowInMSWord = integer(0, 3, default=0) - enhancedEventProcessing = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled") [annotations] reportDetails = boolean(default=true) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 8ab48695fc9..b87650db86e 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -3007,16 +3007,6 @@ def __init__(self, parent): self.UIAInChromiumCombo.SetSelection(config.conf["UIA"]["allowInChromium"]) self.UIAInChromiumCombo.defaultValue = self._getDefaultValue(["UIA", "allowInChromium"]) - # Translators: This is the label for a COMBOBOX in the Advanced settings panel. - label = _("Use en&hanced event processing (requires restart)") - self.enhancedEventProcessingComboBox = cast(nvdaControls.FeatureFlagCombo, UIAGroup.addLabeledControl( - labelText=label, - wxCtrlClass=nvdaControls.FeatureFlagCombo, - keyPath=["UIA", "enhancedEventProcessing"], - conf=config.conf, - )) - self.bindHelpEvent("UIAEnhancedEventProcessing", self.enhancedEventProcessingComboBox) - # Translators: This is the label for a group of advanced options in the # Advanced settings panel label = _("Annotations") @@ -3315,7 +3305,6 @@ def haveConfigDefaultsBeenRestored(self): and self.UIAInMSExcelCheckBox.IsChecked() == self.UIAInMSExcelCheckBox.defaultValue and self.consoleCombo.GetSelection() == self.consoleCombo.defaultValue and self.UIAInChromiumCombo.GetSelection() == self.UIAInChromiumCombo.defaultValue - and self.enhancedEventProcessingComboBox.isValueConfigSpecDefault() and self.annotationsDetailsCheckBox.IsChecked() == self.annotationsDetailsCheckBox.defaultValue and self.ariaDescCheckBox.IsChecked() == self.ariaDescCheckBox.defaultValue and self.brailleLiveRegionsCombo.isValueConfigSpecDefault() @@ -3342,7 +3331,6 @@ def restoreToDefaults(self): self.UIAInMSExcelCheckBox.SetValue(self.UIAInMSExcelCheckBox.defaultValue) self.consoleCombo.SetSelection(self.consoleCombo.defaultValue == 'auto') self.UIAInChromiumCombo.SetSelection(self.UIAInChromiumCombo.defaultValue) - self.enhancedEventProcessingComboBox.resetToConfigSpecDefault() self.annotationsDetailsCheckBox.SetValue(self.annotationsDetailsCheckBox.defaultValue) self.ariaDescCheckBox.SetValue(self.ariaDescCheckBox.defaultValue) self.brailleLiveRegionsCombo.resetToConfigSpecDefault() @@ -3374,7 +3362,6 @@ def onSave(self): ) config.conf["featureFlag"]["cancelExpiredFocusSpeech"] = self.cancelExpiredFocusSpeechCombo.GetSelection() config.conf["UIA"]["allowInChromium"] = self.UIAInChromiumCombo.GetSelection() - self.enhancedEventProcessingComboBox.saveCurrentValueToConf() config.conf["terminals"]["speakPasswords"] = self.winConsoleSpeakPasswordsCheckBox.IsChecked() config.conf["terminals"]["keyboardSupportInLegacy"]=self.keyboardSupportInLegacyCheckBox.IsChecked() diffAlgoChoice = self.diffAlgoCombo.GetSelection() diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 905b1a39acc..c3b7178e6ae 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -124,9 +124,9 @@ Users of Poedit 1 are encouraged to update to Poedit 3 if they want to rely on e - NVDA will not fail to start anymore when the configuration file is corrupted, but it will restore the configuration to default as it did in the past. (#15690, @CyrilleB79) - Fixed support for System List view (``SysListView32``) controls in Windows Forms applications. (#15283, @LeonarddeR) - It is not possible anymore to overwrite NVDA's Python console history. (#15792, @CyrilleB79) -- NVDA should remain responsive when being flooded with many UI Automation events, e.g. when large chunks of text are printed to a terminal or when listening to voice messages in WhatsApp messenger. (#14888, #15169) - - This new behavior can be disabled using the new "Use enhanced event processing" setting in NVDA's advanced settings. - - +- In Word, the landing cell will now be correctly reported when using the native Word commands for table navigation ``alt+home``, ``alt+end``, ``alt+pageUp`` and ``alt+pageDown``. (#15805, @CyrilleB79) +- NVDA now resumes audio if the configuration of the output device changes or another application releases exclusive control of the device. (#15758, #15775, @jcsteh) +- Contracted braille input works properly again. (#15773, @aaclause) - NVDA is again able to track the focus in applications running within Windows Defender Application Guard (WDAG). (#15164) - The speech text is no longer updated when the mouse moves in the Speech Viewer. (#15952, @hwf1324) - NVDA will again switch back to browse mode when closing combo boxes with ``escape`` or ``alt+upArrow`` in Firefox or Chrome. (#15653) diff --git a/user_docs/en/userGuide.t2t b/user_docs/en/userGuide.t2t index 3fc25c60512..c13add6a517 100644 --- a/user_docs/en/userGuide.t2t +++ b/user_docs/en/userGuide.t2t @@ -2438,16 +2438,6 @@ However, for basic spreadsheet navigating / editing, this option may provide a v We still do not recommend that the majority of users turn this on by default, though we do welcome users of Microsoft Excel build 16.0.13522.10000 or higher to test this feature and provide feedback. Microsoft Excel's UI automation implementation is ever changing, and versions of Microsoft Office older than 16.0.13522.10000 may not expose enough information for this option to be of any use. -==== Use enhanced event processing ====[UIAEnhancedEventProcessing] - -|| . {.hideHeaderRow} | . | -| Options | Default (Enabled), Disabled, Enabled | -| Default | Enabled | - - -When this option is enabled, NVDA should remain responsive when being flooded with many UI Automation events, e.g. large amounts of text in a terminal. -After changing this option, you will need to restart NVDA for the change to take effect. - ==== Windows Console support ====[AdvancedSettingsConsoleUIA] || . {.hideHeaderRow} | . |