diff --git a/.gitmodules b/.gitmodules index 94c15f5d611..ae380139786 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,3 +30,9 @@ [submodule "include/Detours"] path = include/detours url = https://github.com/microsoft/Detours/ +[submodule "include/microsoft-ui-uiautomation"] + path = include/microsoft-ui-uiautomation + url = https://github.com/michaeldcurran/microsoft-ui-uiautomation +[submodule "include/wil"] + path = include/wil + url = https://github.com/microsoft/wil diff --git a/include/microsoft-ui-uiautomation b/include/microsoft-ui-uiautomation new file mode 160000 index 00000000000..224b22f3bf9 --- /dev/null +++ b/include/microsoft-ui-uiautomation @@ -0,0 +1 @@ +Subproject commit 224b22f3bf9e6bcd7326e471d1e134fe739bf4ff diff --git a/include/wil b/include/wil new file mode 160000 index 00000000000..3234003fe96 --- /dev/null +++ b/include/wil @@ -0,0 +1 @@ +Subproject commit 3234003fe96a02a9c1b686cf51f43ee5d3ec5c84 diff --git a/nvdaHelper/UIARemote/UIARemote.cpp b/nvdaHelper/UIARemote/UIARemote.cpp new file mode 100644 index 00000000000..e95425d3a40 --- /dev/null +++ b/nvdaHelper/UIARemote/UIARemote.cpp @@ -0,0 +1,163 @@ +/* +This file is a part of the NVDA project. +URL: http://www.nvaccess.org/ +Copyright 2021-2022 NV Access Limited + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2.0, as published by + the Free Software Foundation. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +This license can be found at: +http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace UiaOperationAbstraction; + +#include "remoteLog.h" + +wchar_t dllDirectory[MAX_PATH]; + +// Several custom extension GUIDs specific to Microsoft Word +winrt::guid guid_msWord_extendedTextRangePattern{ 0x93514122, 0xff04, 0x4b2c, { 0xa4, 0xad, 0x4a, 0xb0, 0x45, 0x87, 0xc1, 0x29 } }; +winrt::guid guid_msWord_getCustomAttributeValue{ 0x81aca91, 0x32f2, 0x46f0, { 0x9f, 0xb9, 0x1, 0x70, 0x38, 0xbc, 0x45, 0xf8 } }; + +bool _isInitialized {false}; + +// Fetches a custom attribute value from a range of text in Microsoft Word. +// this function uses the UI automation Operation Abstraction API to call the Microsoft Word specific custom extension +extern "C" __declspec(dllexport) bool __stdcall msWord_getCustomAttributeValue(IUIAutomationElement* docElementArg, IUIAutomationTextRange* pTextRangeArg, int customAttribIDArg, VARIANT* pCustomAttribValueArg) { + if(!_isInitialized) { + LOG_ERROR(L"UIARemote not initialized!"); + return false; + } + try { + auto scope=UiaOperationScope::StartNew(); + RemoteableLogger logger{scope}; + // Here starts declaritive code which will be executed remotely + logger<vt = VT_I4; + pCustomAttribValueArg->lVal = customAttribValue.AsInt(); + return true; + } else if(customAttribValue.IsString()) { + pCustomAttribValueArg->vt = VT_BSTR; + pCustomAttribValueArg->bstrVal = customAttribValue.AsString().get(); + return true; + } else { + LOG_ERROR(L"Unknown data type"); + return false; + } + } else { + LOG_DEBUG(L"Extension not supported"); + } + } catch (std::exception& e) { + auto wideWhat = stringToWstring(e.what()); + LOG_ERROR(L"msWord_getCustomAttributeValue exception: "<()) { + LOG_ERROR(L"Unable to get Microsoft.UI.UIAutomation activation factory"); + return false; + } + LOG_INFO(L"Microsoft.UI.UIAutomation is available"); + UiaOperationAbstraction::Initialize(doRemote,client); + _isInitialized = true; + return true; +} + +BOOL WINAPI DllMain(HINSTANCE hModule,DWORD reason,LPVOID lpReserved) { + if(reason==DLL_PROCESS_ATTACH) { + GetModuleFileName(hModule,dllDirectory,MAX_PATH); + PathRemoveFileSpec(dllDirectory); + } + return true; +} + diff --git a/nvdaHelper/UIARemote/remoteLog.h b/nvdaHelper/UIARemote/remoteLog.h new file mode 100644 index 00000000000..b3bb9856dbd --- /dev/null +++ b/nvdaHelper/UIARemote/remoteLog.h @@ -0,0 +1,77 @@ +/* +This file is a part of the NVDA project. +URL: http://www.nvaccess.org/ +Copyright 2021-2022 NV Access Limited + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2.0, as published by + the Free Software Foundation. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +This license can be found at: +http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +*/ + +#pragma once + +// Converts a utf8 encoded string into a utf16 encoded wstring +std::wstring stringToWstring(const std::string& from) { + int wideLen = MultiByteToWideChar(CP_UTF8, 0, from.c_str(), from.length(), nullptr, 0); + std::wstring wideBuf (wideLen, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, from.c_str(), from.length(), wideBuf.data(), wideLen); + return wideBuf; +} + +const std::wstring endl{L"\n"}; + +class RemoteableLogger; + +// A class for logging messages from within a remote ops call. +// Push messages to the object with << just like an ostream. +// Currently standard strings, UiaStrings, and UiaInt instances are supported. +// After remote execution is complete, call dumpLog to write the content to our standard logging framework. +class RemoteableLogger { + public: + + RemoteableLogger(UiaOperationScope& scope): _log{} { + scope.BindResult(_log); + } + + RemoteableLogger& operator <<(UiaInt& message) { + _log.Append(message.Stringify()); + return *this; + } + + RemoteableLogger& operator <<(UiaString& message) { + _log.Append(message); + return *this; + } + + RemoteableLogger& operator <<(const std::wstring message) { + _log.Append(message); + return *this; + } + + void dumpLog() { + assert(!UiaOperationAbstraction::ShouldUseRemoteApi()); + std::wstring messageBlock{L"Dump log start:\n"}; + try { + // locally, a UiaArray is a shared_ptr to a vector of will_shared_bstr + const std::vector& v = *_log; + for(const auto& message: v) { + messageBlock+=message.get(); + } + } catch (std::exception& e) { + auto wideWhat = stringToWstring(e.what()); + messageBlock += L"dumpLog exception: "; + messageBlock += wideWhat; + messageBlock += L"\n"; + } + messageBlock+=L"Dump log end"; + LOG_DEBUG(messageBlock); + } + + private: + UiaArray _log; + +}; diff --git a/nvdaHelper/UIARemote/sconscript b/nvdaHelper/UIARemote/sconscript new file mode 100644 index 00000000000..0d6f7ea9154 --- /dev/null +++ b/nvdaHelper/UIARemote/sconscript @@ -0,0 +1,41 @@ +### +#This file is a part of the NVDA project. +#URL: http://www.nvda-project.org/ +#Copyright 2021 NV Access Limited. +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License version 2.0, as published by +#the Free Software Foundation. +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#This license can be found at: +#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +### + +Import([ + 'env', + 'localLib', + 'MSUIA_lib_outDir', + 'MSUIA_include_outDir', +]) + +env = env.Clone() +env.Append(CPPPATH=Dir('#include/wil/include')) +env.Append(CPPPATH=MSUIA_include_outDir) +env.Append(CCFLAGS='/MD') + +UIARemoteLib=env.SharedLibrary( + target="UIARemote", + source=[ + env['projectResFile'], + "UIARemote.cpp", + ], + LIBS=[ + "runtimeobject", + "UIAutomationCore", + localLib[2], + MSUIA_lib_outDir.File('UiaOperationAbstraction.lib'), + ], +) + +Return('UIARemoteLib') diff --git a/nvdaHelper/archBuild_sconscript b/nvdaHelper/archBuild_sconscript index ad6c373b41d..06cc6c48ef4 100644 --- a/nvdaHelper/archBuild_sconscript +++ b/nvdaHelper/archBuild_sconscript @@ -209,6 +209,13 @@ if TARGET_ARCH=='x86': if signExec: env.AddPostAction(win10localLib[0],[signExec]) env.Install(libInstallDir,win10localLib) + MSUIA_lib_outDir,MSUIA_include_outDir = thirdPartyEnv.SConscript('microsoft-ui-uiautomation/sconscript') + Export('MSUIA_lib_outDir') + Export('MSUIA_include_outDir') + UIARemoteLib=env.SConscript('UIARemote/sconscript') + if signExec: + env.AddPostAction(UIARemoteLib[0],[signExec]) + env.Install(libInstallDir,UIARemoteLib) clientLib=env.SConscript('client/sconscript') Export('clientLib') diff --git a/nvdaHelper/microsoft-ui-uiautomation/Microsoft.UI.UIAutomation.dll.manifest b/nvdaHelper/microsoft-ui-uiautomation/Microsoft.UI.UIAutomation.dll.manifest new file mode 100644 index 00000000000..f8b20a28579 --- /dev/null +++ b/nvdaHelper/microsoft-ui-uiautomation/Microsoft.UI.UIAutomation.dll.manifest @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/nvdaHelper/microsoft-ui-uiautomation/sconscript b/nvdaHelper/microsoft-ui-uiautomation/sconscript new file mode 100644 index 00000000000..0573e54bc81 --- /dev/null +++ b/nvdaHelper/microsoft-ui-uiautomation/sconscript @@ -0,0 +1,121 @@ +### +#This file is a part of the NVDA project. +#URL: http://www.nvaccess.org/ +#Copyright 2021 NV Access Limited +#This program is free software: you can redistribute it and/or modify +#it under the terms of the GNU General Public License version 2.0, as published by +#the Free Software Foundation. +#This program is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +#This license can be found at: +#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +### + +import os +import glob + +""" +Builds the open source Microsoft-UI-UIAutomation Remote Operations library from https://github.com/microsoft/microsoft-ui-uiautomation. +This library contains both a low-level winrt API, and a higher-level pure C++ API. +The outputs of this sconscript are: +* a 'lib' directory, containing: + * microsoft.ui.uiAutomation.dll and .lib: the dll containing the low-level winrt implementation. + To use in other code, link against the .lib, and also load the included microsoft.ui.uiAutomation.dll.manifest file into an activation context and activate it. + * UiaOperationAbstraction.lib: a static library containing runtime code for the higher-level pure C++ API. + This should be linked into any compiled executable or library that needs to use the higher-level C++ API. +* an 'include' directory, containing: + * a 'UIAOperationAbstraction' directory containing all the public headers for the high-level C++ API + * a 'winrt' directory containing the generated C++/winrt language bindings of the low-level API, required by the high-level C++ API headers +""" + + +Import( + 'env', + 'sourceDir', + 'sourceLibDir', +) + +MSUIA_sourceDir = Dir('#include/microsoft-ui-uiautomation/src/uiAutomation') +MSUIA_lib_outDir = Dir('lib') +MSUIA_include_outDir = Dir('include') +MSUIA_solutionFile = MSUIA_sourceDir.File('UIAutomation.sln') + +MSUIA_wrapper_libs = env.Command( + target = [ + MSUIA_lib_outDir.File('Microsoft.UI.UIAutomation.dll'), + MSUIA_lib_outDir.File('Microsoft.UI.UIAutomation.exp'), + MSUIA_lib_outDir.File('Microsoft.UI.UIAutomation.pdb'), + MSUIA_lib_outDir.File('Microsoft.UI.UIAutomation.lib'), + MSUIA_lib_outDir.File('winmd/Microsoft.UI.UIAutomation.winmd'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/microsoft.ui.uiautomation.h'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.0.h'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.1.h'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.2.h'), + ], + source = [ + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Microsoft.UI.UIAutomation.vcxproj'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Microsoft.UI.UIAutomation.idl'), + glob.glob(os.path.join(MSUIA_sourceDir.abspath, 'microsoft.ui.uiautomation', '*.cpp')), + glob.glob(os.path.join(MSUIA_sourceDir.abspath, 'microsoft.ui.uiautomation', '*.h')), + ], + action = [ + # Fetch any required NuGet packages + f"msbuild {MSUIA_solutionFile} /t:Restore /p:RestorePackagesConfig=true,Configuration=Release,Platform=x86", + # Remove any old generated files + Delete(MSUIA_sourceDir.Dir('microsoft.ui.uiautomation/Generated Files')), + # Do the actual build + "msbuild /t:Build /p:Configuration=Release,Platform=x86,OutDir={outDir}/ $SOURCE".format(outDir=MSUIA_lib_outDir.abspath) + ], +) +env.Ignore(MSUIA_wrapper_libs,MSUIA_sourceDir.File('microsoft.ui.uiautomation/Microsoft.UI.UIAutomation_h.h')) + +env.Install(sourceLibDir,MSUIA_wrapper_libs[0]) +env.Install(sourceLibDir, "Microsoft.UI.UIAutomation.dll.manifest") + +MSUIA_abstraction_libs = env.Command( + target = [ + MSUIA_lib_outDir.File('UiaOperationAbstraction.lib'), + ], + source = [ + MSUIA_sourceDir.File('UiaOperationAbstraction/UiaOperationAbstraction.vcxproj'), + glob.glob(os.path.join(MSUIA_sourceDir.abspath, 'UiaOperationAbstraction', '*.cpp')), + glob.glob(os.path.join(MSUIA_sourceDir.abspath, 'UiaOperationAbstraction', '*.h')), + ], + action = [ + "msbuild /t:Build /p:Configuration=Release,Platform=x86,OutDir={outDir}/ $SOURCE".format(outDir=MSUIA_lib_outDir.abspath), + ] +) + +env.Depends(MSUIA_abstraction_libs,MSUIA_wrapper_libs) + +MSUIA_wrapper_header = env.Install( + MSUIA_include_outDir.Dir('winrt'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/microsoft.ui.uiautomation.h'), +) +env.Depends(MSUIA_wrapper_header,MSUIA_wrapper_libs) + +MSUIA_wrapper_impl = env.Install( + MSUIA_include_outDir.Dir('winrt/impl'), + [ + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.0.h'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.1.h'), + MSUIA_sourceDir.File('microsoft.ui.uiautomation/Generated Files/winrt/impl/microsoft.ui.uiautomation.2.h'), + ] +) +env.Depends(MSUIA_wrapper_impl,MSUIA_wrapper_libs) + + +MSUIA_abstraction_headers = env.Install( + MSUIA_include_outDir.Dir('UiaOperationAbstraction'), + [ + MSUIA_sourceDir.File('UiaOperationAbstraction/UiaOperationAbstraction.h'), + MSUIA_sourceDir.File('UiaOperationAbstraction/UiaTypeAbstractionEnums.g.h'), + MSUIA_sourceDir.File('UiaOperationAbstraction/UiaTypeAbstraction.g.h'), + MSUIA_sourceDir.File('UiaOperationAbstraction/SafeArrayUtil.h'), + ] +) + +env.Depends(MSUIA_abstraction_headers,[MSUIA_wrapper_header,MSUIA_wrapper_impl]) + +Return(['MSUIA_lib_outDir','MSUIA_include_outDir']) diff --git a/nvdaHelper/readme.md b/nvdaHelper/readme.md index 82c0170db8f..823a7049f4f 100644 --- a/nvdaHelper/readme.md +++ b/nvdaHelper/readme.md @@ -6,18 +6,47 @@ While virtual buffers apply to several different application types, it may first work with browsers, the rest of this document will take a web browser centric view unless specified otherwise. ### Output -Internal code is built into three DLLs: +Internal code is built into several DLLs: - `nvdaHelperLocal.dll` - `nvdaHelperLocalWin10.dll` - `nvdaHelperRemote.dll` - +- Several COM proxy dlls +- `UIARemote.dll` + +The `*local*.dll`'s are built for x86 (ie to match NVDA's arch), others are built for x86, x64, and arm64. + +#### COM proxy dlls Several COM Proxy DLLs are built from IDL files. A COM Proxy tells windows how to marshal data over COM when calling the target API interface. For instance: - IAccessible2 IDL files are built into `IAccessible2Proxy.dll` - ISimpleDOM IDL files are built into `ISimpleDOM.dll` - -The `*local*.dll`'s are built for x86 (ie to match NVDA's arch), others are built for x86, x64, and arm64. + +#### nvdaHelperLocalWin10.dll +Contains code specific to Windows 10 and above, that aides in accessing newer technologies such as Windows OneCore speech synthesis, the Windows in-built OCR service. +This code is mostly C++/WinRT. + +#### nvdaHelperLocal.dll +This dll is loaded directly in to NVDA. It provides the following features: +* client stub methods for several RPC interfaces allowing NVDA to execute code in-process. These interfaces include nvdaInprocUtils, vbufBackends, and displayModel, which are implemented in nvdaHelperRemote.dll. +* Server stub methods for several RPC interfaces allowing in-process code to execute code in NVDA. These interfaces include nvdaController and nvdaControllerInternal. +* Functions to aide NVDA in hooking platform dlls to make their calls easier to cancel +* Several small utility functions that asist in processing text (which are faster in c++). + +#### NVDAHelperRemote.dll +This dll injects itself into other processes on the system, allowing for in-process code execution by NVDA. +It provides the following features: +* Server stub methods for several RPC interfaces, including NVDAInprocUtils, VBufBackends and displayModel. +* Client stub methods for several RPC interfaces, allowing in-process code to execute code back in NVDA. These interfaces include NVDAController and NVDAControllerInternal + +#### UIARemote.dll +This dll is loaded by NVDA, providing utility functions that perform certain tasks or batch procedures on Microsoft UI Automation elements. +It makes use of the UI Automation Remote Operations capabilities in Windows 11, allowing to declaritively define code to access and manipulate UI Automation elements, that will be Just-In-Time compiled by Windows and executed in the process providing the UI Automation elements. + +##### microsoft-ui-uiAutomation remote ops library +As a dependency of UIARemote.dll, the open source [Microsoft-UI-UIAutomation Remote Operations library](https://github.com/microsoft/microsoft-ui-uiautomation) is also built. +This library contains both a low-level winrt API, and a higher-level pure C++ API which depends on the lower-level winrt API. UIARemote.dll tends to use the higher-level Operations abstraction API for its work. +In order for the winrt API to be available, UIARemote must register it with the Windows winrt platform. this involves loading a manifest file (See `microsoft-ui-uiautomation/Microsoft.UI.UIAutomation.dll.manifest`) and activating an activation context. ### Configuring Visual Studio The following steps won't prepare a buildable solution, but it will enable intellisense. diff --git a/source/NVDAObjects/UIA/wordDocument.py b/source/NVDAObjects/UIA/wordDocument.py index 252393d8670..4cb2ca63064 100644 --- a/source/NVDAObjects/UIA/wordDocument.py +++ b/source/NVDAObjects/UIA/wordDocument.py @@ -8,13 +8,16 @@ Dict, ) +import enum from comtypes import COMError from collections import defaultdict +import winVersion import mathPres from scriptHandler import isScriptWaiting import textInfos import eventHandler import UIAHandler +import UIAHandler.remote as UIARemote from logHandler import log import controlTypes import ui @@ -40,6 +43,15 @@ """Support for Microsoft Word via UI Automation.""" + +class UIACustomAttributeID(enum.IntEnum): + LINE_NUMBER = 0 + PAGE_NUMBER = 1 + COLUMN_NUMBER = 2 + SECTION_NUMBER = 3 + BOOKMARK_NAME = 4 + + #: the non-printable unicode character that represents the end of cell or end of row mark in Microsoft Word END_OF_ROW_MARK = '\x07' @@ -412,6 +424,32 @@ def getTextWithFields( # noqa: C901 index+=1 return fields + def _getFormatFieldAtRange(self, textRange, formatConfig, ignoreMixedValues=False): + formatField = super()._getFormatFieldAtRange(textRange, formatConfig, ignoreMixedValues=ignoreMixedValues) + if not formatField: + return formatField + if winVersion.getWinVer() >= winVersion.WIN11: + docElement = self.obj.UIAElement + if formatConfig['reportLineNumber']: + lineNumber = UIARemote.msWord_getCustomAttributeValue( + docElement, textRange, UIACustomAttributeID.LINE_NUMBER + ) + if isinstance(lineNumber, int): + formatField.field['line-number'] = lineNumber + if formatConfig['reportPage']: + sectionNumber = UIARemote.msWord_getCustomAttributeValue( + docElement, textRange, UIACustomAttributeID.SECTION_NUMBER + ) + if isinstance(sectionNumber, int): + formatField.field['section-number'] = sectionNumber + textColumnNumber = UIARemote.msWord_getCustomAttributeValue( + docElement, textRange, UIACustomAttributeID.COLUMN_NUMBER + ) + if isinstance(textColumnNumber, int): + formatField.field['text-column-number'] = textColumnNumber + return formatField + + class WordBrowseModeDocument(UIABrowseModeDocument): def _shouldSetFocusToObj(self, obj: NVDAObject) -> bool: diff --git a/source/UIAHandler/__init__.py b/source/UIAHandler/__init__.py index 8cfcd5a86ce..a375cee02aa 100644 --- a/source/UIAHandler/__init__.py +++ b/source/UIAHandler/__init__.py @@ -44,6 +44,7 @@ from typing import Dict from queue import Queue import aria +from . import remote as UIARemote #: The window class name for Microsoft Word documents. @@ -339,6 +340,8 @@ def MTAThreadFunc(self): if config.conf['UIA']['selectiveEventRegistration']: self._createLocalEventHandlerGroup() self._registerGlobalEventHandlers() + if winVersion.getWinVer() >= winVersion.WIN11: + UIARemote.initialize(True, self.clientObject) except Exception as e: self.MTAThreadInitException=e finally: diff --git a/source/UIAHandler/remote.py b/source/UIAHandler/remote.py new file mode 100644 index 00000000000..53154b17eef --- /dev/null +++ b/source/UIAHandler/remote.py @@ -0,0 +1,39 @@ +# A part of NonVisual Desktop Access (NVDA) +# This file is covered by the GNU General Public License. +# See the file COPYING for more details. +# Copyright (C) 2021-2022 NV Access Limited + + +from typing import Optional, Union +import os +from ctypes import windll, byref, POINTER +from comtypes.automation import VARIANT +import NVDAHelper +from comInterfaces import UIAutomationClient as UIA + + +_dll = None + + +def initialize(doRemote: bool, UIAClient: POINTER(UIA.IUIAutomation)): + """ + Initializes UI Automation remote operations. + @param doRemote: true if code should be executed remotely, or false for locally. + @param UIAClient: the current instance of the UI Automation client library running in NVDA. + """ + global _dll + _dll = windll[os.path.join(NVDAHelper.versionedLibPath, "UIARemote.dll")] + _dll.initialize(doRemote, UIAClient) + + +def msWord_getCustomAttributeValue( + docElement: POINTER(UIA.IUIAutomationElement), + textRange: POINTER(UIA.IUIAutomationTextRange), + customAttribID: int +) -> Optional[Union[int, str]]: + if _dll is None: + raise RuntimeError("UIARemote not initialized") + customAttribValue = VARIANT() + if _dll.msWord_getCustomAttributeValue(docElement, textRange, customAttribID, byref(customAttribValue)): + return customAttribValue.value + return None diff --git a/source/speech/speech.py b/source/speech/speech.py index 2f26f21459c..54280cdc6a6 100644 --- a/source/speech/speech.py +++ b/source/speech/speech.py @@ -2129,6 +2129,10 @@ def getFormatFieldSpeech( # noqa: C901 # %s will be replaced with the number of text columns. text=_("%s columns")%(textColumnCount) textList.append(text) + elif textColumnNumber: + # Translators: Indicates the text column number in a document. + text = _("column {columnNumber}").format(columnNumber=textColumnNumber) + textList.append(text) sectionBreakType=attrs.get("section-break") if sectionBreakType: diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t index 1b919e89d41..2f76b1216cd 100644 --- a/user_docs/en/changes.t2t +++ b/user_docs/en/changes.t2t @@ -23,6 +23,7 @@ What's New in NVDA - Added commands for toggling multiple modifiers simultaneously with a Braille display (#13152) - The Speech Dictionary dialog now features a "Remove all" button to help clear a whole dictionary. (#11802) - Added support for Windows 11 Calculator. (#13212) +- In Microsoft Word with UI Automation enabled on Windows 11, line numbers, section numbers and layout column numbers can now be reported. (#13283) -