Skip to content

Commit 61e47fb

Browse files
Merge e29988d into 8d6a385
2 parents 8d6a385 + e29988d commit 61e47fb

14 files changed

Lines changed: 466 additions & 1 deletion

File tree

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@
3030
[submodule "include/Detours"]
3131
path = include/detours
3232
url = https://github.com/microsoft/Detours/
33+
[submodule "include/microsoft-ui-uiautomation"]
34+
path = include/microsoft-ui-uiautomation
35+
url = https://github.com/michaeldcurran/microsoft-ui-uiautomation
36+
[submodule "include/wil"]
37+
path = include/wil
38+
url = https://github.com/microsoft/wil

include/microsoft-ui-uiautomation

include/wil

Submodule wil added at 3234003

nvdaHelper/UIARemote/UIARemote.cpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
This file is a part of the NVDA project.
3+
URL: http://www.nvaccess.org/
4+
Copyright 2021-2022 NV Access Limited
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License version 2.0, as published by
7+
the Free Software Foundation.
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
This license can be found at:
12+
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
*/
14+
15+
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
16+
#include <locale>
17+
#include <codecvt>
18+
#include <functional>
19+
#include <string>
20+
#include <windows.h>
21+
#include <atlsafe.h>
22+
#include <atlcomcli.h>
23+
#include <roapi.h>
24+
#include <winstring.h>
25+
#include <UIAutomation.h>
26+
#include <UiaOperationAbstraction/UiaOperationAbstraction.h>
27+
#include <UiaOperationAbstraction/SafeArrayUtil.h>
28+
#include <common/log.h>
29+
#include <winrt/microsoft.ui.uiautomation.h>
30+
31+
using namespace UiaOperationAbstraction;
32+
33+
#include "remoteLog.h"
34+
35+
wchar_t dllDirectory[MAX_PATH];
36+
37+
// Several custom extension GUIDs specific to Microsoft Word
38+
winrt::guid guid_msWord_extendedTextRangePattern{ 0x93514122, 0xff04, 0x4b2c, { 0xa4, 0xad, 0x4a, 0xb0, 0x45, 0x87, 0xc1, 0x29 } };
39+
winrt::guid guid_msWord_getCustomAttributeValue{ 0x81aca91, 0x32f2, 0x46f0, { 0x9f, 0xb9, 0x1, 0x70, 0x38, 0xbc, 0x45, 0xf8 } };
40+
41+
bool _isInitialized {false};
42+
43+
// Fetches a custom attribute value from a range of text in Microsoft Word.
44+
// this function uses the UI automation Operation Abstraction API to call the Microsoft Word specific custom extension
45+
extern "C" __declspec(dllexport) bool __stdcall msWord_getCustomAttributeValue(IUIAutomationElement* docElementArg, IUIAutomationTextRange* pTextRangeArg, int customAttribIDArg, VARIANT* pCustomAttribValueArg) {
46+
if(!_isInitialized) {
47+
LOG_ERROR(L"UIARemote not initialized!");
48+
return false;
49+
}
50+
try {
51+
auto scope=UiaOperationScope::StartNew();
52+
RemoteableLogger logger{scope};
53+
// Here starts declaritive code which will be executed remotely
54+
logger<<L"Remoting msWord_getCustomAttributeValue"<<endl;
55+
UiaBool isExtensionSupported{false};
56+
UiaElement docElement{docElementArg};
57+
UiaTextRange textRange{pTextRangeArg};
58+
UiaInt customAttribID{customAttribIDArg};
59+
UiaVariant customAttribValue;
60+
scope.If(docElement.IsExtensionSupported(guid_msWord_extendedTextRangePattern),[&]() {
61+
logger<<L"guid_msWord_extendedTextRangePattern is supported extension"<<endl;
62+
UiaElement patternElement{nullptr};
63+
docElement.CallExtension(guid_msWord_extendedTextRangePattern, patternElement);
64+
scope.If(patternElement,[&]() {
65+
logger<<L"Got custom pattern element "<<endl;
66+
scope.If(patternElement.IsExtensionSupported(guid_msWord_getCustomAttributeValue),[&]() {
67+
isExtensionSupported = true;
68+
logger<<L"guid_msWord_getCustomAttributeValue extension supported on pattern"<<endl;
69+
patternElement.CallExtension(guid_msWord_getCustomAttributeValue, textRange, customAttribID, customAttribValue);
70+
logger<<L"Called guid_msWord_getCustomAttributeValue extention"<<endl;
71+
}, [&]() {
72+
logger<<L"No guid_msWord_getCustomAttributeValue extension supported"<<endl;
73+
});
74+
}, [&]() {
75+
logger<<L"Could not fetch guid_msWord_extendedTextRangePattern pattern"<<endl;
76+
});
77+
}, [&]() {
78+
logger<<L"No guid_msWord_extendedTextRangePattern extension supported"<<endl;
79+
});
80+
// Request that certain variables be made available locally after execution remotely
81+
scope.BindResult(isExtensionSupported, customAttribValue);
82+
// Actually execute the remote code
83+
auto res = scope.ResolveHr();
84+
if(res != S_OK) {
85+
LOG_ERROR(L"Error in scope.Resolve: code "<<res);
86+
return false;
87+
}
88+
logger.dumpLog();
89+
// We aare back to local again
90+
if(isExtensionSupported) {
91+
if(customAttribValue.IsInt()) {
92+
pCustomAttribValueArg->vt = VT_I4;
93+
pCustomAttribValueArg->lVal = customAttribValue.AsInt();
94+
return true;
95+
} else if(customAttribValue.IsString()) {
96+
pCustomAttribValueArg->vt = VT_BSTR;
97+
pCustomAttribValueArg->bstrVal = customAttribValue.AsString().get();
98+
return true;
99+
} else {
100+
LOG_ERROR(L"Unknown data type");
101+
return false;
102+
}
103+
} else {
104+
LOG_DEBUG(L"Extension not supported");
105+
}
106+
} catch (std::exception& e) {
107+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
108+
auto what = converter.from_bytes(e.what());
109+
LOG_ERROR(L"msWord_getCustomAttributeValue exception: "<<what);
110+
} catch(...) {
111+
LOG_ERROR(L"msWord_getCustomAttributeValue exception: unknown");
112+
}
113+
return false;
114+
}
115+
116+
// Registers and initializes the Microsoft-ui-UIAutomation remote operations library.
117+
extern "C" __declspec(dllexport) bool __stdcall initialize(bool doRemote, IUIAutomation* client) {
118+
std::wstring manifestPath = dllDirectory;
119+
manifestPath += L"\\Microsoft.UI.UIAutomation.dll.manifest";
120+
ACTCTX actCtx={0};
121+
actCtx.cbSize=sizeof(actCtx);
122+
actCtx.lpSource = L"Microsoft.UI.UIAutomation.dll.manifest";
123+
actCtx.lpAssemblyDirectory = dllDirectory;
124+
actCtx.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
125+
HANDLE hActCtx=CreateActCtx(&actCtx);
126+
if(hActCtx==NULL) {
127+
LOG_ERROR(L"Could not create activation context for "<<manifestPath);
128+
return false;
129+
}
130+
ULONG_PTR actCtxCookie;
131+
if(!ActivateActCtx(hActCtx,&actCtxCookie)) {
132+
LOG_ERROR(L"Error activating activation context for "<<manifestPath);
133+
ReleaseActCtx(hActCtx);
134+
return false;
135+
}
136+
LOG_INFO(L"Registered "<<manifestPath);
137+
if(!winrt::get_activation_factory<winrt::Microsoft::UI::UIAutomation::AutomationRemoteOperation>()) {
138+
LOG_ERROR(L"Unable to get Microsoft.UI.UIAutomation activation factory");
139+
return false;
140+
}
141+
LOG_INFO(L"Microsoft.UI.UIAutomation is available");
142+
UiaOperationAbstraction::Initialize(doRemote,client);
143+
_isInitialized = true;
144+
return true;
145+
}
146+
147+
BOOL WINAPI DllMain(HINSTANCE hModule,DWORD reason,LPVOID lpReserved) {
148+
if(reason==DLL_PROCESS_ATTACH) {
149+
GetModuleFileName(hModule,dllDirectory,MAX_PATH);
150+
PathRemoveFileSpec(dllDirectory);
151+
}
152+
return true;
153+
}
154+

nvdaHelper/UIARemote/remoteLog.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
This file is a part of the NVDA project.
3+
URL: http://www.nvaccess.org/
4+
Copyright 2021-2022 NV Access Limited
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License version 2.0, as published by
7+
the Free Software Foundation.
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
This license can be found at:
12+
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
*/
14+
15+
#pragma once
16+
17+
const std::wstring endl{L"\n"};
18+
19+
class RemoteableLogger;
20+
21+
// A class for logging messages from within a remote ops call.
22+
// Push messages to the object with << just like an ostream.
23+
// Currently standard strings, UiaStrings, and UiaInt instances are supported.
24+
// After remote execution is complete, call dumpLog to write the content to our standard logging framework.
25+
class RemoteableLogger {
26+
public:
27+
28+
RemoteableLogger(UiaOperationScope& scope): _log{} {
29+
scope.BindResult(_log);
30+
}
31+
32+
template<typename T> RemoteableLogger& operator <<(T& message) {
33+
if constexpr(std::is_base_of<UiaInt,T>::value) {
34+
_log.Append(message.Stringify());
35+
} else {
36+
_log.Append(message);
37+
}
38+
return *this;
39+
}
40+
41+
void dumpLog() {
42+
assert(!UiaOperationAbstraction::ShouldUseRemoteApi());
43+
std::wstring messageBlock{L"Dump log start:\n"};
44+
try {
45+
auto v = *_log;
46+
for(const auto& message: v) {
47+
messageBlock+=message.get();
48+
}
49+
} catch (std::exception& e) {
50+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
51+
auto what = converter.from_bytes(e.what());
52+
messageBlock+=L"dumpLog exception: " + what + L"\n";
53+
}
54+
messageBlock+=L"Dump log end";
55+
LOG_DEBUG(messageBlock);
56+
}
57+
58+
private:
59+
UiaArray<UiaString> _log;
60+
61+
};

nvdaHelper/UIARemote/sconscript

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
###
2+
#This file is a part of the NVDA project.
3+
#URL: http://www.nvda-project.org/
4+
#Copyright 2021 NV Access Limited.
5+
#This program is free software: you can redistribute it and/or modify
6+
#it under the terms of the GNU General Public License version 2.0, as published by
7+
#the Free Software Foundation.
8+
#This program is distributed in the hope that it will be useful,
9+
#but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
#This license can be found at:
12+
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13+
###
14+
15+
Import([
16+
'env',
17+
'localLib',
18+
'MSUIA_lib_outDir',
19+
'MSUIA_include_outDir',
20+
])
21+
22+
env = env.Clone()
23+
env.Append(CPPPATH=Dir('#include/wil/include'))
24+
env.Append(CPPPATH=MSUIA_include_outDir)
25+
env.Append(CCFLAGS='/MD')
26+
27+
UIARemoteLib=env.SharedLibrary(
28+
target="UIARemote",
29+
source=[
30+
env['projectResFile'],
31+
"UIARemote.cpp",
32+
],
33+
LIBS=[
34+
"runtimeobject",
35+
"UIAutomationCore",
36+
localLib[2],
37+
MSUIA_lib_outDir.File('UiaOperationAbstraction.lib'),
38+
],
39+
)
40+
41+
Return('UIARemoteLib')

nvdaHelper/archBuild_sconscript

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ if TARGET_ARCH=='x86':
209209
if signExec:
210210
env.AddPostAction(win10localLib[0],[signExec])
211211
env.Install(libInstallDir,win10localLib)
212+
MSUIA_lib_outDir,MSUIA_include_outDir = thirdPartyEnv.SConscript('microsoft-ui-uiautomation/sconscript')
213+
Export('MSUIA_lib_outDir')
214+
Export('MSUIA_include_outDir')
215+
UIARemoteLib=env.SConscript('UIARemote/sconscript')
216+
if signExec:
217+
env.AddPostAction(UIARemoteLib[0],[signExec])
218+
env.Install(libInstallDir,UIARemoteLib)
212219

213220
clientLib=env.SConscript('client/sconscript')
214221
Export('clientLib')
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3+
<assemblyIdentity version="1.0.0.0" name="UIARemote"/>
4+
5+
<file name="Microsoft.UI.UIAutomation.dll">
6+
<activatableClass
7+
name="Microsoft.UI.UIAutomation.AutomationRemoteOperation"
8+
threadingModel="both"
9+
xmlns="urn:schemas-microsoft-com:winrt.v1" />
10+
</file>
11+
12+
</assembly>

0 commit comments

Comments
 (0)