Skip to content

Commit 455407c

Browse files
committed
Reference commit
1 parent 22c94bf commit 455407c

File tree

3 files changed

+141
-2
lines changed

3 files changed

+141
-2
lines changed

src/tools/scratch/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Narrator Buddy
2+
3+
This is a sample of how we might implement Narrator Buddy. This was an internal tool for taking what narrator would read aloud, and logging it to the console.
4+
5+
Currently this builds as a scratch project in the Terminal solution. It's provided as a reference implementation for how a real narrator buddy might be implemented in the future.

src/tools/scratch/Scratch.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ConfigurationType>Application</ConfigurationType>
1010
</PropertyGroup>
1111
<Import Project="..\..\common.build.pre.props" />
12+
<Import Project="..\..\common.nugetversions.props" />
1213
<ItemGroup>
1314
<ClCompile Include="main.cpp" />
1415
</ItemGroup>
@@ -32,4 +33,5 @@
3233
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
3334
<Import Project="..\..\common.build.post.props" />
3435
<Import Project="..\..\common.build.tests.props" />
36+
<Import Project="..\..\common.nugetversions.targets" />
3537
</Project>

src/tools/scratch/main.cpp

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,141 @@
22
// Licensed under the MIT license.
33

44
#include <windows.h>
5+
#include <iostream>
6+
#include <string>
7+
#include <vector>
8+
#include <array>
9+
#include <thread>
10+
#include <sstream>
511

6-
// This wmain exists for help in writing scratch programs while debugging.
7-
int __cdecl wmain(int /*argc*/, WCHAR* /*argv[]*/)
12+
#include <wil\result.h>
13+
#include <wil\resource.h>
14+
#include <wil\com.h>
15+
16+
#include <evntcons.h>
17+
#include <evntrace.h>
18+
19+
namespace
20+
{
21+
wil::unique_event g_stopEvent{ wil::EventOptions::None };
22+
23+
} // namespace anonymous
24+
25+
void WINAPI ProcessEtwEvent(_In_ PEVENT_RECORD rawEvent)
26+
{
27+
// This is the task id from srh.man (with the same name).
28+
constexpr auto InitiateSpeaking = 5;
29+
30+
auto data = rawEvent->UserData;
31+
auto dataLen = rawEvent->UserDataLength;
32+
auto processId = rawEvent->EventHeader.ProcessId;
33+
auto task = rawEvent->EventHeader.EventDescriptor.Task;
34+
35+
if (task == InitiateSpeaking)
36+
{
37+
// The payload first has an int32 representing the channel we're writing to. This is basically always writing to the
38+
// default channel, so just skip over those bytes... the rest is the string payload we want to speak,
39+
// as a null terminated string.
40+
if (dataLen <= 4)
41+
{
42+
return;
43+
}
44+
45+
const auto stringPayloadSize = (dataLen - 4) / sizeof(wchar_t);
46+
// We don't need the null terminator, because wstring intends to handle that on its own.
47+
const auto stringLen = stringPayloadSize - 1;
48+
const auto payload = std::wstring(reinterpret_cast<wchar_t*>(static_cast<char*>(data) + 4), stringLen);
49+
50+
wprintf(L"[Narrator pid=%d]: %s\n", processId, payload.c_str());
51+
fflush(stdout);
52+
53+
if (payload == L"Exiting Narrator")
54+
{
55+
g_stopEvent.SetEvent();
56+
}
57+
}
58+
}
59+
60+
int __cdecl wmain(int argc, wchar_t* argv[])
861
{
62+
const bool runForever = (argc == 2) &&
63+
std::wstring{ argv[1] } == L"-forever";
64+
65+
GUID sessionGuid;
66+
FAIL_FAST_IF_FAILED(::CoCreateGuid(&sessionGuid));
67+
68+
std::array<wchar_t, 64> traceSessionName{};
69+
FAIL_FAST_IF_FAILED(StringCchPrintf(traceSessionName.data(), static_cast<DWORD>(traceSessionName.size()), L"NarratorTraceSession_%d", ::GetCurrentProcessId()));
70+
71+
unsigned int traceSessionNameBytes = static_cast<unsigned int>((wcslen(traceSessionName.data()) + 1) * sizeof(wchar_t));
72+
73+
// Now, to get tracing. Most settings below are defaults from MSDN, except where noted.
74+
// First, set up a session (StartTrace) - which requires a PROPERTIES struct. This has to have
75+
// the session name after it in the same block of memory...
76+
const unsigned int propertiesByteSize = sizeof(EVENT_TRACE_PROPERTIES) + traceSessionNameBytes;
77+
std::vector<char> eventTracePropertiesBuffer(propertiesByteSize);
78+
auto properties = reinterpret_cast<EVENT_TRACE_PROPERTIES*>(eventTracePropertiesBuffer.data());
79+
80+
// Set up properties struct for a real-time session...
81+
properties->Wnode.BufferSize = propertiesByteSize;
82+
properties->Wnode.Guid = sessionGuid;
83+
properties->Wnode.ClientContext = 1;
84+
properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
85+
properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE | EVENT_TRACE_USE_PAGED_MEMORY;
86+
properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
87+
properties->FlushTimer = 1;
88+
// Finally, copy the session name...
89+
memcpy(properties + 1, traceSessionName.data(), traceSessionNameBytes);
90+
91+
std::thread traceThread;
92+
auto joinTraceThread = wil::scope_exit([&]() {
93+
if (traceThread.joinable())
94+
{
95+
traceThread.join();
96+
}
97+
});
98+
99+
TRACEHANDLE session{};
100+
const auto rc = ::StartTrace(&session, traceSessionName.data(), properties);
101+
FAIL_FAST_IF(rc != ERROR_SUCCESS);
102+
auto stopTrace = wil::scope_exit([&]() {
103+
EVENT_TRACE_PROPERTIES properties{};
104+
properties.Wnode.BufferSize = sizeof(properties);
105+
properties.Wnode.Guid = sessionGuid;
106+
properties.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
107+
108+
::ControlTrace(session, nullptr, &properties, EVENT_TRACE_CONTROL_STOP);
109+
});
110+
111+
constexpr GUID narratorProviderGuid = { 0x835b79e2, 0xe76a, 0x44c4, 0x98, 0x85, 0x26, 0xad, 0x12, 0x2d, 0x3b, 0x4d };
112+
FAIL_FAST_IF(ERROR_SUCCESS != ::EnableTrace(TRUE /* enable */, 0 /* enableFlag */, TRACE_LEVEL_VERBOSE, &narratorProviderGuid, session));
113+
auto disableTrace = wil::scope_exit([&]() {
114+
::EnableTrace(FALSE /* enable */, 0 /* enableFlag */, TRACE_LEVEL_VERBOSE, &narratorProviderGuid, session);
115+
});
116+
117+
// Finally, start listening (OpenTrace/ProcessTrace/CloseTrace)...
118+
EVENT_TRACE_LOGFILE trace{};
119+
trace.LoggerName = traceSessionName.data();
120+
trace.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
121+
trace.EventRecordCallback = ProcessEtwEvent;
122+
123+
using unique_tracehandle = wil::unique_any<TRACEHANDLE, decltype(::CloseTrace), ::CloseTrace>;
124+
unique_tracehandle traceHandle{ ::OpenTrace(&trace) };
125+
126+
// Since the actual call to ProcessTrace blocks while it's working,
127+
// we spin up a separate thread to do that.
128+
traceThread = std::thread([traceHandle(std::move(traceHandle))]() mutable {
129+
::ProcessTrace(traceHandle.addressof(), 1 /* handleCount */, nullptr /* startTime */, nullptr /* endTime */);
130+
});
131+
132+
if (runForever)
133+
{
134+
::Sleep(INFINITE);
135+
}
136+
else
137+
{
138+
g_stopEvent.wait(INFINITE);
139+
}
140+
9141
return 0;
10142
}

0 commit comments

Comments
 (0)