Repository: microsoft/terminal (language: cpp)

Issue:
[Windows Powershell/Command Prompt/Azure CloudShell]: The Name property of a focusable element present on the homepage,must not be null.
### Windows Terminal version

1.17.3052.0

### Windows build number

Windows 11 Version 22H2 (OS Build 25206.1000)

### Other Software

_No response_

### Steps to reproduce

1. Open Windows Terminal.
2. Run Accessibility insights for Window on the homepage.
3. Observe the issue.


****User Experience**:** -
Screen Reader user are impacted here as name is announced for control as result, they will not be able to know the purpose of control.

**Guideline Reference**:
https://www.w3.org/WAI/WCAG21/Understanding/name-role-value

Attachments:
[The Name property of a focusable element must not be null.zip](https://github.com/microsoft/terminal/files/10002487/The.Name.property.of.a.focusable.element.must.not.be.null.zip)


### Expected Behavior

The Name property of a focusable element must not be null.

### Actual Behavior

The Name property of the control defined in the side of the homepage should not be null and the control should be made hidden.


Discussion / hints:

Fix patch:
diff --git a/src/interactivity/base/InteractivityFactory.cpp b/src/interactivity/base/InteractivityFactory.cpp
index 3fd2b636856..31fbb4b9f53 100644
--- a/src/interactivity/base/InteractivityFactory.cpp
+++ b/src/interactivity/base/InteractivityFactory.cpp
@@ -352,7 +352,7 @@ using namespace Microsoft::Console::Interactivity;
                     const auto gle = GetLastError();
                     status = NTSTATUS_FROM_WIN32(gle);
                 }
-
+                _pseudoConsoleWindowHwnd = hwnd;
                 break;
             }
 #ifdef BUILD_ONECORE_INTERACTIVITY
@@ -464,6 +464,18 @@ using namespace Microsoft::Console::Interactivity;
             _WritePseudoWindowCallback((bool)wParam);
         }
     }
+    case WM_GETOBJECT:
+    {
+        if (static_cast<long>(lParam) == static_cast<long>(UiaRootObjectId))
+        {
+            if (nullptr == _pPseudoConsoleUiaProvider)
+            {
+                LOG_IF_FAILED(WRL::MakeAndInitialize<PseudoConsoleWindowAccessibilityProvider>(&_pPseudoConsoleUiaProvider, _pseudoConsoleWindowHwnd));
+            }
+            return UiaReturnRawElementProvider(hWnd, wParam, lParam, _pPseudoConsoleUiaProvider.Get());
+        }
+        return 0;
+    }
     }
     // If we get this far, call the default window proc
     return DefWindowProcW(hWnd, Message, wParam, lParam);
diff --git a/src/interactivity/base/InteractivityFactory.hpp b/src/interactivity/base/InteractivityFactory.hpp
index 5497841749b..2a0b3460eef 100644
--- a/src/interactivity/base/InteractivityFactory.hpp
+++ b/src/interactivity/base/InteractivityFactory.hpp
@@ -8,6 +8,7 @@
 #include "ApiDetector.hpp"
 
 #include "../inc/IInteractivityFactory.hpp"
+#include "PseudoConsoleWindowAccessibilityProvider.hpp"
 
 #include <map>
 
@@ -40,5 +41,8 @@ namespace Microsoft::Console::Interactivity
 
     private:
         void _WritePseudoWindowCallback(bool showOrHide);
+
+        HWND _pseudoConsoleWindowHwnd{ nullptr };
+        WRL::ComPtr<PseudoConsoleWindowAccessibilityProvider> _pPseudoConsoleUiaProvider{ nullptr };
     };
 }
diff --git a/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.cpp b/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.cpp
new file mode 100644
index 00000000000..09d7507639b
--- /dev/null
+++ b/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+
+#include "PseudoConsoleWindowAccessibilityProvider.hpp"
+
+using namespace Microsoft::Console::Interactivity;
+
+HRESULT PseudoConsoleWindowAccessibilityProvider::RuntimeClassInitialize(HWND pseudoConsoleHwnd) noexcept
+{
+    RETURN_HR_IF_NULL(E_INVALIDARG, pseudoConsoleHwnd);
+    _pseudoConsoleHwnd = pseudoConsoleHwnd;
+    return S_OK;
+}
+
+IFACEMETHODIMP PseudoConsoleWindowAccessibilityProvider::get_ProviderOptions(_Out_ ProviderOptions* pOptions)
+{
+    RETURN_HR_IF_NULL(E_INVALIDARG, pOptions);
+    *pOptions = ProviderOptions_ServerSideProvider;
+    return S_OK;
+}
+
+IFACEMETHODIMP PseudoConsoleWindowAccessibilityProvider::GetPatternProvider(_In_ PATTERNID /*iid*/,
+                                                                            _COM_Outptr_result_maybenull_ IUnknown** ppInterface)
+{
+    RETURN_HR_IF_NULL(E_INVALIDARG, ppInterface);
+    *ppInterface = nullptr;
+    return S_OK;
+}
+
+IFACEMETHODIMP PseudoConsoleWindowAccessibilityProvider::GetPropertyValue(_In_ PROPERTYID propertyId,
+                                                                          _Out_ VARIANT* pVariant)
+{
+    RETURN_HR_IF_NULL(E_INVALIDARG, pVariant);
+
+    pVariant->vt = VT_EMPTY;
+
+    // Returning the default will leave the property as the default
+    // so we only really need to touch it for the properties we want to implement
+    switch (propertyId)
+    {
+    case UIA_ControlTypePropertyId:
+    {
+        pVariant->vt = VT_I4;
+        pVariant->lVal = UIA_WindowControlTypeId;
+        break;
+    }
+    case UIA_NamePropertyId:
+    {
+        static constexpr auto AutomationPropertyName = L"Internal Console Management Window";
+        pVariant->bstrVal = SysAllocString(AutomationPropertyName);
+        if (pVariant->bstrVal != nullptr)
+        {
+            pVariant->vt = VT_BSTR;
+        }
+        break;
+    }
+    case UIA_IsControlElementPropertyId:
+    case UIA_IsContentElementPropertyId:
+    case UIA_IsKeyboardFocusablePropertyId:
+    case UIA_HasKeyboardFocusPropertyId:
+    {
+        pVariant->vt = VT_BOOL;
+        pVariant->boolVal = VARIANT_FALSE;
+        break;
+    }
+    default:
+        break;
+    }
+    return S_OK;
+}
+
+IFACEMETHODIMP PseudoConsoleWindowAccessibilityProvider::get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider)
+{
+    RETURN_HR_IF_NULL(E_INVALIDARG, ppProvider);
+    RETURN_HR_IF_NULL(gsl::narrow_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE), _pseudoConsoleHwnd);
+    return UiaHostProviderFromHwnd(_pseudoConsoleHwnd, ppProvider);
+}
diff --git a/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.hpp b/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.hpp
new file mode 100644
index 00000000000..ab30eeeec44
--- /dev/null
+++ b/src/interactivity/base/PseudoConsoleWindowAccessibilityProvider.hpp
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#pragma once
+
+#include "precomp.h"
+
+namespace Microsoft::Console::Interactivity
+{
+    class PseudoConsoleWindowAccessibilityProvider final :
+        public WRL::RuntimeClass<WRL::RuntimeClassFlags<WRL::ClassicCom | WRL::InhibitFtmBase>, IRawElementProviderSimple>
+    {
+    public:
+        PseudoConsoleWindowAccessibilityProvider() = default;
+        ~PseudoConsoleWindowAccessibilityProvider() = default;
+        HRESULT RuntimeClassInitialize(HWND pseudoConsoleHwnd) noexcept;
+
+        PseudoConsoleWindowAccessibilityProvider(const PseudoConsoleWindowAccessibilityProvider&) = delete;
+        PseudoConsoleWindowAccessibilityProvider(PseudoConsoleWindowAccessibilityProvider&&) = delete;
+        PseudoConsoleWindowAccessibilityProvider& operator=(const PseudoConsoleWindowAccessibilityProvider&) = delete;
+        PseudoConsoleWindowAccessibilityProvider& operator=(PseudoConsoleWindowAccessibilityProvider&&) = delete;
+
+        // IRawElementProviderSimple methods
+        IFACEMETHODIMP get_ProviderOptions(_Out_ ProviderOptions* pOptions) override;
+        IFACEMETHODIMP GetPatternProvider(_In_ PATTERNID iid,
+                                          _COM_Outptr_result_maybenull_ IUnknown** ppInterface) override;
+        IFACEMETHODIMP GetPropertyValue(_In_ PROPERTYID idProp,
+                                        _Out_ VARIANT* pVariant) override;
+        IFACEMETHODIMP get_HostRawElementProvider(_COM_Outptr_result_maybenull_ IRawElementProviderSimple** ppProvider) override;
+
+    private:
+        HWND _pseudoConsoleHwnd;
+    };
+}
diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj b/src/interactivity/base/lib/InteractivityBase.vcxproj
index 0b9a03e97a3..7517d99f4f6 100644
--- a/src/interactivity/base/lib/InteractivityBase.vcxproj
+++ b/src/interactivity/base/lib/InteractivityBase.vcxproj
@@ -28,6 +28,7 @@
     <ClCompile Include="..\precomp.cpp">
       <PrecompiledHeader>Create</PrecompiledHeader>
     </ClCompile>
+    <ClCompile Include="..\PseudoConsoleWindowAccessibilityProvider.cpp" />
     <ClCompile Include="..\RemoteConsoleControl.cpp" />
     <ClCompile Include="..\ServiceLocator.cpp" />
     <ClCompile Include="..\VtApiRedirection.cpp" />
@@ -49,9 +50,10 @@
     <ClInclude Include="..\InteractivityFactory.hpp" />
     <ClInclude Include="..\precomp.h" />
     <ClInclude Include="..\..\inc\ServiceLocator.hpp" />
+    <ClInclude Include="..\PseudoConsoleWindowAccessibilityProvider.hpp" />
     <ClInclude Include="..\RemoteConsoleControl.hpp" />
   </ItemGroup>
   <!-- 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>
\ No newline at end of file
diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
index d761db55b44..3cad47ac50c 100644
--- a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
+++ b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
@@ -36,6 +36,12 @@
     <ClCompile Include="..\HostSignalInputThread.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\VtApiRedirection.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\PseudoConsoleWindowAccessibilityProvider.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\InteractivityFactory.hpp">
@@ -89,6 +95,9 @@
     <ClInclude Include="..\HostSignalInputThread.hpp">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\PseudoConsoleWindowAccessibilityProvider.hpp">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
diff --git a/src/interactivity/base/sources.inc b/src/interactivity/base/sources.inc
index 5d77a022b0b..0e77324d005 100644
--- a/src/interactivity/base/sources.inc
+++ b/src/interactivity/base/sources.inc
@@ -42,10 +42,11 @@ SOURCES = \
     ..\VtApiRedirection.cpp \
     ..\EventSynthesis.cpp \
     ..\RemoteConsoleControl.cpp \
-    ..\HostSignalInputThread.cpp \
+    ..\HostSignalInputThread.cpp \
+    ..\PseudoConsoleWindowAccessibilityProvider.cpp \
 
 INCLUDES = \
     $(INCLUDES); \
     ..; \
     ..\..\..\..\..\ConIoSrv; \
-    
+    


Test patch: