winaccessibility/source/UAccCOM/AccRelation.cxx | 27 ++++++++ winaccessibility/source/UAccCOM/AccRelation.h | 1 winaccessibility/source/UAccCOM/MAccessible.cxx | 80 ++++++++++++++++++++++++ winaccessibility/source/UAccCOM/MAccessible.h | 6 + winaccessibility/source/UAccCOMIDL/UAccCOM.idl | 2 5 files changed, 115 insertions(+), 1 deletion(-)
New commits: commit 2318b823732df3820557943e56dd5243381dc558 Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Thu Aug 22 13:39:34 2024 +0100 Commit: Michael Weghorn <m.wegh...@posteo.de> CommitDate: Fri Aug 23 07:40:17 2024 +0200 tdf#91739 wina11y: Implement IAccessible2_2::get_relationTargetsOfType Implement the `IAccessible2_2::get_relationTargetsOfType` method [1] in the Windows accessibility bridge. As described in NVDA issue comment [2], this method is the preferred way that NVDA uses to get e.g. the target object of the "flowsTo" relation. With this commit in place, retrieving the next Writer paragraph in the NVDA Python console using that relation now also works with the fallback code path in NVDA [3] dropped in a local build of NVDA: >>> focus.flowsTo <NVDAObjects.Dynamic_SymphonyParagraphSymphonyTextEditableTextWithAutoSelectDetectionIAccessible object at 0x0B8F5890> [1] https://accessibility.linuxfoundation.org/a11yspecs/ia2/docs/html/interface_i_accessible2__2.html#a63f214b322c663caf01d4bb67277f5af [2] https://github.com/nvaccess/nvda/issues/4115#issuecomment-2299084993 [3] https://github.com/nvaccess/nvda/blob/df6bd0770db12ff73905251054d12f01911030dd/source/NVDAObjects/IAccessible/__init__.py#L1861-L1874 Change-Id: Ifcd15527b4a3f5302168942f19248a0ba9a4d306 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172258 Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> diff --git a/winaccessibility/source/UAccCOM/AccRelation.cxx b/winaccessibility/source/UAccCOM/AccRelation.cxx index 50de3dd63a57..20bfa20ffa97 100644 --- a/winaccessibility/source/UAccCOM/AccRelation.cxx +++ b/winaccessibility/source/UAccCOM/AccRelation.cxx @@ -224,4 +224,31 @@ const wchar_t* CAccRelation::mapToIA2RelationType(sal_Int16 nUnoRelationType) } } +sal_Int16 CAccRelation::mapToUnoRelationType(const BSTR aIA2RelationType) +{ + if (wcscmp(aIA2RelationType, IA2_RELATION_FLOWS_FROM) == 0) + return AccessibleRelationType::CONTENT_FLOWS_FROM; + if (wcscmp(aIA2RelationType, IA2_RELATION_FLOWS_TO) == 0) + return AccessibleRelationType::CONTENT_FLOWS_TO; + if (wcscmp(aIA2RelationType, IA2_RELATION_CONTROLLED_BY) == 0) + return AccessibleRelationType::CONTROLLED_BY; + if (wcscmp(aIA2RelationType, IA2_RELATION_CONTROLLER_FOR) == 0) + return AccessibleRelationType::CONTROLLER_FOR; + if (wcscmp(aIA2RelationType, IA2_RELATION_LABEL_FOR) == 0) + return AccessibleRelationType::LABEL_FOR; + if (wcscmp(aIA2RelationType, IA2_RELATION_LABELED_BY) == 0) + return AccessibleRelationType::LABELED_BY; + if (wcscmp(aIA2RelationType, IA2_RELATION_MEMBER_OF) == 0) + return AccessibleRelationType::MEMBER_OF; + if (wcscmp(aIA2RelationType, IA2_RELATION_SUBWINDOW_OF) == 0) + return AccessibleRelationType::SUB_WINDOW_OF; + if (wcscmp(aIA2RelationType, IA2_RELATION_NODE_CHILD_OF) == 0) + return AccessibleRelationType::NODE_CHILD_OF; + if (wcscmp(aIA2RelationType, IA2_RELATION_DESCRIBED_BY) == 0) + return AccessibleRelationType::DESCRIBED_BY; + + // not all IAccessible2 relation types have a UNO equivalent + return AccessibleRelationType::INVALID; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/winaccessibility/source/UAccCOM/AccRelation.h b/winaccessibility/source/UAccCOM/AccRelation.h index a576e23aa952..de709a9a434f 100644 --- a/winaccessibility/source/UAccCOM/AccRelation.h +++ b/winaccessibility/source/UAccCOM/AccRelation.h @@ -75,6 +75,7 @@ public: STDMETHOD(put_XSubInterface)(hyper pXSubInterface) override; static const wchar_t* mapToIA2RelationType(sal_Int16 nUnoRelationType); + static sal_Int16 mapToUnoRelationType(const BSTR aIA2RelationType); private: css::accessibility::AccessibleRelation relation; diff --git a/winaccessibility/source/UAccCOM/MAccessible.cxx b/winaccessibility/source/UAccCOM/MAccessible.cxx index 549fee11a92f..44ba7016c6b2 100644 --- a/winaccessibility/source/UAccCOM/MAccessible.cxx +++ b/winaccessibility/source/UAccCOM/MAccessible.cxx @@ -2687,9 +2687,73 @@ COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_accessibleWithCaret(IUnknown return E_NOTIMPL; } -COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_relationTargetsOfType(BSTR, long, IUnknown***, long*) +COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_relationTargetsOfType(BSTR type, + long maxTargets, + IUnknown*** targets, + long* nTargets) { - return E_NOTIMPL; + SolarMutexGuard g; + + if (!type || !targets || !nTargets || maxTargets <= 0) + return E_INVALIDARG; + + if (m_isDestroy) + return S_FALSE; + + if (!m_xContext.is()) + return E_FAIL; + + try + { + Reference<XAccessibleRelationSet> xRelationSet = m_xContext->getAccessibleRelationSet(); + if (!xRelationSet.is()) + return S_FALSE; + + const sal_Int16 nUnoRelationType = CAccRelation::mapToUnoRelationType(type); + if (nUnoRelationType == AccessibleRelationType::INVALID) + return S_FALSE; + + AccessibleRelation aRelation = xRelationSet->getRelationByType(nUnoRelationType); + if (aRelation.RelationType != nUnoRelationType || !aRelation.TargetSet.hasElements()) + return S_FALSE; + + const sal_Int32 nRetCount + = std::min(sal_Int32(maxTargets), aRelation.TargetSet.getLength()); + *targets = static_cast<IUnknown**>(CoTaskMemAlloc(nRetCount * sizeof(IUnknown*))); + assert(*targets && "Failed to allocate memory for relation targets"); + + for (sal_Int32 i = 0; i < nRetCount; i++) + { + Reference<XAccessible> xTarget = aRelation.TargetSet[i]; + assert(xTarget.is()); + + IAccessible* pIAccessible = CMAccessible::get_IAccessibleFromXAccessible(xTarget.get()); + if (!pIAccessible) + { + Reference<XAccessibleContext> xTargetContext = xTarget->getAccessibleContext(); + if (!xTargetContext.is()) + { + SAL_WARN("iacc2", "Relation target doesn't have an accessible context"); + CoTaskMemFree(*targets); + return E_FAIL; + } + Reference<XAccessible> xParent = xTargetContext->getAccessibleParent(); + CMAccessible::g_pAccObjectManager->InsertAccObj(xTarget.get(), xParent.get()); + pIAccessible = CMAccessible::get_IAccessibleFromXAccessible(xTarget.get()); + } + assert(pIAccessible && "Couldn't retrieve IAccessible object for relation target."); + + pIAccessible->AddRef(); + (*targets)[i] = pIAccessible; + } + + *nTargets = nRetCount; + return S_OK; + } + catch (...) + { + return E_FAIL; + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 002a29b8dd0d4c61194227e4151b4847480cb8b1 Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Thu Aug 22 09:37:01 2024 +0100 Commit: Michael Weghorn <m.wegh...@posteo.de> CommitDate: Fri Aug 23 07:40:10 2024 +0200 tdf#91739 wina11y: Add skeleton IAccessible2_2 implementation Derive from the `IAccessible2_2` insterface [1] instead of directly from (its base interface) `IAccessible2` and add dummy implementations for the corresponding methods that return `E_NOTIMPL` to indicate that there's no real implementation yet. An actual implementations for at least `CMAccessible::get_relationTargetsOfType` is planned for a separate upcoming commit. Support for the interface can be checked e.g. in Accerciser's Python console: Before: >>> from comInterfaces import IAccessible2Lib as IA2 >>> focus.IAccessibleObject <POINTER(IAccessible2) ptr=0x9667f24 at 6841850> >>> focus.IAccessibleObject.QueryInterface(IA2.IAccessible2) <POINTER(IAccessible2) ptr=0x9667f24 at 6822120> >>> focus.IAccessibleObject.QueryInterface(IA2.IAccessible2_2) Traceback (most recent call last): File "<console>", line 1, in <module> File "C: ools self.__com_QueryInterface(byref(iid), byref(p)) File "C: ools return super().__call__(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ctypes.COMError: (-2147467262, 'No such interface supported', (None, None, None, 0, None)) With this commit in place: >>> from comInterfaces import IAccessible2Lib as IA2 >>> focus.IAccessibleObject <POINTER(IAccessible2) ptr=0x988c784 at 6363580> >>> focus.IAccessibleObject.QueryInterface(IA2.IAccessible2) <POINTER(IAccessible2) ptr=0x988c784 at c55d7b0> >>> focus.IAccessibleObject.QueryInterface(IA2.IAccessible2_2) <POINTER(IAccessible2_2) ptr=0x974d824 at 6847f80> And calling one of the methods gives a "Not Implemented" COMError as expected: >>> ia2_2 = focus.IAccessibleObject.QueryInterface(IA2.IAccessible2_2) >>> ia2_2.attribute("dummy") Traceback (most recent call last): File "<console>", line 1, in <module> File "C: ools return self.fget(self.instance, *args) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _ctypes.COMError: (-2147467263, 'Not implemented', (None, None, None, 0, None)) [1] https://github.com/LinuxA11y/IAccessible2/blob/34f96c2a28f4974ad488703b41599a6c6059a7e0/api/Accessible2_2.idl Change-Id: I77bed1f27353c4d08229ffe17afee03b813a4d4f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172256 Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> diff --git a/winaccessibility/source/UAccCOM/MAccessible.cxx b/winaccessibility/source/UAccCOM/MAccessible.cxx index cdc90cfb82bd..549fee11a92f 100644 --- a/winaccessibility/source/UAccCOM/MAccessible.cxx +++ b/winaccessibility/source/UAccCOM/MAccessible.cxx @@ -2676,4 +2676,20 @@ COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_attributes(/*[out]*/ BSTR *p } catch(...) { return E_FAIL; } } +// IAccessible2_2 methods +COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_attribute(BSTR, VARIANT*) +{ + return E_NOTIMPL; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_accessibleWithCaret(IUnknown**, long*) +{ + return E_NOTIMPL; +} + +COM_DECLSPEC_NOTHROW STDMETHODIMP CMAccessible::get_relationTargetsOfType(BSTR, long, IUnknown***, long*) +{ + return E_NOTIMPL; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/winaccessibility/source/UAccCOM/MAccessible.h b/winaccessibility/source/UAccCOM/MAccessible.h index b17a8d78b7d9..7bd2153bf344 100644 --- a/winaccessibility/source/UAccCOM/MAccessible.h +++ b/winaccessibility/source/UAccCOM/MAccessible.h @@ -64,6 +64,7 @@ public: COM_INTERFACE_ENTRY(IMAccessible) COM_INTERFACE_ENTRY(IAccessible) COM_INTERFACE_ENTRY(IAccessible2) + COM_INTERFACE_ENTRY(IAccessible2_2) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IAccessibleApplication) COM_INTERFACE_ENTRY(IServiceProvider) @@ -121,6 +122,11 @@ public: STDMETHOD(get_locale)( IA2Locale __RPC_FAR *locale ) override; STDMETHOD(get_attributes)(/*[out]*/ BSTR *pAttr) override; + // IAccessible2_2 methods + STDMETHOD(get_attribute)(BSTR name, VARIANT* attribute) override; + STDMETHOD(get_accessibleWithCaret)(IUnknown** accessible, long* caretOffset) override; + STDMETHOD(get_relationTargetsOfType)(BSTR type, long maxTargets, IUnknown*** targets, long* nTargets) override; + //IServiceProvider. STDMETHOD(QueryService)(REFGUID guidService, REFIID riid, void** ppvObject) override; diff --git a/winaccessibility/source/UAccCOMIDL/UAccCOM.idl b/winaccessibility/source/UAccCOMIDL/UAccCOM.idl index 8c63a5a3bfdf..f006146fd58f 100644 --- a/winaccessibility/source/UAccCOMIDL/UAccCOM.idl +++ b/winaccessibility/source/UAccCOMIDL/UAccCOM.idl @@ -30,7 +30,7 @@ import "defines.idl"; helpstring("IMAccessible Interface"), pointer_default(unique) ] - interface IMAccessible : IAccessible2 + interface IMAccessible : IAccessible2_2 { [id(2), helpstring("method Put_XAccRole")] HRESULT Put_XAccRole(unsigned short pRole); [id(3), helpstring("method DecreaseState")] HRESULT DecreaseState(DWORD pXSate);