Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Comment thread
carlos-zamora marked this conversation as resolved.
Outdated
#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