Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/actions/spelling/expect/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,8 @@ msft
MSGCMDLINEF
MSGF
MSGFILTER
MSGFINDNORESULT
MSGFINDRESULT
MSGFLG
MSGMARKMODE
MSGSCROLLMODE
Expand Down
5 changes: 5 additions & 0 deletions src/host/res.rc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ BEGIN

ID_CONSOLE_FMT_WINDOWTITLE, "%s%s"

/* Find dialog screen reader announcements. %1 is the 1-based index of the current
match, %2 is the total number of matches. */
ID_CONSOLE_MSGFINDRESULT, "%1!d! of %2!d!"
ID_CONSOLE_MSGFINDNORESULT, "No results"

/* Menu items that replace the standard ones. These don't have the accelerators */
SC_CLOSE, "&Close"

Expand Down
2 changes: 2 additions & 0 deletions src/host/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Author(s):
#define ID_CONSOLE_MSGMARKMODE 0x100C
#define ID_CONSOLE_MSGSCROLLMODE 0x100D
#define ID_CONSOLE_FMT_WINDOWTITLE 0x100E
#define ID_CONSOLE_MSGFINDRESULT 0x100F
#define ID_CONSOLE_MSGFINDNORESULT 0x1010

// Menu Item strings
#define ID_CONSOLE_COPY 0xFFF0
Expand Down
2 changes: 2 additions & 0 deletions src/interactivity/inc/IAccessibilityNotifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ namespace Microsoft::Console::Interactivity
virtual void NotifyConsoleLayoutEvent() = 0;
virtual void NotifyConsoleStartApplicationEvent(_In_ DWORD processId) = 0;
virtual void NotifyConsoleEndApplicationEvent(_In_ DWORD processId) = 0;

virtual void AnnounceSearchResults(_In_ ptrdiff_t index, _In_ size_t count) = 0;
};
}
4 changes: 4 additions & 0 deletions src/interactivity/onecore/AccessibilityNotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ void AccessibilityNotifier::NotifyConsoleStartApplicationEvent(_In_ DWORD /*proc
void AccessibilityNotifier::NotifyConsoleEndApplicationEvent(_In_ DWORD /*processId*/) noexcept
{
}

void AccessibilityNotifier::AnnounceSearchResults(_In_ ptrdiff_t /*index*/, _In_ size_t /*count*/) noexcept
{
}
2 changes: 2 additions & 0 deletions src/interactivity/onecore/AccessibilityNotifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@ namespace Microsoft::Console::Interactivity::OneCore
void NotifyConsoleLayoutEvent() noexcept override;
void NotifyConsoleStartApplicationEvent(_In_ DWORD processId) noexcept override;
void NotifyConsoleEndApplicationEvent(_In_ DWORD processId) noexcept override;

void AnnounceSearchResults(_In_ ptrdiff_t index, _In_ size_t count) noexcept override;
};
}
69 changes: 69 additions & 0 deletions src/interactivity/win32/AccessibilityNotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "../inc/ServiceLocator.hpp"
#include "ConsoleControl.hpp"
#include "resource.h"
#include "window.hpp"

using namespace Microsoft::Console::Types;
using namespace Microsoft::Console::Interactivity::Win32;
Expand Down Expand Up @@ -133,3 +135,70 @@ void AccessibilityNotifier::NotifyConsoleEndApplicationEvent(_In_ DWORD processI
0);
}
}

// Routine Description:
// - Loads a string resource and returns it. Returns an empty string on failure
static std::wstring _loadString(const UINT id)
{
WCHAR buffer[70];
const auto length = LoadStringW(Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().hInstance, id, buffer, ARRAYSIZE(buffer));
return { &buffer[0], gsl::narrow_cast<size_t>(std::max(length, 0)) };
}

// Routine Description:
// - Announces the state of the Find dialog's search results to screen readers
// Arguments:
// - index: the 0-based index of the current match
// - count: the total number of matches
void AccessibilityNotifier::AnnounceSearchResults(_In_ const ptrdiff_t index, _In_ const size_t count)
try
{
const auto pWindow = ServiceLocator::LocateConsoleWindow<Window>();
if (!pWindow)
{
return;
}

std::wstring announcement;

if (count == 0)
{
// No results found
announcement = _loadString(ID_CONSOLE_MSGFINDNORESULT);
}
else
{
// Results found. Announce as the 1-based index of the total ("2 of 5")
const auto format = _loadString(ID_CONSOLE_MSGFINDRESULT);
const auto position = std::clamp<size_t>(gsl::narrow_cast<size_t>(std::max<ptrdiff_t>(index, 0)) + 1, 1, count);

// The resource uses positional inserts (%1, %2) so that translations can reorder them.
const DWORD_PTR args[]{
gsl::narrow_cast<DWORD_PTR>(position),
gsl::narrow_cast<DWORD_PTR>(count),
};
wil::unique_hlocal_string formatted;
const auto length = FormatMessageW(
FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_ARGUMENT_ARRAY,
format.c_str(),
0,
0,
reinterpret_cast<LPWSTR>(formatted.addressof()),
0,
reinterpret_cast<va_list*>(const_cast<DWORD_PTR*>(&args[0])));
if (length == 0)
{
LOG_LAST_ERROR();
return;
}
announcement.assign(formatted.get(), length);
}

if (announcement.empty())
{
return;
}

LOG_IF_FAILED(pWindow->SignalUiaAnnouncement(announcement));
}
CATCH_LOG()
2 changes: 2 additions & 0 deletions src/interactivity/win32/AccessibilityNotifier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ namespace Microsoft::Console::Interactivity::Win32
void NotifyConsoleLayoutEvent();
void NotifyConsoleStartApplicationEvent(_In_ DWORD processId);
void NotifyConsoleEndApplicationEvent(_In_ DWORD processId);

void AnnounceSearchResults(_In_ ptrdiff_t index, _In_ size_t count);
};
}
5 changes: 5 additions & 0 deletions src/interactivity/win32/find.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l
searcher.FindNext(reverse);
}

if (const auto notifier = ServiceLocator::LocateAccessibilityNotifier())
{
notifier->AnnounceSearchResults(searcher.CurrentMatch(), searcher.Results().size());
}

if (searcher.SelectCurrent())
{
return TRUE;
Expand Down
2 changes: 2 additions & 0 deletions src/interactivity/win32/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Author(s):
#define ID_CONSOLE_MSGMARKMODE 0x100C
#define ID_CONSOLE_MSGSCROLLMODE 0x100D
#define ID_CONSOLE_FMT_WINDOWTITLE 0x100E
#define ID_CONSOLE_MSGFINDRESULT 0x100F
#define ID_CONSOLE_MSGFINDNORESULT 0x1010

// Menu Item strings
#define ID_CONSOLE_COPY 0xFFF0
Expand Down
9 changes: 9 additions & 0 deletions src/interactivity/win32/window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1370,6 +1370,15 @@ IRawElementProviderSimple* Window::_GetUiaProvider()
return S_FALSE;
}

[[nodiscard]] HRESULT Window::SignalUiaAnnouncement(const std::wstring_view text)
{
if (_pUiaProvider != nullptr)
{
return _pUiaProvider->SignalAnnouncement(text);
}
return S_FALSE;
}

[[nodiscard]] HRESULT Window::UiaSetTextAreaFocus()
{
if (_pUiaProvider != nullptr)
Expand Down
1 change: 1 addition & 0 deletions src/interactivity/win32/window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ namespace Microsoft::Console::Interactivity::Win32
BOOL PostUpdateExtendedEditKeys() const;

[[nodiscard]] HRESULT SignalUia(_In_ EVENTID id);
[[nodiscard]] HRESULT SignalUiaAnnouncement(const std::wstring_view text);

void SetOwner();
BOOL GetCursorPosition(_Out_ til::point* lpPoint);
Expand Down
27 changes: 27 additions & 0 deletions src/interactivity/win32/windowUiaProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,33 @@ CATCH_RETURN();
CATCH_RETURN();
}

// Routine Description:
// - Raises a UIA notification event so that screen readers speak the given text
// Arguments:
// - text: the text to be announced
// Return Value:
// - S_OK, or a suitable HRESULT on failure
[[nodiscard]] HRESULT WindowUiaProvider::SignalAnnouncement(const std::wstring_view text) noexcept
try
{
// Raise the announcement on the text area because
// that's the element screen readers are actually reading from
RETURN_HR_IF_NULL(E_POINTER, _pScreenInfoProvider.Get());

const wil::unique_bstr announcement{ SysAllocStringLen(text.data(), gsl::narrow<UINT>(text.size())) };
RETURN_IF_NULL_ALLOC(announcement);

static const auto activityId = wil::make_bstr_nothrow(L"ConhostSearchResultAnnouncement");
RETURN_IF_NULL_ALLOC(activityId);

return UiaRaiseNotificationEvent(_pScreenInfoProvider.Get(),
NotificationKind_ActionCompleted,
NotificationProcessing_ImportantMostRecent,
announcement.get(),
activityId.get());
}
CATCH_RETURN();

#pragma region IRawElementProviderSimple

// Implementation of IRawElementProviderSimple::get_ProviderOptions.
Expand Down
1 change: 1 addition & 0 deletions src/interactivity/win32/windowUiaProvider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ namespace Microsoft::Console::Interactivity::Win32
public:
[[nodiscard]] virtual HRESULT Signal(_In_ EVENTID id);
[[nodiscard]] virtual HRESULT SetTextAreaFocus();
[[nodiscard]] HRESULT SignalAnnouncement(const std::wstring_view text) noexcept;

// IRawElementProviderSimple methods
IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions) override;
Expand Down
Loading