Rewrite the MSAA/UIA integration into conhost

This commit is contained in:
Leonard Hecker 2025-09-12 15:55:25 +02:00
parent 814f78ed2c
commit 3230cb18b6
69 changed files with 865 additions and 968 deletions

View File

@ -13,7 +13,6 @@
// - ulSize - The height of the cursor within this buffer
Cursor::Cursor(const ULONG ulSize, TextBuffer& parentBuffer) noexcept :
_parentBuffer{ parentBuffer },
_fHasMoved(false),
_fIsVisible(true),
_fIsOn(true),
_fIsDouble(false),
@ -35,11 +34,6 @@ til::point Cursor::GetPosition() const noexcept
return _cPosition;
}
bool Cursor::HasMoved() const noexcept
{
return _fHasMoved;
}
bool Cursor::IsVisible() const noexcept
{
return _fIsVisible;
@ -75,11 +69,6 @@ ULONG Cursor::GetSize() const noexcept
return _ulSize;
}
void Cursor::SetHasMoved(const bool fHasMoved) noexcept
{
_fHasMoved = fHasMoved;
}
void Cursor::SetIsVisible(const bool fIsVisible) noexcept
{
_fIsVisible = fIsVisible;
@ -249,7 +238,6 @@ void Cursor::CopyProperties(const Cursor& OtherCursor) noexcept
// We shouldn't copy the position as it will be already rearranged by the resize operation.
//_cPosition = pOtherCursor->_cPosition;
_fHasMoved = OtherCursor._fHasMoved;
_fIsVisible = OtherCursor._fIsVisible;
_fIsOn = OtherCursor._fIsOn;
_fIsDouble = OtherCursor._fIsDouble;

View File

@ -38,7 +38,6 @@ public:
Cursor(Cursor&&) = default;
Cursor& operator=(Cursor&&) & = delete;
bool HasMoved() const noexcept;
bool IsVisible() const noexcept;
bool IsOn() const noexcept;
bool IsBlinkingAllowed() const noexcept;
@ -54,7 +53,6 @@ public:
bool IsDeferDrawing() noexcept;
void EndDeferDrawing() noexcept;
void SetHasMoved(const bool fHasMoved) noexcept;
void SetIsVisible(const bool fIsVisible) noexcept;
void SetIsOn(const bool fIsOn) noexcept;
void SetBlinkingAllowed(const bool fIsOn) noexcept;
@ -90,7 +88,6 @@ private:
til::point _cPosition; // current position on screen (in screen buffer coords).
bool _fHasMoved;
bool _fIsVisible; // whether cursor is visible (set only through the API)
bool _fIsOn; // whether blinking cursor is on or not
bool _fIsDouble; // whether the cursor size should be doubled

View File

@ -154,7 +154,6 @@ public:
void UseMainScreenBuffer() override;
bool IsVtInputEnabled() const noexcept override;
void NotifyAccessibilityChange(const til::rect& changedRect) noexcept override;
void NotifyBufferRotation(const int delta) override;
void NotifyShellIntegrationMark() override;

View File

@ -347,11 +347,6 @@ bool Terminal::IsVtInputEnabled() const noexcept
return false;
}
void Terminal::NotifyAccessibilityChange(const til::rect& /*changedRect*/) noexcept
{
// This is only needed in conhost. Terminal handles accessibility in another way.
}
void Terminal::InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength)
{
if (_pfnCompletionsChanged)

View File

@ -0,0 +1,511 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "AccessibilityNotifier.h"
#include "../interactivity/inc/ServiceLocator.hpp"
using namespace Microsoft::Console;
using namespace Microsoft::Console::Interactivity;
template<typename U, typename T>
constexpr U satcast(T v) noexcept
{
static_assert(sizeof(U) <= sizeof(T), "use this for narrowing");
constexpr T min = std::numeric_limits<U>::min();
constexpr T max = std::numeric_limits<U>::max();
return gsl::narrow_cast<U>(v < min ? min : (v > max ? max : v));
}
AccessibilityNotifier::AccessibilityNotifier()
{
// Mirrors _timerEmitMSAA / _timerEmitUIA
memset(&_state, 0, sizeof(_state));
}
AccessibilityNotifier::~AccessibilityNotifier()
{
SetUIAProvider(nullptr);
}
void AccessibilityNotifier::Initialize(HWND hwnd, DWORD msaaDelay, DWORD uiaDelay) noexcept
{
_hwnd = hwnd;
// delay=INFINITE is intended to disable events completely, but realistically,
// even a delay of 1s makes little sense. So, the cut-off was set to 10s.
if (msaaDelay < 10000 && hwnd)
{
_msaaEnabled = true;
// msaaDelay=0 makes all events synchronous. That's how
// it used to work and has a huge performance impact.
if (msaaDelay != 0)
{
// Convert from milliseconds to 100-nanosecond intervals.
// Negative values indicate relative time.
_msaaDelay = static_cast<int64_t>(msaaDelay) * -10000;
}
}
if (uiaDelay < 10000)
{
_uiaEnabled = true;
if (uiaDelay != 0)
{
_uiaDelay = static_cast<int64_t>(uiaDelay) * -10000;
}
}
if (_msaaDelay || _uiaDelay)
{
_timer.reset(_createTimer(&_timerEmitMSAA));
}
// Triggers the computation of _delay and _delayWindow.
SetUIAProvider(nullptr);
}
void AccessibilityNotifier::SetUIAProvider(IRawElementProviderSimple* provider) noexcept
{
// NOTE: The assumption is that you're holding the console lock when calling any of the member functions.
// This is why we can safely update these members (no worker thread is running nor can be scheduled).
assert(ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked());
// If UIA events are disabled, don't set _uiaProvider either.
// It would trigger unnecessary work.
if (!_uiaEnabled)
{
return;
}
// Of course we must ensure our precious provider object doesn't go away.
if (provider)
{
provider->AddRef();
}
const auto old = _uiaProvider.exchange(provider, std::memory_order_relaxed);
// Before we can release the old object, we must ensure it's not in use by a worker thread.
WaitForThreadpoolTimerCallbacks(_timer.get(), TRUE);
if (old)
{
old->Release();
}
// Update the delay. If UIA is enabled now, use the UIA delay.
// Note that a delay of 0 means "no delay" and we signal that as _delay=nullptr.
//
// NOTE: We don't set a second timer just for UIA, because some applications like NVDA
// listen to both MSAA and UIA events. If they don't arrive approximately together,
// they'll be announced as seperate events, which breaks announcements.
if (const auto delay = provider ? &_uiaDelay : &_msaaDelay; *delay == 0)
{
_delay = nullptr;
_delayWindow = 0;
}
else
{
static_assert(sizeof(FILETIME) == sizeof(_uiaDelay));
_delay = reinterpret_cast<FILETIME*>(delay);
// Set the delay window to 1/5th of the delay, but in milliseconds.
_delayWindow = gsl::narrow_cast<DWORD>(std::max<int64_t>(0, *delay / (5 * -10000)));
}
// If we canceled the timer, reschedule it.
if (_state.timerScheduled)
{
_state.timerScheduled = false;
// Of course there's no point to schedule it if there isn't a provider.
if (provider)
{
_timerSet();
}
}
}
// Emits EVENT_CONSOLE_CARET, indicating the new cursor position.
// `rectangle` is the cursor rectangle in buffer coordinates (rows/columns)
// `flags` can be either CONSOLE_CARET_SELECTION _or_ CONSOLE_CARET_VISIBLE (not a bitfield)
//
//
// It then also Calls ConsoleControl() with ConsoleSetCaretInfo, which goes through the kernel sets
// cciConsole on the HWND and then raises EVENT_OBJECT_LOCATIONCHANGE with OBJID_CARET, INDEXID_CONTAINER.
// The cciConsole information is then used by GetGUIThreadInfo() to populate hwndCaret and rcCaret.
// Unfortunately there's no way to know whether anyone even needs this information so we always raise this.
void AccessibilityNotifier::CursorChanged(til::point position, bool activeSelection) noexcept
{
// Can't check for IsWinEventHookInstalled(EVENT_CONSOLE_CARET),
// because we need to emit a ConsoleControl() call regardless.
if (_msaaEnabled)
{
const auto guard = _lock.lock_exclusive();
_state.eventConsoleCaretPositionX = position.x;
_state.eventConsoleCaretPositionY = position.y;
_state.eventConsoleCaretSelecting = activeSelection;
_state.eventConsoleCaretPrimed = true;
_timerSet();
}
}
void AccessibilityNotifier::SelectionChanged() noexcept
{
if (_uiaProvider.load(std::memory_order_relaxed))
{
const auto guard = _lock.lock_exclusive();
_state.textSelectionChanged = true;
_timerSet();
}
}
// Emits EVENT_CONSOLE_UPDATE_REGION, the region of the console that changed.
void AccessibilityNotifier::RegionChanged(til::point begin, til::point end) noexcept
{
if (begin >= end)
{
return;
}
const auto msaa = _msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_REGION);
const auto uia = _uiaProvider.load(std::memory_order_relaxed) != nullptr;
if (!msaa && !uia)
{
return;
}
const auto guard = _lock.lock_exclusive();
if (msaa)
{
const til::HugeCoordType begX = begin.x;
const til::HugeCoordType begY = begin.y;
const til::HugeCoordType endX = end.x;
const til::HugeCoordType endY = end.y;
const auto primed = _state.eventConsoleUpdateRegionPrimed;
// Initialize the region (if !primed) or extend the region to the union of old and new.
if (!primed || begY < _state.eventConsoleUpdateRegionBeginY || (begY == _state.eventConsoleUpdateRegionBeginY && begX < _state.eventConsoleUpdateRegionBeginX))
{
_state.eventConsoleUpdateRegionBeginX = begX;
_state.eventConsoleUpdateRegionBeginY = begY;
_state.eventConsoleUpdateRegionPrimed = true;
}
if (!primed || endY > _state.eventConsoleUpdateRegionEndY || (endY == _state.eventConsoleUpdateRegionEndY && endX > _state.eventConsoleUpdateRegionEndX))
{
_state.eventConsoleUpdateRegionEndX = endX;
_state.eventConsoleUpdateRegionEndY = endY;
_state.eventConsoleUpdateRegionPrimed = true;
}
}
if (uia)
{
_state.textChanged = true;
}
_timerSet();
}
// Emits EVENT_CONSOLE_UPDATE_SCROLL. Specific to buffer scrolls and
// allows us to adjust previously cached buffer coordinates accordingly.
void AccessibilityNotifier::ScrollBuffer(til::CoordType delta) noexcept
{
if (_msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL))
{
const auto guard = _lock.lock_exclusive();
// They say accessibility is hard, but then they design EVENT_CONSOLE_UPDATE_SCROLL
// to count _both_ viewport scrolls _and_ buffer scrolls as the same thing,
// making the information carried by the event completely useless. Don't ask me.
//
// Fun fact: conhost "v2" (Windows 10+) would raise EVENT_CONSOLE_UPDATE_SCROLL events every
// time ScrollConsoleScreenBuffer is called. People ask me why I'm balding. They don't know.
_state.eventConsoleUpdateScrollDeltaY += delta;
_state.eventConsoleUpdateScrollPrimed = true;
if (_state.eventConsoleCaretPrimed)
{
_state.eventConsoleCaretPositionY += delta;
}
if (_state.eventConsoleUpdateRegionPrimed)
{
_state.eventConsoleUpdateRegionBeginY += delta;
_state.eventConsoleUpdateRegionEndY += delta;
}
_timerSet();
}
}
// Emits EVENT_CONSOLE_UPDATE_SCROLL. Specific to viewport scrolls.
void AccessibilityNotifier::ScrollViewport(til::point delta) noexcept
{
if (_msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL))
{
const auto guard = _lock.lock_exclusive();
_state.eventConsoleUpdateScrollDeltaX += delta.x;
_state.eventConsoleUpdateScrollDeltaY += delta.y;
_state.eventConsoleUpdateScrollPrimed = true;
_timerSet();
}
}
// Emits EVENT_CONSOLE_LAYOUT. Documentation just states "The console layout has changed."
// but it's absolutely unclear what that even means. Try to emit it when the scrollbar
// position or window size has changed... I guess.
void AccessibilityNotifier::Layout() noexcept
{
if (_msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_LAYOUT))
{
const auto guard = _lock.lock_exclusive();
_state.eventConsoleLayoutPrimed = true;
_timerSet();
}
}
void AccessibilityNotifier::ApplicationStart(DWORD pid) const noexcept
{
if (_msaaEnabled)
{
const auto cc = ServiceLocator::LocateConsoleControl<IConsoleControl>();
cc->NotifyWinEvent(EVENT_CONSOLE_START_APPLICATION, _hwnd, pid, 0);
}
}
void AccessibilityNotifier::ApplicationEnd(DWORD pid) const noexcept
{
if (_msaaEnabled)
{
const auto cc = ServiceLocator::LocateConsoleControl<IConsoleControl>();
cc->NotifyWinEvent(EVENT_CONSOLE_END_APPLICATION, _hwnd, pid, 0);
}
}
PTP_TIMER AccessibilityNotifier::_createTimer(PTP_TIMER_CALLBACK callback) noexcept
{
return THROW_LAST_ERROR_IF_NULL(CreateThreadpoolTimer(callback, this, nullptr));
}
void AccessibilityNotifier::_timerSet() noexcept
{
if (!_delay)
{
_emitMSAA(_state);
}
else if (!_state.timerScheduled)
{
_state.timerScheduled = true;
SetThreadpoolTimerEx(_timer.get(), _delay, 0, _delayWindow);
}
}
void AccessibilityNotifier::_timerEmitMSAA(PTP_CALLBACK_INSTANCE, PVOID context, PTP_TIMER) noexcept
{
const auto self = static_cast<AccessibilityNotifier*>(context);
State state;
// Make a copy of _state, because UIA and MSAA are very slow (up to 1ms per call).
// Holding a lock while _emitEventsCallback would mean that the IO thread can't proceed.
//
// The only concern I have is whether calling SetThreadpoolTimerEx() again on
// _timer while we're still executing will properly schedule another run.
// The docs say to read the "Remarks" and the remarks just don't clarify it. Great.
// FWIW we can't just create two timer objects since that may (theoretically)
// just end up with two callbacks running at the same time = same problem.
{
const auto guard = self->_lock.lock_exclusive();
// What we want to do is
// state = self->_state;
// self->_state = {};
// MSVC optimizes the first line with SIMD, but fails to do so for the second line.
// This forces us to use memset. memcpy is used for consistency.
static_assert(std::is_trivially_copyable_v<State>);
memcpy(&state, &self->_state, sizeof(State));
memset(&self->_state, 0, sizeof(State));
}
self->_emitMSAA(state);
}
void AccessibilityNotifier::_emitMSAA(State& state) const noexcept
{
const auto cc = ServiceLocator::LocateConsoleControl<IConsoleControl>();
const auto provider = _uiaProvider.load(std::memory_order_relaxed);
if (state.eventConsoleCaretPrimed)
{
const auto x = satcast<SHORT>(state.eventConsoleCaretPositionX);
const auto y = satcast<SHORT>(state.eventConsoleCaretPositionY);
// Technically, CONSOLE_CARET_SELECTION and CONSOLE_CARET_VISIBLE are bitflags,
// however Microsoft's _own_ example code for these assumes that they're an
// enumation and also assumes that a value of 0 (= invisible cursor) is invalid.
// So, we just pretend as if the cursor is always visible.
const auto flags = state.eventConsoleCaretSelecting ? CONSOLE_CARET_SELECTION : CONSOLE_CARET_VISIBLE;
// There's no need to check for IsWinEventHookInstalled,
// because NotifyWinEvent is very fast if no event is installed.
cc->NotifyWinEvent(EVENT_CONSOLE_CARET, _hwnd, flags, MAKELONG(x, y));
{
std::optional<CONSOLE_CARET_INFO> caretInfo;
// Convert the buffer position to the equivalent screen coordinates
// required by CONSOLE_CARET_INFO, taking line rendition into account.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (gci.HasActiveOutputBuffer())
{
auto& screenInfo = gci.GetActiveOutputBuffer();
auto& buffer = screenInfo.GetTextBuffer();
const auto position = buffer.BufferToScreenPosition({ x, y });
const auto viewport = screenInfo.GetViewport();
const auto fontSize = screenInfo.GetScreenFontSize();
const auto left = (position.x - viewport.Left()) * fontSize.width;
const auto top = (position.y - viewport.Top()) * fontSize.height;
caretInfo.emplace(CONSOLE_CARET_INFO{
.hwnd = _hwnd,
.rc = RECT{
left,
top,
left + fontSize.width,
top + fontSize.height,
},
});
}
gci.UnlockConsole();
if (caretInfo)
{
cc->Control(ControlType::ConsoleSetCaretInfo, &*caretInfo, sizeof(*caretInfo));
}
}
state.eventConsoleCaretPositionX = 0;
state.eventConsoleCaretPositionY = 0;
state.eventConsoleCaretSelecting = false;
state.eventConsoleCaretPrimed = false;
}
if (state.eventConsoleUpdateRegionPrimed)
{
const auto begX = satcast<SHORT>(state.eventConsoleUpdateRegionBeginX);
const auto begY = satcast<SHORT>(state.eventConsoleUpdateRegionBeginY);
const auto endX = satcast<SHORT>(state.eventConsoleUpdateRegionEndX);
const auto endY = satcast<SHORT>(state.eventConsoleUpdateRegionEndY);
const auto beg = MAKELONG(begX, begY);
const auto end = MAKELONG(endX, endY);
// Previously, we'd also emit a EVENT_CONSOLE_UPDATE_SIMPLE event for single-char updates,
// but in the 30 years since, the way fast software is written has changed:
// We now have plenty CPU power but the speed of light is still the same.
// It's much more important to batch events to avoid NotifyWinEvent's latency problems.
// EVENT_CONSOLE_UPDATE_SIMPLE is not trivially batchable and so it got removed.
//
// That said, NVDA is currently a very popular screen reader for Windows.
// IF you set its "Windows Console support" to "Legacy" AND disable
// "Use enhanced typed character support in legacy Windows Console when available"
// then it will purely rely on these WinEvents for accessibility.
//
// In this case it assumes that EVENT_CONSOLE_UPDATE_REGION is regular output
// and that EVENT_CONSOLE_UPDATE_SIMPLE is keyboard input (FYI: don't do this).
// The problem now is that it doesn't announce any EVENT_CONSOLE_UPDATE_REGION
// events where beg == end (i.e. a single character change).
//
// The good news is that if you set these two options in NVDA, it crashes whenever
// any conhost instance exits, so... maybe we don't need to work around this? :D
//
// I'll leave this code here, in case we ever need to shim EVENT_CONSOLE_UPDATE_SIMPLE.
#if 0
LONG charAndAttr = 0;
if (beg == end)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.LockConsole();
if (gci.HasActiveOutputBuffer())
{
auto& tb = gci.GetActiveOutputBuffer().GetTextBuffer();
const auto& row = tb.GetRowByOffset(begY);
const auto glyph = row.GlyphAt(begX);
const auto attr = row.GetAttrByColumn(begX);
charAndAttr = MAKELONG(Utf16ToUcs2(glyph), attr.GetLegacyAttributes());
}
gci.UnlockConsole();
}
if (charAndAttr)
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_SIMPLE, _hwnd, beg, charAndAttr);
}
else
{
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION, _hwnd, beg, end);
}
#else
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION, _hwnd, beg, end);
#endif
state.eventConsoleUpdateRegionBeginX = 0;
state.eventConsoleUpdateRegionBeginY = 0;
state.eventConsoleUpdateRegionEndX = 0;
state.eventConsoleUpdateRegionEndY = 0;
state.eventConsoleUpdateRegionPrimed = false;
}
if (state.eventConsoleUpdateScrollPrimed)
{
const auto dx = satcast<LONG>(state.eventConsoleUpdateScrollDeltaX);
const auto dy = satcast<LONG>(state.eventConsoleUpdateScrollDeltaY);
cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL, _hwnd, dx, dy);
state.eventConsoleUpdateScrollDeltaX = 0;
state.eventConsoleUpdateScrollDeltaY = 0;
state.eventConsoleUpdateScrollPrimed = false;
}
if (state.eventConsoleLayoutPrimed)
{
cc->NotifyWinEvent(EVENT_CONSOLE_LAYOUT, _hwnd, 0, 0);
state.eventConsoleLayoutPrimed = false;
}
if (state.textSelectionChanged)
{
_emitUIAEvent(provider, UIA_Text_TextSelectionChangedEventId);
state.textSelectionChanged = false;
}
if (state.textChanged)
{
_emitUIAEvent(provider, UIA_Text_TextChangedEventId);
state.textChanged = false;
}
}
void AccessibilityNotifier::_emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept
{
if (provider)
{
LOG_IF_FAILED(UiaRaiseAutomationEvent(provider, id));
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
struct IRawElementProviderSimple;
typedef int EVENTID;
namespace Microsoft::Console
{
// UIA and MSAA calls are both extraordinarily slow, so we got this
// handy class to batch then up and emit them on a background thread.
struct AccessibilityNotifier
{
AccessibilityNotifier();
~AccessibilityNotifier();
AccessibilityNotifier(const AccessibilityNotifier&) = delete;
AccessibilityNotifier& operator=(const AccessibilityNotifier&) = delete;
// NOTE: It is assumed that you're holding the console lock when calling any of the member functions.
// This class uses mutexes, but those are only for synchronizing with the worker threads.
void Initialize(HWND hwnd, DWORD msaaDelay, DWORD uiaDelay) noexcept;
void SetUIAProvider(IRawElementProviderSimple* provider) noexcept;
void CursorChanged(til::point position, bool activeSelection) noexcept;
void SelectionChanged() noexcept;
void RegionChanged(til::point begin, til::point end) noexcept;
void ScrollBuffer(til::CoordType delta) noexcept;
void ScrollViewport(til::point delta) noexcept;
void Layout() noexcept;
void ApplicationStart(DWORD pid) const noexcept;
void ApplicationEnd(DWORD pid) const noexcept;
private:
// !!! NOTE !!!
// * _emitEventsCallback assumes that this struct can be quickly initialized with memset(0).
// * Members are intentionally left undefined so that we can create instances on the stack without initialization.
struct State
{
// EVENT_CONSOLE_CARET / ConsoleControl(ConsoleSetCaretInfo)
til::HugeCoordType eventConsoleCaretPositionX;
til::HugeCoordType eventConsoleCaretPositionY;
bool eventConsoleCaretSelecting;
bool eventConsoleCaretPrimed;
// EVENT_CONSOLE_UPDATE_REGION
til::HugeCoordType eventConsoleUpdateRegionBeginX;
til::HugeCoordType eventConsoleUpdateRegionBeginY;
til::HugeCoordType eventConsoleUpdateRegionEndX;
til::HugeCoordType eventConsoleUpdateRegionEndY;
bool eventConsoleUpdateRegionPrimed;
// EVENT_CONSOLE_UPDATE_SCROLL
til::HugeCoordType eventConsoleUpdateScrollDeltaX;
til::HugeCoordType eventConsoleUpdateScrollDeltaY;
bool eventConsoleUpdateScrollPrimed;
// EVENT_CONSOLE_LAYOUT
bool eventConsoleLayoutPrimed;
// UIA
bool textSelectionChanged; // UIA_Text_TextSelectionChangedEventId
bool textChanged; // UIA_Text_TextChangedEventId
bool timerScheduled;
};
PTP_TIMER _createTimer(PTP_TIMER_CALLBACK callback) noexcept;
void _timerSet() noexcept;
static void _timerEmitMSAA(PTP_CALLBACK_INSTANCE instance, PVOID context, PTP_TIMER timer) noexcept;
void _emitMSAA(State& msaa) const noexcept;
static void _emitUIAEvent(IRawElementProviderSimple* provider, EVENTID id) noexcept;
// The main window, used for NotifyWinEvent / ConsoleControl(ConsoleSetCaretInfo) calls.
HWND _hwnd = nullptr;
// The current UIA provider, if any.
std::atomic<IRawElementProviderSimple*> _uiaProvider{ nullptr };
// The timer object used to schedule debounced a11y events.
// It's null if the delay is set to 0.
wil::unique_threadpool_timer _timer;
// The delay to use for MSAA/UIA events, in filetime units (100ns units).
// The value will be negative because that's what SetThreadpoolTimerEx needs.
int64_t _msaaDelay = 0;
int64_t _uiaDelay = 0;
// Depending on whether we have a UIA provider or not, this points to either _msaaDelay or _uiaDelay.
FILETIME* _delay = nullptr;
// The delay window to use for SetThreadpoolTimerEx, in milliseconds.
DWORD _delayWindow = 0;
// Whether MSAA and UIA are enabled.
bool _msaaEnabled = false;
bool _uiaEnabled = false;
// _lock protects access to _state.
wil::srwlock _lock;
State _state;
};
}

View File

@ -81,50 +81,12 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept
auto& buffer = ScreenInfo.GetTextBuffer();
auto& cursor = buffer.GetCursor();
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS))
{
goto DoScroll;
}
// Update the cursor pos in USER so accessibility will work.
// Don't do all this work or send events if we don't have a notifier target.
if (pAccessibilityNotifier && cursor.HasMoved())
{
// Convert the buffer position to the equivalent screen coordinates
// required by the notifier, taking line rendition into account.
const auto position = buffer.BufferToScreenPosition(cursor.GetPosition());
const auto viewport = ScreenInfo.GetViewport();
const auto fontSize = ScreenInfo.GetScreenFontSize();
cursor.SetHasMoved(false);
til::rect rc;
rc.left = (position.x - viewport.Left()) * fontSize.width;
rc.top = (position.y - viewport.Top()) * fontSize.height;
rc.right = rc.left + fontSize.width;
rc.bottom = rc.top + fontSize.height;
pAccessibilityNotifier->NotifyConsoleCaretEvent(rc);
// Send accessibility information
{
auto flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretInvisible;
// Flags is expected to be 2, 1, or 0. 2 in selecting (whether or not visible), 1 if just visible, 0 if invisible/noselect.
if (WI_IsFlagSet(gci.Flags, CONSOLE_SELECTING))
{
flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection;
}
else if (cursor.IsVisible())
{
flags = IAccessibilityNotifier::ConsoleCaretEventFlags::CaretVisible;
}
pAccessibilityNotifier->NotifyConsoleCaretEvent(flags, MAKELONG(position.x, position.y));
}
}
// If the DelayCursor flag has been set, wait one more tick before toggle.
// This is used to guarantee the cursor is on for a finite period of time
// after a move and off for a finite period of time after a WriteString.

View File

@ -253,12 +253,13 @@ static FillConsoleResult FillConsoleImpl(SCREEN_INFORMATION& screenInfo, FillCon
ImageSlice::EraseCells(screenInfo.GetTextBuffer(), startingCoordinate, result.cellsModified);
}
if (screenBuffer.HasAccessibilityEventing())
{
// Notify accessibility
auto endingCoordinate = startingCoordinate;
bufferSize.WalkInBounds(endingCoordinate, result.cellsModified);
screenBuffer.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y);
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.RegionChanged(startingCoordinate, endingCoordinate);
}
return result;

View File

@ -31,6 +31,39 @@ constexpr bool controlCharPredicate(wchar_t wch)
return wch < L' ' || wch == 0x007F;
}
static auto raiseAccessibilityEventsOnExit(SCREEN_INFORMATION& screenInfo)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferBefore = gci.GetActiveOutputBuffer();
const auto cursorBefore = bufferBefore.GetTextBuffer().GetCursor().GetPosition();
auto raise = wil::scope_exit([&bufferBefore, cursorBefore] {
// !!! NOTE !!! `bufferBefore` may now be a stale pointer, because VT
// sequences can switch between the main and alternative screen buffer.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto& bufferAfter = gci.GetActiveOutputBuffer();
const auto cursorAfter = bufferAfter.GetTextBuffer().GetCursor().GetPosition();
if (&bufferBefore == &bufferAfter)
{
an.RegionChanged(cursorBefore, cursorAfter);
}
if (cursorBefore != cursorAfter)
{
an.CursorChanged(cursorAfter, false);
}
});
// Don't raise any events for inactive buffers.
if (&bufferBefore != &screenInfo)
{
raise.release();
}
return raise;
}
// Routine Description:
// - This routine updates the cursor position. Its input is the non-special
// cased new location of the cursor. For example, if the cursor were being
@ -76,12 +109,13 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
auto& buffer = screenInfo.GetTextBuffer();
buffer.IncrementCircularBuffer(buffer.GetCurrentAttributes());
// TODO: This is very bad for performance.
// Track the total scroll offset as a int64 in buffer --> No need to track it here anymore.
if (buffer.IsActiveBuffer())
{
if (const auto notifier = ServiceLocator::LocateAccessibilityNotifier())
{
notifier->NotifyConsoleUpdateScrollEvent(0, -1);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.ScrollBuffer(-1);
if (const auto renderer = ServiceLocator::LocateGlobals().pRender)
{
static constexpr til::point delta{ 0, -1 };
@ -104,7 +138,6 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point
static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const std::wstring_view& text, til::CoordType* psScrollY)
{
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
const auto hasAccessibilityEventing = screenInfo.HasAccessibilityEventing();
auto& textBuffer = screenInfo.GetTextBuffer();
bool wrapped = false;
@ -127,11 +160,6 @@ static bool _writeCharsLegacyUnprocessed(SCREEN_INFORMATION& screenInfo, const s
textBuffer.SetWrapForced(cursorPosition.y, true);
}
if (hasAccessibilityEventing && state.columnEnd > state.columnBegin)
{
screenInfo.NotifyAccessibilityEventing(state.columnBegin, cursorPosition.y, state.columnEnd - 1, cursorPosition.y);
}
AdjustCursorPosition(screenInfo, cursorPosition, psScrollY);
}
@ -148,6 +176,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
auto& textBuffer = screenInfo.GetTextBuffer();
const auto width = textBuffer.GetSize().Width();
auto& cursor = textBuffer.GetCursor();
const auto cursorPosBefore = cursor.GetPosition();
const auto wrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
const auto beg = text.begin();
const auto end = text.end();
@ -156,6 +185,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
const auto a11y = raiseAccessibilityEventsOnExit(screenInfo);
const auto snap = screenInfo.SnapOnOutput();
// If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode.
@ -343,6 +373,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str)
// may change, so get the VtIo reference now, just in case.
auto writer = gci.GetVtWriterForBuffer(&screenInfo);
const auto a11y = raiseAccessibilityEventsOnExit(screenInfo);
const auto snap = screenInfo.SnapOnOutput();
stateMachine.ProcessString(str);

View File

@ -188,7 +188,7 @@ namespace Conhost.UIA.Tests
Log.Comment("---Status Request Commands---");
Globals.WaitForTimeout();
app.UIRoot.SendKeys("c");
string expectedTitle = string.Format("Response Received: {0}", "\x1b[?1;0c");
string expectedTitle = string.Format("Response Received: {0}", "\x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c");
Globals.WaitForTimeout();
string title = app.GetWindowTitle();

View File

@ -796,6 +796,8 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
// how the cmd shell's CLS command resets the buffer.
buffer.UpdateBottom();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(buffer.GetTextBuffer().GetCursor().GetPosition(), false);
return S_OK;
}
CATCH_RETURN();

View File

@ -17,19 +17,18 @@ Revision History:
#pragma once
#include "AccessibilityNotifier.h"
#include "ApiRoutines.h"
#include "ConsoleArguments.hpp"
#include "selection.hpp"
#include "server.h"
#include "ConsoleArguments.hpp"
#include "ApiRoutines.h"
#include "../propslib/DelegationConfig.hpp"
#include "../renderer/base/Renderer.hpp"
#include "../server/DeviceComm.h"
#include "../tsf/Handle.h"
#include "../server/ConDrvDeviceComm.h"
#include <TraceLoggingProvider.h>
#include <winmeta.h>
TRACELOGGING_DECLARE_PROVIDER(g_hConhostV2EventTraceProvider);
class Globals
@ -63,6 +62,7 @@ public:
std::vector<wchar_t> WordDelimiters;
Microsoft::Console::Render::Renderer* pRender;
Microsoft::Console::TSF::Handle tsf;
Microsoft::Console::AccessibilityNotifier accessibilityNotifier;
Microsoft::Console::Render::IFontDefaultList* pFontDefaultList;
bool IsHeadless() const;

View File

@ -47,6 +47,7 @@
<ClCompile Include="..\writeData.cpp" />
<ClCompile Include="..\_output.cpp" />
<ClCompile Include="..\_stream.cpp" />
<ClCompile Include="..\AccessibilityNotifier.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\IIoProvider.hpp" />
@ -95,6 +96,7 @@
<ClInclude Include="..\writeData.hpp" />
<ClInclude Include="..\_output.h" />
<ClInclude Include="..\_stream.h" />
<ClInclude Include="..\AccessibilityNotifier.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\audio\midi\lib\midi.vcxproj">

View File

@ -355,6 +355,6 @@ void ProcessCtrlEvents()
// The bad news is that EndTask() returns STATUS_UNSUCCESSFUL no matter whether
// the process was already dead, or if the request actually failed for some reason.
// Hopefully there aren't any regressions, but we can't know without trying.
LOG_IF_NTSTATUS_FAILED(ctrl->EndTask(r.dwProcessID, EventType, CtrlFlags));
ctrl->EndTask(r.dwProcessID, EventType, CtrlFlags);
}
}

View File

@ -147,6 +147,9 @@
<ClCompile Include="..\CursorBlinker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\AccessibilityNotifier.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h">
@ -287,6 +290,9 @@
<ClInclude Include="..\IIoProvider.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\AccessibilityNotifier.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />

View File

@ -5,10 +5,6 @@
#include "_output.h"
#include "output.h"
#include "handle.h"
#include "getset.h"
#include "misc.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/Viewport.hpp"
@ -283,17 +279,8 @@ void ScreenBufferSizeChange(const til::size coordNewSize)
// - source - The viewport describing the region where data was copied from
// - fill - The viewport describing the area that was filled in with the fill character (uncovered area)
// - target - The viewport describing the region where data was copied to
static void _ScrollScreen(SCREEN_INFORMATION& screenInfo, const Viewport& source, const Viewport& fill, const Viewport& target)
static void _ScrollScreen(SCREEN_INFORMATION& screenInfo, const Viewport& fill, const Viewport& target)
{
if (screenInfo.IsActiveScreenBuffer())
{
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier != nullptr)
{
pNotifier->NotifyConsoleUpdateScrollEvent(target.Origin().x - source.Left(), target.Origin().y - source.RightInclusive());
}
}
// Get the text buffer and send it commands.
// It will figure out whether or not we're active and where the messages need to go.
auto& textBuffer = screenInfo.GetTextBuffer();
@ -413,7 +400,7 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
_CopyRectangle(screenInfo, source, target.Origin());
// Notify the renderer and accessibility as to what moved and where.
_ScrollScreen(screenInfo, source, fill, target);
_ScrollScreen(screenInfo, fill, target);
}
// ------ 6. FILL ------

View File

@ -408,42 +408,14 @@ bool ConhostInternalGetSet::IsVtInputEnabled() const
return _io.GetActiveInputBuffer()->IsInVirtualTerminalInputMode();
}
// Routine Description:
// - Lets accessibility apps know when an area of the screen has changed.
// Arguments:
// - changedRect - the area that has changed.
// Return value:
// - <none>
void ConhostInternalGetSet::NotifyAccessibilityChange(const til::rect& changedRect)
{
auto& screenInfo = _io.GetActiveOutputBuffer();
if (screenInfo.HasAccessibilityEventing() && changedRect)
{
screenInfo.NotifyAccessibilityEventing(
changedRect.left,
changedRect.top,
changedRect.right - 1,
changedRect.bottom - 1);
}
}
// Routine Description:
// - Implements conhost-specific behavior when the buffer is rotated.
// Arguments:
// - delta - the number of cycles that the buffer has rotated.
// Return value:
// - <none>
void ConhostInternalGetSet::NotifyBufferRotation(const int delta)
void ConhostInternalGetSet::NotifyBufferRotation(const int)
{
auto& screenInfo = _io.GetActiveOutputBuffer();
if (screenInfo.IsActiveScreenBuffer())
{
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier)
{
pNotifier->NotifyConsoleUpdateScrollEvent(0, -delta);
}
}
}
void ConhostInternalGetSet::NotifyShellIntegrationMark()

View File

@ -65,7 +65,6 @@ public:
bool IsVtInputEnabled() const override;
void NotifyAccessibilityChange(const til::rect& changedRect) override;
void NotifyBufferRotation(const int delta) override;
void NotifyShellIntegrationMark() override;

View File

@ -7,7 +7,8 @@
#include "output.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/CodepointWidthDetector.hpp"
#include "../types/inc/convert.hpp"
#include "../terminal/adapter/adaptDispatch.hpp"
#include "../terminal/parser/OutputStateMachineEngine.hpp"
using namespace Microsoft::Console;
using namespace Microsoft::Console::Types;
@ -19,7 +20,6 @@ using namespace Microsoft::Console::VirtualTerminal;
SCREEN_INFORMATION::SCREEN_INFORMATION(
_In_ IWindowMetrics* pMetrics,
_In_ IAccessibilityNotifier* pNotifier,
const TextAttribute popupAttributes,
const FontInfo fontInfo) :
OutputMode{ ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT },
@ -31,7 +31,6 @@ SCREEN_INFORMATION::SCREEN_INFORMATION(
FillOutDbcsLeadChar{ 0 },
ScrollScale{ 1ul },
_pConsoleWindowMetrics{ pMetrics },
_pAccessibilityNotifier{ pNotifier },
_api{ *this },
_stateMachine{ nullptr },
_viewport(Viewport::Empty()),
@ -89,11 +88,10 @@ SCREEN_INFORMATION::~SCREEN_INFORMATION()
auto pMetrics = ServiceLocator::LocateWindowMetrics();
THROW_HR_IF_NULL(E_FAIL, pMetrics);
const auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
// It is possible for pNotifier to be null and that's OK.
// For instance, the PTY doesn't need to send events. Just pass it along
// and be sure that `SCREEN_INFORMATION` bypasses all event work if it's not there.
const auto pScreen = new SCREEN_INFORMATION(pMetrics, pNotifier, popupAttributes, fontInfo);
const auto pScreen = new SCREEN_INFORMATION(pMetrics, popupAttributes, fontInfo);
// Set up viewport
pScreen->_viewport = Viewport::FromDimensions({ 0, 0 },
@ -572,70 +570,6 @@ void SCREEN_INFORMATION::UpdateFont(const FontInfo* const pfiNewFont)
}
}
// Routine Description:
// - Informs clients whether we have accessibility eventing so they can
// save themselves the work of performing math or lookups before calling
// `NotifyAccessibilityEventing`.
// Arguments:
// - <none>
// Return Value:
// - True if we have an accessibility listener. False otherwise.
bool SCREEN_INFORMATION::HasAccessibilityEventing() const noexcept
{
return _pAccessibilityNotifier;
}
// NOTE: This method was historically used to notify accessibility apps AND
// to aggregate drawing metadata to determine whether or not to use PolyTextOut.
// After the Nov 2015 graphics refactor, the metadata drawing flag calculation is no longer necessary.
// This now only notifies accessibility apps of a change.
void SCREEN_INFORMATION::NotifyAccessibilityEventing(const til::CoordType sStartX,
const til::CoordType sStartY,
const til::CoordType sEndX,
const til::CoordType sEndY)
{
if (!_pAccessibilityNotifier)
{
return;
}
// Fire off a winevent to let accessibility apps know what changed.
if (IsActiveScreenBuffer())
{
const auto coordScreenBufferSize = GetBufferSize().Dimensions();
FAIL_FAST_IF(!(sEndX < coordScreenBufferSize.width));
if (sStartX == sEndX && sStartY == sEndY)
{
try
{
const auto cellData = GetCellDataAt({ sStartX, sStartY });
const auto charAndAttr = MAKELONG(Utf16ToUcs2(cellData->Chars()),
cellData->TextAttr().GetLegacyAttributes());
_pAccessibilityNotifier->NotifyConsoleUpdateSimpleEvent(MAKELONG(sStartX, sStartY),
charAndAttr);
}
catch (...)
{
LOG_HR(wil::ResultFromCaughtException());
return;
}
}
else
{
_pAccessibilityNotifier->NotifyConsoleUpdateRegionEvent(MAKELONG(sStartX, sStartY),
MAKELONG(sEndX, sEndY));
}
auto pConsoleWindow = ServiceLocator::LocateConsoleWindow();
if (pConsoleWindow)
{
LOG_IF_FAILED(pConsoleWindow->SignalUia(UIA_Text_TextChangedEventId));
// TODO MSFT 7960168 do we really need this event to not signal?
//pConsoleWindow->SignalUia(UIA_LayoutInvalidatedEventId);
}
}
}
#pragma endregion
#pragma region UI_Refresh
@ -663,10 +597,7 @@ SCREEN_INFORMATION::ScrollBarState SCREEN_INFORMATION::FetchScrollBarState()
WI_ClearFlag(gci.Flags, CONSOLE_UPDATING_SCROLL_BARS);
// Fire off an event to let accessibility apps know the layout has changed.
if (_pAccessibilityNotifier)
{
_pAccessibilityNotifier->NotifyConsoleLayoutEvent();
}
ServiceLocator::LocateGlobals().accessibilityNotifier.Layout();
const auto buffer = GetBufferSize();
const auto isAltBuffer = _IsAltBuffer();
@ -1481,15 +1412,12 @@ NT_CATCH_RETURN()
if (SUCCEEDED_NTSTATUS(status))
{
if (HasAccessibilityEventing())
{
NotifyAccessibilityEventing(0, 0, coordNewScreenSize.width - 1, coordNewScreenSize.height - 1);
}
// Fire off an event to let accessibility apps know the layout has changed.
if (_pAccessibilityNotifier && IsActiveScreenBuffer())
if (IsActiveScreenBuffer())
{
_pAccessibilityNotifier->NotifyConsoleLayoutEvent();
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.RegionChanged({}, { coordNewScreenSize.width - 1, coordNewScreenSize.height - 1 });
an.Layout();
}
if (fDoScrollBarUpdate)
@ -1665,7 +1593,6 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor)
{
cursor.SetDelay(true);
}
cursor.SetHasMoved(true);
}
return STATUS_SUCCESS;

View File

@ -19,8 +19,6 @@ Revision History:
#pragma once
#include "conapi.h"
#include "settings.hpp"
#include "outputStream.hpp"
#include "../buffer/out/OutputCellRect.hpp"
@ -30,15 +28,10 @@ Revision History:
#include "../buffer/out/textBufferTextIterator.hpp"
#include "IIoProvider.hpp"
#include "outputStream.hpp"
#include "../terminal/adapter/adaptDispatch.hpp"
#include "../terminal/parser/stateMachine.hpp"
#include "../terminal/parser/OutputStateMachineEngine.hpp"
#include "../server/ObjectHeader.h"
#include "../interactivity/inc/IAccessibilityNotifier.hpp"
#include "../interactivity/inc/IConsoleWindow.hpp"
#include "../interactivity/inc/IWindowMetrics.hpp"
#include "../renderer/inc/FontInfo.hpp"
@ -110,9 +103,6 @@ public:
[[nodiscard]] NTSTATUS ResizeScreenBuffer(const til::size coordNewScreenSize, const bool fDoScrollBarUpdate);
bool HasAccessibilityEventing() const noexcept;
void NotifyAccessibilityEventing(const til::CoordType sStartX, const til::CoordType sStartY, const til::CoordType sEndX, const til::CoordType sEndY);
struct ScrollBarState
{
til::size maxSize;
@ -236,12 +226,10 @@ public:
private:
SCREEN_INFORMATION(_In_ Microsoft::Console::Interactivity::IWindowMetrics* pMetrics,
_In_ Microsoft::Console::Interactivity::IAccessibilityNotifier* pNotifier,
const TextAttribute popupAttributes,
const FontInfo fontInfo);
Microsoft::Console::Interactivity::IWindowMetrics* _pConsoleWindowMetrics;
Microsoft::Console::Interactivity::IAccessibilityNotifier* _pAccessibilityNotifier;
[[nodiscard]] HRESULT _AdjustScreenBufferHelper(const til::rect* const prcClientNew,
const til::size coordBufferOld,

View File

@ -3,7 +3,6 @@
#include "precomp.h"
#include "_output.h"
#include "stream.h"
#include "scrolling.hpp"
@ -121,10 +120,9 @@ void Selection::_SetSelectionVisibility(const bool fMakeVisible)
_PaintSelection();
}
if (const auto window = ServiceLocator::LocateConsoleWindow())
{
LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId));
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
}
// Routine Description:
@ -179,15 +177,14 @@ void Selection::InitializeMouseSelection(const til::point coordBufferPos)
if (pWindow != nullptr)
{
pWindow->UpdateWindowText();
LOG_IF_FAILED(pWindow->SignalUia(UIA_Text_TextSelectionChangedEventId));
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
}
// Fire off an event to let accessibility apps know the selection has changed.
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier)
{
pNotifier->NotifyConsoleCaretEvent(IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection, PACKCOORD(coordBufferPos));
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(coordBufferPos, true);
}
// Routine Description:
@ -303,14 +300,9 @@ void Selection::_ExtendSelection(Selection::SelectionData* d, _In_ til::point co
_PaintSelection();
// Fire off an event to let accessibility apps know the selection has changed.
if (const auto pNotifier = ServiceLocator::LocateAccessibilityNotifier())
{
pNotifier->NotifyConsoleCaretEvent(IAccessibilityNotifier::ConsoleCaretEventFlags::CaretSelection, PACKCOORD(coordBufferPos));
}
if (const auto window = ServiceLocator::LocateConsoleWindow())
{
LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId));
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(coordBufferPos, true);
an.SelectionChanged();
}
// Routine Description:
@ -338,7 +330,9 @@ void Selection::_CancelMouseSelection()
}
// Mark the cursor position as changed so we'll fire off a win event.
ScreenInfo.GetTextBuffer().GetCursor().SetHasMoved(true);
// NOTE(lhecker): Why is this the only cancel function that would raise a WinEvent? Makes no sense to me.
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(ScreenInfo.GetTextBuffer().GetCursor().GetPosition(), false);
}
// Routine Description:
@ -402,10 +396,9 @@ void Selection::ClearSelection(const bool fStartingNewSelection)
{
_CancelMarkSelection();
}
if (const auto window = ServiceLocator::LocateConsoleWindow())
{
LOG_IF_FAILED(window->SignalUia(UIA_Text_TextSelectionChangedEventId));
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
auto d{ _d.write() };
wil::hide_name _d;
@ -523,7 +516,9 @@ void Selection::InitializeMarkSelection()
if (pWindow != nullptr)
{
pWindow->UpdateWindowText();
LOG_IF_FAILED(pWindow->SignalUia(UIA_Text_TextSelectionChangedEventId));
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SelectionChanged();
}
}

View File

@ -20,7 +20,6 @@ Revision History:
#include "input.h"
#include "../interactivity/inc/IAccessibilityNotifier.hpp"
#include "../interactivity/inc/IConsoleWindow.hpp"
#include "til/generational.h"

View File

@ -4,9 +4,7 @@
#include "precomp.h"
#include "../buffer/out/search.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/convert.hpp"
using namespace Microsoft::Console::Types;
using Microsoft::Console::Interactivity::ServiceLocator;
@ -903,11 +901,13 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe
d->fUseAlternateSelection = false;
}
cursor.SetHasMoved(true);
d->coordSelectionAnchor = textBuffer.GetCursor().GetPosition();
ScreenInfo.MakeCursorVisible(d->coordSelectionAnchor);
d->srSelectionRect.left = d->srSelectionRect.right = d->coordSelectionAnchor.x;
d->srSelectionRect.top = d->srSelectionRect.bottom = d->coordSelectionAnchor.y;
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.CursorChanged(d->coordSelectionAnchor, true);
}
return true;
}

View File

@ -4,7 +4,6 @@
#include "precomp.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/viewport.hpp"
using namespace Microsoft::Console::Types;

View File

@ -777,6 +777,16 @@ std::wstring_view Settings::GetAnswerbackMessage() const noexcept
return _answerbackMessage;
}
DWORD Settings::GetMSAADelay() const noexcept
{
return _msaaDelay;
}
DWORD Settings::GetUIADelay() const noexcept
{
return _uiaDelay;
}
// Determines whether our primary renderer should be DirectX or GDI.
// This is based on user preference and velocity hold back state.
bool Settings::GetUseDx() const noexcept

View File

@ -179,6 +179,8 @@ public:
std::wstring_view GetAnswerbackMessage() const noexcept;
DWORD GetMSAADelay() const noexcept;
DWORD GetUIADelay() const noexcept;
bool GetUseDx() const noexcept;
bool GetCopyColor() const noexcept;
SettingsTextMeasurementMode GetTextMeasurementMode() const noexcept;
@ -225,6 +227,8 @@ private:
std::wstring _LaunchFaceName;
bool _fAllowAltF4Close;
DWORD _dwVirtTermLevel;
DWORD _msaaDelay = 100;
DWORD _uiaDelay = 25;
SettingsTextMeasurementMode _textMeasurement = SettingsTextMeasurementMode::Graphemes;
bool _fUseDx;
bool _fCopyColor;
@ -238,7 +242,7 @@ private:
bool _fInterceptCopyPaste;
bool _TerminalScrolling;
bool _TerminalScrolling = true;
WCHAR _answerbackMessage[32] = {};
friend class RegistrySerialization;
};

View File

@ -11,6 +11,7 @@
#include "../interactivity/base/ApiDetector.hpp"
#include "../interactivity/base/RemoteConsoleControl.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../server/ConDrvDeviceComm.h"
#include "../server/DeviceHandle.h"
#include "../server/IoSorter.h"
#include "../types/inc/CodepointWidthDetector.hpp"
@ -73,17 +74,6 @@ try
Globals.defaultTerminalMarkerCheckRequired = true;
}
// Create the accessibility notifier early in the startup process.
// Only create if we're not in PTY mode.
// The notifiers use expensive legacy MSAA events and the PTY isn't even responsible
// for the terminal user interface, so we should set ourselves up to skip all
// those notifications and the mathematical calculations required to send those events
// for performance reasons.
if (!args->InConptyMode())
{
RETURN_IF_FAILED(ServiceLocator::CreateAccessibilityNotifier());
}
// Removed allocation of scroll buffer here.
return S_OK;
}

View File

@ -3,17 +3,11 @@
#include "precomp.h"
#include "_stream.h"
#include "stream.h"
#include "handle.h"
#include "misc.h"
#include "readDataRaw.hpp"
#include "ApiRoutines.h"
#include "../types/inc/GlyphWidth.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
using Microsoft::Console::Interactivity::ServiceLocator;

View File

@ -363,9 +363,6 @@ void TextBufferTests::TestCopyProperties()
VERIFY_IS_NOT_NULL(testTextBuffer.get());
// set initial mapping values
testTextBuffer->GetCursor().SetHasMoved(false);
otherTbi.GetCursor().SetHasMoved(true);
testTextBuffer->GetCursor().SetIsVisible(false);
otherTbi.GetCursor().SetIsVisible(true);
@ -382,7 +379,6 @@ void TextBufferTests::TestCopyProperties()
testTextBuffer->CopyProperties(otherTbi);
// test that new now contains values from other
VERIFY_IS_TRUE(testTextBuffer->GetCursor().HasMoved());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsVisible());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsOn());
VERIFY_IS_TRUE(testTextBuffer->GetCursor().IsDouble());

View File

@ -9,6 +9,10 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
inline constexpr CoordType CoordTypeMin = INT32_MIN;
inline constexpr CoordType CoordTypeMax = INT32_MAX;
using HugeCoordType = int64_t;
inline constexpr HugeCoordType HugeCoordTypeMin = INT64_MIN;
inline constexpr HugeCoordType HugeCoordTypeMax = INT64_MAX;
namespace details
{
template<typename T, typename U = T>
@ -249,6 +253,15 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
};
}
constexpr COORD unwrap_coord_clamped(const point pt) noexcept
{
constexpr short min = -32768;
constexpr short max = 32767;
const auto x = pt.x < min ? min : (pt.x > max ? max : gsl::narrow_cast<short>(pt.x));
const auto y = pt.y < min ? min : (pt.y > max ? max : gsl::narrow_cast<short>(pt.y));
return { x, y };
}
constexpr HRESULT unwrap_coord_hr(const point pt, COORD& out) noexcept
{
short x = 0;

View File

@ -86,10 +86,8 @@ T HostSignalInputThread::_ReceiveTypedPacket()
{
case HostSignals::NotifyApp:
{
auto msg = _ReceiveTypedPacket<HostSignalNotifyAppData>();
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(msg.processId));
const auto msg = _ReceiveTypedPacket<HostSignalNotifyAppData>();
ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(msg.processId);
break;
}
case HostSignals::SetForeground:
@ -104,10 +102,8 @@ T HostSignalInputThread::_ReceiveTypedPacket()
}
case HostSignals::EndTask:
{
auto msg = _ReceiveTypedPacket<HostSignalEndTaskData>();
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->EndTask(msg.processId, msg.eventType, msg.ctrlFlags));
const auto msg = _ReceiveTypedPacket<HostSignalEndTaskData>();
ServiceLocator::LocateConsoleControl()->EndTask(msg.processId, msg.eventType, msg.ctrlFlags);
break;
}
default:

View File

@ -17,7 +17,6 @@
#include "..\onecore\WindowMetrics.hpp"
#endif
#include "../win32/AccessibilityNotifier.hpp"
#include "../win32/ConsoleControl.hpp"
#include "../win32/ConsoleInputThread.hpp"
#include "../win32/WindowDpiApi.hpp"
@ -199,48 +198,6 @@ using namespace Microsoft::Console::Interactivity;
return status;
}
[[nodiscard]] NTSTATUS InteractivityFactory::CreateAccessibilityNotifier(_Inout_ std::unique_ptr<IAccessibilityNotifier>& notifier)
{
auto status = STATUS_SUCCESS;
ApiLevel level;
status = ApiDetector::DetectNtUserWindow(&level);
if (SUCCEEDED_NTSTATUS(status))
{
std::unique_ptr<IAccessibilityNotifier> newNotifier;
try
{
switch (level)
{
case ApiLevel::Win32:
newNotifier = std::make_unique<Microsoft::Console::Interactivity::Win32::AccessibilityNotifier>();
break;
#ifdef BUILD_ONECORE_INTERACTIVITY
case ApiLevel::OneCore:
newNotifier = std::make_unique<Microsoft::Console::Interactivity::OneCore::AccessibilityNotifier>();
break;
#endif
default:
status = STATUS_INVALID_LEVEL;
break;
}
}
catch (...)
{
status = NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
if (SUCCEEDED_NTSTATUS(status))
{
notifier.swap(newNotifier);
}
}
return status;
}
[[nodiscard]] NTSTATUS InteractivityFactory::CreateSystemConfigurationProvider(_Inout_ std::unique_ptr<ISystemConfigurationProvider>& provider)
{
auto status = STATUS_SUCCESS;

View File

@ -24,7 +24,6 @@ namespace Microsoft::Console::Interactivity
[[nodiscard]] NTSTATUS CreateHighDpiApi(_Inout_ std::unique_ptr<IHighDpiApi>& api);
[[nodiscard]] NTSTATUS CreateWindowMetrics(_Inout_ std::unique_ptr<IWindowMetrics>& metrics);
[[nodiscard]] NTSTATUS CreateAccessibilityNotifier(_Inout_ std::unique_ptr<IAccessibilityNotifier>& notifier);
[[nodiscard]] NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr<ISystemConfigurationProvider>& provider);
[[nodiscard]] NTSTATUS CreatePseudoWindow(HWND& hwnd);

View File

@ -14,10 +14,20 @@ RemoteConsoleControl::RemoteConsoleControl(HANDLE signalPipe) :
{
}
void RemoteConsoleControl::Control(ControlType, PVOID, DWORD) noexcept
{
WI_ASSERT_FAIL();
}
void RemoteConsoleControl::NotifyWinEvent(DWORD, HWND, LONG, LONG) noexcept
{
WI_ASSERT_FAIL();
}
#pragma region IConsoleControl Members
template<typename T>
[[nodiscard]] NTSTATUS _SendTypedPacket(HANDLE pipe, ::Microsoft::Console::HostSignals signalCode, T& payload)
void _SendTypedPacket(HANDLE pipe, ::Microsoft::Console::HostSignals signalCode, T& payload) noexcept
{
// To ensure it's a happy wire format, pack it tight at 1.
#pragma pack(push, 1)
@ -33,21 +43,10 @@ template<typename T>
packet.data = payload;
DWORD bytesWritten = 0;
if (!WriteFile(pipe, &packet, sizeof(packet), &bytesWritten, nullptr))
{
const auto gle = ::GetLastError();
NT_RETURN_NTSTATUS(static_cast<NTSTATUS>(NTSTATUS_FROM_WIN32(gle)));
}
if (bytesWritten != sizeof(packet))
{
NT_RETURN_NTSTATUS(static_cast<NTSTATUS>(NTSTATUS_FROM_WIN32(E_UNEXPECTED)));
}
return STATUS_SUCCESS;
LOG_IF_WIN32_BOOL_FALSE(WriteFile(pipe, &packet, sizeof(packet), &bytesWritten, nullptr));
}
[[nodiscard]] NTSTATUS RemoteConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId)
void RemoteConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId) noexcept
{
HostSignalNotifyAppData data{};
data.sizeInBytes = sizeof(data);
@ -56,15 +55,15 @@ template<typename T>
return _SendTypedPacket(_pipe.get(), HostSignals::NotifyApp, data);
}
[[nodiscard]] NTSTATUS RemoteConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground)
void RemoteConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) noexcept
{
// GH#13211 - Apparently this API doesn't need to be forwarded to conhost at
// all. Instead, just perform the ConsoleControl operation here, in proc.
// This lets us avoid all sorts of strange handle duplicating weirdness.
return _control.SetForeground(hProcess, fForeground);
_control.SetForeground(hProcess, fForeground);
}
[[nodiscard]] NTSTATUS RemoteConsoleControl::EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags)
void RemoteConsoleControl::EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) noexcept
{
HostSignalEndTaskData data{};
data.sizeInBytes = sizeof(data);
@ -75,11 +74,11 @@ template<typename T>
return _SendTypedPacket(_pipe.get(), HostSignals::EndTask, data);
}
[[nodiscard]] NTSTATUS RemoteConsoleControl::SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId)
void RemoteConsoleControl::SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept
{
// This call doesn't need to get forwarded to the root conhost. Just handle
// it in-proc, to set the owner of OpenConsole
return _control.SetWindowOwner(hwnd, processId, threadId);
_control.SetWindowOwner(hwnd, processId, threadId);
}
#pragma endregion

View File

@ -24,10 +24,12 @@ namespace Microsoft::Console::Interactivity
RemoteConsoleControl(HANDLE signalPipe);
// IConsoleControl Members
[[nodiscard]] NTSTATUS NotifyConsoleApplication(_In_ DWORD dwProcessId);
[[nodiscard]] NTSTATUS SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground);
[[nodiscard]] NTSTATUS EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags);
[[nodiscard]] NTSTATUS SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId);
void Control(ControlType command, PVOID ptr, DWORD len) noexcept override;
void NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept override;
void NotifyConsoleApplication(_In_ DWORD dwProcessId) noexcept override;
void SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) noexcept override;
void EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) noexcept override;
void SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept override;
private:
wil::unique_handle _pipe;

View File

@ -23,7 +23,6 @@ std::unique_ptr<IConsoleControl> ServiceLocator::s_consoleControl;
std::unique_ptr<IConsoleInputThread> ServiceLocator::s_consoleInputThread;
std::unique_ptr<IConsoleWindow> ServiceLocator::s_consoleWindow;
std::unique_ptr<IWindowMetrics> ServiceLocator::s_windowMetrics;
std::unique_ptr<IAccessibilityNotifier> ServiceLocator::s_accessibilityNotifier;
std::unique_ptr<IHighDpiApi> ServiceLocator::s_highDpiApi;
std::unique_ptr<ISystemConfigurationProvider> ServiceLocator::s_systemConfigurationProvider;
void (*ServiceLocator::s_oneCoreTeardownFunction)() = nullptr;
@ -137,24 +136,6 @@ void ServiceLocator::RundownAndExit(const HRESULT hr)
return status;
}
[[nodiscard]] HRESULT ServiceLocator::CreateAccessibilityNotifier()
{
// Can't create if we've already created.
if (s_accessibilityNotifier)
{
return E_UNEXPECTED;
}
if (!s_interactivityFactory)
{
RETURN_IF_NTSTATUS_FAILED(ServiceLocator::LoadInteractivityFactory());
}
RETURN_IF_NTSTATUS_FAILED(s_interactivityFactory->CreateAccessibilityNotifier(s_accessibilityNotifier));
return S_OK;
}
#pragma endregion
#pragma region Set Methods
@ -277,11 +258,6 @@ IWindowMetrics* ServiceLocator::LocateWindowMetrics()
return s_windowMetrics.get();
}
IAccessibilityNotifier* ServiceLocator::LocateAccessibilityNotifier()
{
return s_accessibilityNotifier.get();
}
ISystemConfigurationProvider* ServiceLocator::LocateSystemConfigurationProvider()
{
auto status = STATUS_SUCCESS;

View File

@ -34,7 +34,6 @@
<ClCompile Include="..\VtApiRedirection.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\inc\IAccessibilityNotifier.hpp" />
<ClInclude Include="..\..\inc\IConsoleControl.hpp" />
<ClInclude Include="..\..\inc\IConsoleInputThread.hpp" />
<ClInclude Include="..\..\inc\IConsoleWindow.hpp" />

View File

@ -53,9 +53,6 @@
<ClInclude Include="..\..\inc\ServiceLocator.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\IAccessibilityNotifier.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\IConsoleControl.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@ -101,5 +98,6 @@
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>

View File

@ -1,41 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- IAccessibilityNotifier.hpp
Abstract:
- Defines accessibility notification methods used by accessibility systems to
provide accessible access to the console.
Author(s):
- Hernan Gatta (HeGatta) 29-Mar-2017
--*/
#pragma once
namespace Microsoft::Console::Interactivity
{
class IAccessibilityNotifier
{
public:
enum class ConsoleCaretEventFlags
{
CaretInvisible,
CaretSelection,
CaretVisible
};
virtual ~IAccessibilityNotifier() = default;
virtual void NotifyConsoleCaretEvent(_In_ const til::rect& rectangle) = 0;
virtual void NotifyConsoleCaretEvent(_In_ ConsoleCaretEventFlags flags, _In_ LONG position) = 0;
virtual void NotifyConsoleUpdateScrollEvent(_In_ LONG x, _In_ LONG y) = 0;
virtual void NotifyConsoleUpdateSimpleEvent(_In_ LONG start, _In_ LONG charAndAttribute) = 0;
virtual void NotifyConsoleUpdateRegionEvent(_In_ LONG startXY, _In_ LONG endXY) = 0;
virtual void NotifyConsoleLayoutEvent() = 0;
virtual void NotifyConsoleStartApplicationEvent(_In_ DWORD processId) = 0;
virtual void NotifyConsoleEndApplicationEvent(_In_ DWORD processId) = 0;
};
}

View File

@ -17,13 +17,27 @@ Author(s):
namespace Microsoft::Console::Interactivity
{
enum class ControlType
{
ConsoleSetVDMCursorBounds,
ConsoleNotifyConsoleApplication,
ConsoleFullscreenSwitch,
ConsoleSetCaretInfo,
ConsoleSetReserveKeys,
ConsoleSetForeground,
ConsoleSetWindowOwner,
ConsoleEndTask,
};
class IConsoleControl
{
public:
virtual ~IConsoleControl() = default;
[[nodiscard]] virtual NTSTATUS NotifyConsoleApplication(DWORD dwProcessId) = 0;
[[nodiscard]] virtual NTSTATUS SetForeground(HANDLE hProcess, BOOL fForeground) = 0;
[[nodiscard]] virtual NTSTATUS EndTask(DWORD dwProcessId, DWORD dwEventType, ULONG ulCtrlFlags) = 0;
[[nodiscard]] virtual NTSTATUS SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) = 0;
virtual void Control(ControlType command, PVOID ptr, DWORD len) noexcept = 0;
virtual void NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept = 0;
virtual void NotifyConsoleApplication(DWORD dwProcessId) noexcept = 0;
virtual void SetForeground(HANDLE hProcess, BOOL fForeground) noexcept = 0;
virtual void EndTask(DWORD dwProcessId, DWORD dwEventType, ULONG ulCtrlFlags) noexcept = 0;
virtual void SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept = 0;
};
}

View File

@ -58,7 +58,6 @@ namespace Microsoft::Console::Types
virtual void VerticalScroll(const WORD wScrollCommand,
const WORD wAbsoluteChange) = 0;
[[nodiscard]] virtual HRESULT SignalUia(_In_ EVENTID id) = 0;
[[nodiscard]] virtual HRESULT UiaSetTextAreaFocus() = 0;
virtual til::rect GetWindowRect() const noexcept = 0;
};

View File

@ -21,7 +21,6 @@ Author(s):
#include "IHighDpiApi.hpp"
#include "IWindowMetrics.hpp"
#include "IAccessibilityNotifier.hpp"
#include "ISystemConfigurationProvider.hpp"
#include <memory>
@ -37,7 +36,6 @@ namespace Microsoft::Console::Interactivity
[[nodiscard]] virtual NTSTATUS CreateHighDpiApi(_Inout_ std::unique_ptr<IHighDpiApi>& api) = 0;
[[nodiscard]] virtual NTSTATUS CreateWindowMetrics(_Inout_ std::unique_ptr<IWindowMetrics>& metrics) = 0;
[[nodiscard]] virtual NTSTATUS CreateAccessibilityNotifier(_Inout_ std::unique_ptr<IAccessibilityNotifier>& notifier) = 0;
[[nodiscard]] virtual NTSTATUS CreateSystemConfigurationProvider(_Inout_ std::unique_ptr<ISystemConfigurationProvider>& provider) = 0;
[[nodiscard]] virtual NTSTATUS CreatePseudoWindow(HWND& hwnd) = 0;

View File

@ -39,9 +39,6 @@ namespace Microsoft::Console::Interactivity
// In case the on-demand creation fails, the return value
// is nullptr and a message is logged.
[[nodiscard]] static HRESULT CreateAccessibilityNotifier();
static IAccessibilityNotifier* LocateAccessibilityNotifier();
[[nodiscard]] static NTSTATUS SetConsoleControlInstance(_In_ std::unique_ptr<IConsoleControl>&& control);
static IConsoleControl* LocateConsoleControl();
template<typename T>
@ -96,7 +93,6 @@ namespace Microsoft::Console::Interactivity
static std::unique_ptr<IInteractivityFactory> s_interactivityFactory;
static std::unique_ptr<IAccessibilityNotifier> s_accessibilityNotifier;
static std::unique_ptr<IConsoleControl> s_consoleControl;
static std::unique_ptr<IConsoleInputThread> s_consoleInputThread;
// TODO: MSFT 15344939 - some implementations of IConsoleWindow are currently singleton

View File

@ -1,40 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "AccessibilityNotifier.hpp"
using namespace Microsoft::Console::Interactivity::OneCore;
void AccessibilityNotifier::NotifyConsoleCaretEvent(_In_ const til::rect& /*rectangle*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleCaretEvent(_In_ ConsoleCaretEventFlags /*flags*/, _In_ LONG /*position*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleUpdateScrollEvent(_In_ LONG /*x*/, _In_ LONG /*y*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleUpdateSimpleEvent(_In_ LONG /*start*/, _In_ LONG /*charAndAttribute*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleUpdateRegionEvent(_In_ LONG /*startXY*/, _In_ LONG /*endXY*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleLayoutEvent() noexcept
{
}
void AccessibilityNotifier::NotifyConsoleStartApplicationEvent(_In_ DWORD /*processId*/) noexcept
{
}
void AccessibilityNotifier::NotifyConsoleEndApplicationEvent(_In_ DWORD /*processId*/) noexcept
{
}

View File

@ -1,35 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- IAccessibilityNotifier.hpp
Abstract:
- OneCore implementation of the IAccessibilityNotifier interface.
Author(s):
- Hernan Gatta (HeGatta) 29-Mar-2017
--*/
#pragma once
#include "../inc/IAccessibilityNotifier.hpp"
#pragma hdrstop
namespace Microsoft::Console::Interactivity::OneCore
{
class AccessibilityNotifier : public IAccessibilityNotifier
{
public:
void NotifyConsoleCaretEvent(_In_ const til::rect& rectangle) noexcept override;
void NotifyConsoleCaretEvent(_In_ ConsoleCaretEventFlags flags, _In_ LONG position) noexcept override;
void NotifyConsoleUpdateScrollEvent(_In_ LONG x, _In_ LONG y) noexcept override;
void NotifyConsoleUpdateSimpleEvent(_In_ LONG start, _In_ LONG charAndAttribute) noexcept override;
void NotifyConsoleUpdateRegionEvent(_In_ LONG startXY, _In_ LONG endXY) noexcept override;
void NotifyConsoleLayoutEvent() noexcept override;
void NotifyConsoleStartApplicationEvent(_In_ DWORD processId) noexcept override;
void NotifyConsoleEndApplicationEvent(_In_ DWORD processId) noexcept override;
};
}

View File

@ -12,6 +12,10 @@ using namespace Microsoft::Console::Interactivity::OneCore;
#pragma region IConsoleControl Members
void ConsoleControl::NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept
{
}
[[nodiscard]] NTSTATUS ConsoleControl::NotifyConsoleApplication(_In_ DWORD /*dwProcessId*/) noexcept
{
return STATUS_SUCCESS;

View File

@ -24,6 +24,7 @@ namespace Microsoft::Console::Interactivity::OneCore
{
public:
// IConsoleControl Members
void NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept override;
[[nodiscard]] NTSTATUS NotifyConsoleApplication(_In_ DWORD dwProcessId) noexcept override;
[[nodiscard]] NTSTATUS SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) noexcept override;
[[nodiscard]] NTSTATUS EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) override;

View File

@ -111,11 +111,6 @@ void ConsoleWindow::VerticalScroll(const WORD /*wScrollCommand*/, const WORD /*w
{
}
[[nodiscard]] HRESULT ConsoleWindow::SignalUia(_In_ EVENTID /*id*/) noexcept
{
return E_NOTIMPL;
}
[[nodiscard]] HRESULT ConsoleWindow::UiaSetTextAreaFocus() noexcept
{
return E_NOTIMPL;

View File

@ -51,7 +51,6 @@ namespace Microsoft::Console::Interactivity::OneCore
void HorizontalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange) noexcept override;
void VerticalScroll(const WORD wScrollCommand, const WORD wAbsoluteChange) noexcept override;
[[nodiscard]] HRESULT SignalUia(_In_ EVENTID id) noexcept override;
[[nodiscard]] HRESULT UiaSetTextAreaFocus() noexcept override;
til::rect GetWindowRect() const noexcept override;
};

View File

@ -1,135 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "AccessibilityNotifier.hpp"
#include "../inc/ServiceLocator.hpp"
#include "ConsoleControl.hpp"
using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity::Win32;
void AccessibilityNotifier::NotifyConsoleCaretEvent(_In_ const til::rect& rectangle)
{
const auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow != nullptr)
{
CONSOLE_CARET_INFO caretInfo;
caretInfo.hwnd = pWindow->GetWindowHandle();
caretInfo.rc = rectangle.to_win32_rect();
LOG_IF_FAILED(ServiceLocator::LocateConsoleControl<ConsoleControl>()->Control(ConsoleControl::ControlType::ConsoleSetCaretInfo,
&caretInfo,
sizeof(caretInfo)));
}
}
void AccessibilityNotifier::NotifyConsoleCaretEvent(_In_ ConsoleCaretEventFlags flags, _In_ LONG position)
{
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
DWORD dwFlags = 0;
if (flags == ConsoleCaretEventFlags::CaretSelection)
{
dwFlags = CONSOLE_CARET_SELECTION;
}
else if (flags == ConsoleCaretEventFlags::CaretVisible)
{
dwFlags = CONSOLE_CARET_VISIBLE;
}
// UIA event notification
static til::point previousCursorLocation;
const auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow != nullptr)
{
NotifyWinEvent(EVENT_CONSOLE_CARET,
pWindow->GetWindowHandle(),
dwFlags,
position);
const auto& screenInfo = gci.GetActiveOutputBuffer();
const auto& cursor = screenInfo.GetTextBuffer().GetCursor();
const auto currentCursorPosition = cursor.GetPosition();
if (currentCursorPosition != previousCursorLocation)
{
LOG_IF_FAILED(pWindow->SignalUia(UIA_Text_TextSelectionChangedEventId));
}
previousCursorLocation = currentCursorPosition;
}
}
void AccessibilityNotifier::NotifyConsoleUpdateScrollEvent(_In_ LONG x, _In_ LONG y)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL,
pWindow->GetWindowHandle(),
x,
y);
}
}
void AccessibilityNotifier::NotifyConsoleUpdateSimpleEvent(_In_ LONG start, _In_ LONG charAndAttribute)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_UPDATE_SIMPLE,
pWindow->GetWindowHandle(),
start,
charAndAttribute);
}
}
void AccessibilityNotifier::NotifyConsoleUpdateRegionEvent(_In_ LONG startXY, _In_ LONG endXY)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_UPDATE_REGION,
pWindow->GetWindowHandle(),
startXY,
endXY);
}
}
void AccessibilityNotifier::NotifyConsoleLayoutEvent()
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_LAYOUT,
pWindow->GetWindowHandle(),
0,
0);
}
}
void AccessibilityNotifier::NotifyConsoleStartApplicationEvent(_In_ DWORD processId)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_START_APPLICATION,
pWindow->GetWindowHandle(),
processId,
0);
}
}
void AccessibilityNotifier::NotifyConsoleEndApplicationEvent(_In_ DWORD processId)
{
auto pWindow = ServiceLocator::LocateConsoleWindow();
if (pWindow)
{
NotifyWinEvent(EVENT_CONSOLE_END_APPLICATION,
pWindow->GetWindowHandle(),
processId,
0);
}
}

View File

@ -1,39 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- IAccessibilityNotifier.hpp
Abstract:
- Win32 implementation of the IAccessibilityNotifier interface.
Author(s):
- Hernan Gatta (HeGatta) 29-Mar-2017
--*/
#pragma once
#include "precomp.h"
#include "../inc/IAccessibilityNotifier.hpp"
#pragma hdrstop
namespace Microsoft::Console::Interactivity::Win32
{
class AccessibilityNotifier final : public IAccessibilityNotifier
{
public:
~AccessibilityNotifier() = default;
void NotifyConsoleCaretEvent(_In_ const til::rect& rectangle);
void NotifyConsoleCaretEvent(_In_ ConsoleCaretEventFlags flags, _In_ LONG position);
void NotifyConsoleUpdateScrollEvent(_In_ LONG x, _In_ LONG y);
void NotifyConsoleUpdateSimpleEvent(_In_ LONG start, _In_ LONG charAndAttribute);
void NotifyConsoleUpdateRegionEvent(_In_ LONG startXY, _In_ LONG endXY);
void NotifyConsoleLayoutEvent();
void NotifyConsoleStartApplicationEvent(_In_ DWORD processId);
void NotifyConsoleEndApplicationEvent(_In_ DWORD processId);
};
}

View File

@ -13,31 +13,51 @@
using namespace Microsoft::Console::Interactivity::Win32;
#pragma region IConsoleControl Members
#ifdef CON_USERPRIVAPI_INDIRECT
ConsoleControl::ConsoleControl()
{
// NOTE: GetModuleHandleW is rather expensive, but GetProcAddress is quite cheap.
const auto user32 = GetModuleHandleW(L"user32.dll");
_consoleControl = reinterpret_cast<PfnConsoleControl>(GetProcAddress(user32, "ConsoleControl"));
_enterReaderModeHelper = reinterpret_cast<PfnEnterReaderModeHelper>(GetProcAddress(user32, "EnterReaderModeHelper"));
_translateMessageEx = reinterpret_cast<PfnTranslateMessageEx>(GetProcAddress(user32, "TranslateMessageEx"));
}
#endif
[[nodiscard]] NTSTATUS ConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId)
void ConsoleControl::Control(ControlType command, PVOID ptr, DWORD len) noexcept
{
#ifdef CON_USERPRIVAPI_INDIRECT
if (_consoleControl)
{
LOG_IF_FAILED(_consoleControl(command, ptr, len));
}
#else
LOG_IF_FAILED(::ConsoleControl(command, ptr, len));
#endif
}
void ConsoleControl::NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept
{
::NotifyWinEvent(event, hwnd, idObject, idChild);
}
void ConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId) noexcept
{
CONSOLE_PROCESS_INFO cpi;
cpi.dwProcessID = dwProcessId;
cpi.dwFlags = CPI_NEWPROCESSWINDOW;
return Control(ControlType::ConsoleNotifyConsoleApplication,
&cpi,
sizeof(CONSOLE_PROCESS_INFO));
Control(ControlType::ConsoleNotifyConsoleApplication, &cpi, sizeof(CONSOLE_PROCESS_INFO));
}
[[nodiscard]] NTSTATUS ConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground)
void ConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) noexcept
{
CONSOLESETFOREGROUND Flags;
Flags.hProcess = hProcess;
Flags.bForeground = fForeground;
return Control(ControlType::ConsoleSetForeground,
&Flags,
sizeof(Flags));
Control(ControlType::ConsoleSetForeground, &Flags, sizeof(Flags));
}
[[nodiscard]] NTSTATUS ConsoleControl::EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags)
void ConsoleControl::EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) noexcept
{
auto pConsoleWindow = ServiceLocator::LocateConsoleWindow();
@ -46,108 +66,32 @@ using namespace Microsoft::Console::Interactivity::Win32;
ConsoleEndTaskParams.ConsoleEventCode = dwEventType;
ConsoleEndTaskParams.ConsoleFlags = ulCtrlFlags;
ConsoleEndTaskParams.hwnd = pConsoleWindow == nullptr ? nullptr : pConsoleWindow->GetWindowHandle();
return Control(ControlType::ConsoleEndTask,
&ConsoleEndTaskParams,
sizeof(ConsoleEndTaskParams));
Control(ControlType::ConsoleEndTask, &ConsoleEndTaskParams, sizeof(ConsoleEndTaskParams));
}
[[nodiscard]] NTSTATUS ConsoleControl::SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept
void ConsoleControl::SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept
{
CONSOLEWINDOWOWNER ConsoleOwner;
ConsoleOwner.hwnd = hwnd;
ConsoleOwner.ProcessId = processId;
ConsoleOwner.ThreadId = threadId;
return Control(ConsoleControl::ControlType::ConsoleSetWindowOwner,
&ConsoleOwner,
sizeof(ConsoleOwner));
}
#pragma endregion
#pragma region Public Methods
[[nodiscard]] NTSTATUS ConsoleControl::Control(_In_ ControlType ConsoleCommand,
_In_reads_bytes_(ConsoleInformationLength) PVOID ConsoleInformation,
_In_ DWORD ConsoleInformationLength)
{
#ifdef CON_USERPRIVAPI_INDIRECT
if (_hUser32 != nullptr)
{
typedef NTSTATUS(WINAPI * PfnConsoleControl)(ControlType Command, PVOID Information, DWORD Length);
static auto pfn = (PfnConsoleControl)GetProcAddress(_hUser32, "ConsoleControl");
if (pfn != nullptr)
{
return pfn(ConsoleCommand, ConsoleInformation, ConsoleInformationLength);
}
}
return STATUS_UNSUCCESSFUL;
#else
return ConsoleControl(ConsoleCommand, ConsoleInformation, ConsoleInformationLength);
#endif
Control(ControlType::ConsoleSetWindowOwner, &ConsoleOwner, sizeof(ConsoleOwner));
}
BOOL ConsoleControl::EnterReaderModeHelper(_In_ HWND hwnd)
{
#ifdef CON_USERPRIVAPI_INDIRECT
if (_hUser32 != nullptr)
{
typedef BOOL(WINAPI * PfnEnterReaderModeHelper)(HWND hwnd);
static auto pfn = (PfnEnterReaderModeHelper)GetProcAddress(_hUser32, "EnterReaderModeHelper");
if (pfn != nullptr)
{
return pfn(hwnd);
}
}
return FALSE;
return _enterReaderModeHelper ? _enterReaderModeHelper(hwnd) : FALSE;
#else
return EnterReaderModeHelper(hwnd);
return ::EnterReaderModeHelper(hwnd);
#endif
}
BOOL ConsoleControl::TranslateMessageEx(const MSG* pmsg,
_In_ UINT flags)
BOOL ConsoleControl::TranslateMessageEx(const MSG* pmsg, _In_ UINT flags)
{
#ifdef CON_USERPRIVAPI_INDIRECT
if (_hUser32 != nullptr)
{
typedef BOOL(WINAPI * PfnTranslateMessageEx)(const MSG* pmsg, UINT flags);
static auto pfn = (PfnTranslateMessageEx)GetProcAddress(_hUser32, "TranslateMessageEx");
if (pfn != nullptr)
{
return pfn(pmsg, flags);
}
}
return FALSE;
return _translateMessageEx ? _translateMessageEx(pmsg, flags) : FALSE;
#else
return TranslateMessageEx(pmsg, flags);
return ::TranslateMessageEx(pmsg, flags);
#endif
}
#pragma endregion
#ifdef CON_USERPRIVAPI_INDIRECT
ConsoleControl::ConsoleControl()
{
_hUser32 = LoadLibraryW(L"user32.dll");
}
ConsoleControl::~ConsoleControl()
{
if (_hUser32 != nullptr)
{
FreeLibrary(_hUser32);
_hUser32 = nullptr;
}
}
#endif

View File

@ -32,28 +32,13 @@ namespace Microsoft::Console::Interactivity::Win32
class ConsoleControl final : public IConsoleControl
{
public:
enum ControlType
{
ConsoleSetVDMCursorBounds,
ConsoleNotifyConsoleApplication,
ConsoleFullscreenSwitch,
ConsoleSetCaretInfo,
ConsoleSetReserveKeys,
ConsoleSetForeground,
ConsoleSetWindowOwner,
ConsoleEndTask,
};
// IConsoleControl Members
[[nodiscard]] NTSTATUS NotifyConsoleApplication(_In_ DWORD dwProcessId) override;
[[nodiscard]] NTSTATUS SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) override;
[[nodiscard]] NTSTATUS EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) override;
[[nodiscard]] NTSTATUS SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept override;
// Public Members
[[nodiscard]] NTSTATUS Control(_In_ ConsoleControl::ControlType ConsoleCommand,
_In_reads_bytes_(ConsoleInformationLength) PVOID ConsoleInformation,
_In_ DWORD ConsoleInformationLength);
void Control(ControlType command, PVOID ptr, DWORD len) noexcept override;
void NotifyWinEvent(DWORD event, HWND hwnd, LONG idObject, LONG idChild) noexcept override;
void NotifyConsoleApplication(_In_ DWORD dwProcessId) noexcept override;
void SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground) noexcept override;
void EndTask(_In_ DWORD dwProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags) noexcept override;
void SetWindowOwner(HWND hwnd, DWORD processId, DWORD threadId) noexcept override;
BOOL EnterReaderModeHelper(_In_ HWND hwnd);
@ -62,10 +47,14 @@ namespace Microsoft::Console::Interactivity::Win32
#ifdef CON_USERPRIVAPI_INDIRECT
ConsoleControl();
~ConsoleControl();
private:
HMODULE _hUser32;
using PfnConsoleControl = NTSTATUS(WINAPI*)(ControlType, PVOID, DWORD);
using PfnEnterReaderModeHelper = BOOL(WINAPI*)(HWND);
using PfnTranslateMessageEx = BOOL(WINAPI*)(const MSG*, UINT);
PfnConsoleControl _consoleControl = nullptr;
PfnEnterReaderModeHelper _enterReaderModeHelper = nullptr;
PfnTranslateMessageEx _translateMessageEx = nullptr;
#endif
};
}

View File

@ -16,7 +16,6 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\AccessibilityNotifier.cpp" />
<ClCompile Include="..\Clipboard.cpp" />
<ClCompile Include="..\ConsoleControl.cpp" />
<ClCompile Include="..\ConsoleInputThread.cpp" />
@ -38,7 +37,6 @@
<ClCompile Include="..\windowUiaProvider.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AccessibilityNotifier.hpp" />
<ClInclude Include="..\Clipboard.hpp" />
<ClInclude Include="..\ConsoleControl.hpp" />
<ClInclude Include="..\ConsoleInputThread.hpp" />
@ -70,4 +68,4 @@
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
</Project>
</Project>

View File

@ -15,9 +15,6 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\AccessibilityNotifier.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Clipboard.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -71,9 +68,6 @@
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\AccessibilityNotifier.hpp">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Clipboard.hpp">
<Filter>Header Files</Filter>
</ClInclude>
@ -128,5 +122,6 @@
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
</Project>
</Project>

View File

@ -428,12 +428,8 @@ void Window::ChangeViewport(const til::inclusive_rect& NewWindow)
pSelection->HideSelection();
// Fire off an event to let accessibility apps know we've scrolled.
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier != nullptr)
{
pNotifier->NotifyConsoleUpdateScrollEvent(ScreenInfo.GetViewport().Left() - NewWindow.left,
ScreenInfo.GetViewport().Top() - NewWindow.top);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.ScrollViewport({ ScreenInfo.GetViewport().Left() - NewWindow.left, ScreenInfo.GetViewport().Top() - NewWindow.top });
// The new window is OK. Store it in screeninfo and refresh screen.
ScreenInfo.SetViewport(Viewport::FromInclusive(NewWindow), false);
@ -1356,20 +1352,13 @@ IRawElementProviderSimple* Window::_GetUiaProvider()
if (nullptr == _pUiaProvider)
{
LOG_IF_FAILED(WRL::MakeAndInitialize<WindowUiaProvider>(&_pUiaProvider, this));
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.SetUIAProvider(_pUiaProvider->GetScreenInfoProvider());
}
return _pUiaProvider.Get();
}
[[nodiscard]] HRESULT Window::SignalUia(_In_ EVENTID id)
{
if (_pUiaProvider != nullptr)
{
return _pUiaProvider->Signal(id);
}
return S_FALSE;
}
[[nodiscard]] HRESULT Window::UiaSetTextAreaFocus()
{
if (_pUiaProvider != nullptr)

View File

@ -82,8 +82,6 @@ namespace Microsoft::Console::Interactivity::Win32
BOOL PostUpdateWindowSize() const;
BOOL PostUpdateExtendedEditKeys() const;
[[nodiscard]] HRESULT SignalUia(_In_ EVENTID id);
void SetOwner();
BOOL GetCursorPosition(_Out_ til::point* lpPoint);
BOOL GetClientRectangle(_Out_ til::rect* lpRect);

View File

@ -31,51 +31,14 @@ try
}
CATCH_RETURN();
[[nodiscard]] HRESULT WindowUiaProvider::Signal(_In_ EVENTID id)
ScreenInfoUiaProvider* WindowUiaProvider::GetScreenInfoProvider() const noexcept
{
auto hr = S_OK;
// ScreenInfoUiaProvider is responsible for signaling selection
// changed events and text changed events
if (id == UIA_Text_TextSelectionChangedEventId ||
id == UIA_Text_TextChangedEventId)
{
if (_pScreenInfoProvider)
{
hr = _pScreenInfoProvider->Signal(id);
}
else
{
hr = E_POINTER;
}
return hr;
}
if (_signalEventFiring.find(id) != _signalEventFiring.end() &&
_signalEventFiring[id] == true)
{
return hr;
}
try
{
_signalEventFiring[id] = true;
}
CATCH_RETURN();
hr = UiaRaiseAutomationEvent(this, id);
_signalEventFiring[id] = false;
return hr;
return _pScreenInfoProvider.Get();
}
[[nodiscard]] HRESULT WindowUiaProvider::SetTextAreaFocus()
{
try
{
return _pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId);
}
CATCH_RETURN();
return UiaRaiseAutomationEvent(_pScreenInfoProvider.Get(), UIA_AutomationFocusChangedEventId);
}
#pragma region IRawElementProviderSimple
@ -188,9 +151,6 @@ IFACEMETHODIMP WindowUiaProvider::Navigate(_In_ NavigateDirection direction, _CO
if (direction == NavigateDirection_FirstChild || direction == NavigateDirection_LastChild)
{
RETURN_IF_FAILED(_pScreenInfoProvider.CopyTo(ppProvider));
// signal that the focus changed
LOG_IF_FAILED(_pScreenInfoProvider->Signal(UIA_AutomationFocusChangedEventId));
}
// For the other directions (parent, next, previous) the default of nullptr is correct
@ -240,8 +200,7 @@ IFACEMETHODIMP WindowUiaProvider::GetEmbeddedFragmentRoots(_Outptr_result_mayben
IFACEMETHODIMP WindowUiaProvider::SetFocus()
{
RETURN_IF_FAILED(_EnsureValidHwnd());
return Signal(UIA_AutomationFocusChangedEventId);
return S_OK;
}
IFACEMETHODIMP WindowUiaProvider::get_FragmentRoot(_COM_Outptr_result_maybenull_ IRawElementProviderFragmentRoot** ppProvider)

View File

@ -47,7 +47,7 @@ namespace Microsoft::Console::Interactivity::Win32
WindowUiaProvider& operator=(WindowUiaProvider&&) = delete;
public:
[[nodiscard]] virtual HRESULT Signal(_In_ EVENTID id);
ScreenInfoUiaProvider* GetScreenInfoProvider() const noexcept;
[[nodiscard]] virtual HRESULT SetTextAreaFocus();
// IRawElementProviderSimple methods
@ -78,19 +78,6 @@ namespace Microsoft::Console::Interactivity::Win32
void ChangeViewport(const til::inclusive_rect& NewWindow);
protected:
// this is used to prevent the object from
// signaling an event while it is already in the
// process of signalling another event.
// This fixes a problem with JAWS where it would
// call a public method that calls
// UiaRaiseAutomationEvent to signal something
// happened, which JAWS then detects the signal
// and calls the same method in response,
// eventually overflowing the stack.
// We aren't using this as a cheap locking
// mechanism for multi-threaded code.
std::unordered_map<EVENTID, bool> _signalEventFiring;
[[nodiscard]] HRESULT _EnsureValidHwnd() const;
const OLECHAR* AutomationIdPropertyName = L"Console Window";

View File

@ -89,7 +89,7 @@ VOID SetConsoleWindowOwner(const HWND hwnd, _Inout_opt_ ConsoleProcessHandle* pP
}
// Comment out this line to enable UIA tree to be visible until UIAutomationCore.dll can support our scenario.
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetWindowOwner(hwnd, dwProcessId, dwThreadId));
ServiceLocator::LocateConsoleControl()->SetWindowOwner(hwnd, dwProcessId, dwThreadId);
}
// ----------------------------
@ -892,11 +892,14 @@ NTSTATUS InitWindowsSubsystem(_Out_ HHOOK* phhook)
// was special cased (for CSRSS) to always succeed. Thus, we ignore failure for app compat (as not having the hook isn't fatal).
*phhook = SetWindowsHookExW(WH_MSGFILTER, DialogHookProc, nullptr, GetCurrentThreadId());
SetConsoleWindowOwner(ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), ProcessData);
const auto hwnd = ServiceLocator::LocateConsoleWindow()->GetWindowHandle();
SetConsoleWindowOwner(hwnd, ProcessData);
LOG_IF_FAILED(ServiceLocator::LocateConsoleWindow<Window>()->ActivateAndShow(gci.GetShowWindow()));
NotifyWinEvent(EVENT_CONSOLE_START_APPLICATION, ServiceLocator::LocateConsoleWindow()->GetWindowHandle(), ProcessData->dwProcessId, 0);
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.Initialize(hwnd, gci.GetMSAADelay(), gci.GetUIADelay());
an.ApplicationStart(ProcessData->dwProcessId);
return STATUS_SUCCESS;
}

View File

@ -782,18 +782,6 @@ static constexpr TsfDataProvider s_tsfDataProvider;
break;
}
case EVENT_CONSOLE_CARET:
case EVENT_CONSOLE_UPDATE_REGION:
case EVENT_CONSOLE_UPDATE_SIMPLE:
case EVENT_CONSOLE_UPDATE_SCROLL:
case EVENT_CONSOLE_LAYOUT:
case EVENT_CONSOLE_START_APPLICATION:
case EVENT_CONSOLE_END_APPLICATION:
{
NotifyWinEvent(Message, hWnd, (LONG)wParam, (LONG)lParam);
break;
}
default:
CallDefWin:
{

View File

@ -64,6 +64,8 @@ const RegistrySerialization::_RegPropertyMap RegistrySerialization::s_PropertyMa
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_USEDX, SET_FIELD_AND_SIZE(_fUseDx) },
{ _RegPropertyType::Boolean, CONSOLE_REGISTRY_COPYCOLOR, SET_FIELD_AND_SIZE(_fCopyColor) },
{ _RegPropertyType::Dword, L"TextMeasurement", SET_FIELD_AND_SIZE(_textMeasurement) },
{ _RegPropertyType::Dword, L"MSAADelay", SET_FIELD_AND_SIZE(_msaaDelay) },
{ _RegPropertyType::Dword, L"UIADelay", SET_FIELD_AND_SIZE(_uiaDelay) },
#if TIL_FEATURE_CONHOSTATLASENGINE_ENABLED
{ _RegPropertyType::Boolean, L"EnableBuiltinGlyphs", SET_FIELD_AND_SIZE(_fEnableBuiltinGlyphs) },
#endif

View File

@ -456,14 +456,11 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API
// ConsoleApp will be false in the AttachConsole case.
if (Cac.ConsoleApp)
{
LOG_IF_FAILED(ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(dwProcessId));
ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(dwProcessId);
}
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier)
{
pNotifier->NotifyConsoleStartApplicationEvent(dwProcessId);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.ApplicationStart(dwProcessId);
if (WI_IsFlagClear(gci.Flags, CONSOLE_INITIALIZED))
{
@ -556,11 +553,8 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleClientDisconnectRoutine(_In_ PCONSOLE_API
{
const auto pProcessData = pMessage->GetProcessHandle();
auto pNotifier = ServiceLocator::LocateAccessibilityNotifier();
if (pNotifier)
{
pNotifier->NotifyConsoleEndApplicationEvent(pProcessData->dwProcessId);
}
auto& an = ServiceLocator::LocateGlobals().accessibilityNotifier;
an.ApplicationEnd(pProcessData->dwProcessId);
Tracing::s_TraceConsoleAttachDetach(pProcessData, false);

View File

@ -298,5 +298,5 @@ bool ConsoleProcessList::IsEmpty() const
// - <none>
void ConsoleProcessList::_ModifyProcessForegroundRights(const HANDLE hProcess, const bool fForeground) const
{
LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(hProcess, fForeground));
ServiceLocator::LocateConsoleControl()->SetForeground(hProcess, fForeground);
}

View File

@ -84,7 +84,6 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool ResizeWindow(const til::CoordType width, const til::CoordType height) = 0;
virtual void NotifyAccessibilityChange(const til::rect& changedRect) = 0;
virtual void NotifyBufferRotation(const int delta) = 0;
virtual void NotifyShellIntegrationMark() = 0;

View File

@ -160,12 +160,6 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
}
const auto textPositionAfter = state.text.data();
if (state.columnBeginDirty != state.columnEndDirty)
{
const til::rect changedRect{ state.columnBeginDirty, cursorPosition.y, state.columnEndDirty, cursorPosition.y + 1 };
_api.NotifyAccessibilityChange(changedRect);
}
// If we're past the end of the line, we need to clamp the cursor
// back into range, and if wrapping is enabled, set the delayed wrap
// flag. The wrapping only occurs once another character is output.
@ -413,8 +407,7 @@ void AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
// - Helper method which applies a bunch of flags that are typically set whenever
// the cursor is moved. The IsOn flag is set to true, and the Delay flag to false,
// to force a blinking cursor to be visible, so the user can immediately see the
// new position. The HasMoved flag is set to let the accessibility notifier know
// that there was movement that needs to be reported.
// new position.
// Arguments:
// - cursor - The cursor instance to be updated
// Return Value:
@ -423,7 +416,6 @@ void AdaptDispatch::_ApplyCursorMovementFlags(Cursor& cursor) noexcept
{
cursor.SetDelay(false);
cursor.SetIsOn(true);
cursor.SetHasMoved(true);
}
// Routine Description:
@ -726,7 +718,6 @@ void AdaptDispatch::DeleteCharacter(const VTInt count)
void AdaptDispatch::_FillRect(const Page& page, const til::rect& fillRect, const std::wstring_view& fillChar, const TextAttribute& fillAttrs) const
{
page.Buffer().FillRect(fillRect, fillChar, fillAttrs);
_api.NotifyAccessibilityChange(fillRect);
}
// Routine Description:
@ -870,7 +861,6 @@ void AdaptDispatch::_SelectiveEraseRect(const Page& page, const til::rect& erase
}
}
}
_api.NotifyAccessibilityChange(eraseRect);
}
}
@ -980,7 +970,6 @@ void AdaptDispatch::_ChangeRectAttributes(const Page& page, const til::rect& cha
}
}
page.Buffer().TriggerRedraw(Viewport::FromExclusive(changeRect));
_api.NotifyAccessibilityChange(changeRect);
}
}
@ -1212,7 +1201,6 @@ void AdaptDispatch::CopyRectangularArea(const VTInt top, const VTInt left, const
} while (dstView.WalkInBounds(dstPos, walkDirection));
// Copy any image content in the affected area.
ImageSlice::CopyBlock(src.Buffer(), srcView.ToExclusive(), dst.Buffer(), dstView.ToExclusive());
_api.NotifyAccessibilityChange(dstRect);
}
}
@ -3142,7 +3130,6 @@ void AdaptDispatch::_EraseScrollback()
_api.SetViewportPosition({ page.XPanOffset(), 0 });
// Move the cursor to the same relative location.
cursor.SetYPosition(row - page.Top());
cursor.SetHasMoved(true);
}
//Routine Description:
@ -3194,7 +3181,6 @@ void AdaptDispatch::_EraseAll()
}
// Restore the relative cursor position
cursor.SetYPosition(row + newPageTop);
cursor.SetHasMoved(true);
// Erase all the rows in the current page.
const auto eraseAttributes = _GetEraseAttributes(page);

View File

@ -202,11 +202,6 @@ public:
Log::Comment(L"PlayMidiNote MOCK called...");
}
void NotifyAccessibilityChange(const til::rect& /*changedRect*/) override
{
Log::Comment(L"NotifyAccessibilityChange MOCK called...");
}
void NotifyBufferRotation(const int /*delta*/) override
{
Log::Comment(L"NotifyBufferRotation MOCK called...");

View File

@ -43,29 +43,6 @@ try
}
CATCH_RETURN();
[[nodiscard]] HRESULT ScreenInfoUiaProviderBase::Signal(_In_ EVENTID eventId)
{
auto hr = S_OK;
// check to see if we're already firing this particular event
if (_signalFiringMapping.find(eventId) != _signalFiringMapping.end() &&
_signalFiringMapping[eventId] == true)
{
return hr;
}
try
{
_signalFiringMapping[eventId] = true;
}
CATCH_RETURN();
IRawElementProviderSimple* pProvider = this;
hr = UiaRaiseAutomationEvent(pProvider, eventId);
_signalFiringMapping[eventId] = false;
return hr;
}
#pragma region IRawElementProviderSimple
// Implementation of IRawElementProviderSimple::get_ProviderOptions.
@ -211,7 +188,7 @@ IFACEMETHODIMP ScreenInfoUiaProviderBase::GetEmbeddedFragmentRoots(_Outptr_resul
IFACEMETHODIMP ScreenInfoUiaProviderBase::SetFocus()
{
UiaTracing::TextProvider::SetFocus(*this);
return Signal(UIA_AutomationFocusChangedEventId);
return S_OK;
}
#pragma endregion

View File

@ -47,7 +47,6 @@ namespace Microsoft::Console::Types
ScreenInfoUiaProviderBase& operator=(ScreenInfoUiaProviderBase&&) = delete;
~ScreenInfoUiaProviderBase() = default;
[[nodiscard]] HRESULT Signal(_In_ EVENTID id);
virtual void ChangeViewport(const til::inclusive_rect& NewWindow) = 0;
// IRawElementProviderSimple methods
@ -109,19 +108,6 @@ namespace Microsoft::Console::Types
std::wstring _wordDelimiters{};
private:
// this is used to prevent the object from
// signaling an event while it is already in the
// process of signalling another event.
// This fixes a problem with JAWS where it would
// call a public method that calls
// UiaRaiseAutomationEvent to signal something
// happened, which JAWS then detects the signal
// and calls the same method in response,
// eventually overflowing the stack.
// We aren't using this as a cheap locking
// mechanism for multi-threaded code.
std::unordered_map<EVENTID, bool> _signalFiringMapping{};
til::size _getScreenBufferCoords() const noexcept;
const TextBuffer& _getTextBuffer() const noexcept;
Viewport _getViewport() const noexcept;