- Notifications
You must be signed in to change notification settings - Fork 8.5k
/
Copy pathPtySignalInputThread.cpp
314 lines (278 loc) · 9.72 KB
/
PtySignalInputThread.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include"precomp.h"
#include"PtySignalInputThread.hpp"
#include"output.h"
#include"handle.h"
#include"_stream.h"
#include"../interactivity/inc/ServiceLocator.hpp"
usingnamespaceMicrosoft::Console;
usingnamespaceMicrosoft::Console::Interactivity;
usingnamespaceMicrosoft::Console::VirtualTerminal;
// Constructor Description:
// - Creates the PTY Signal Input Thread.
// Arguments:
// - hPipe - a handle to the file representing the read end of the VT pipe.
PtySignalInputThread::PtySignalInputThread(wil::unique_hfile hPipe) :
_hFile{ std::move(hPipe) },
_hThread{},
_api{ ServiceLocator::LocateGlobals().getConsoleInformation() },
_dwThreadId{ 0 },
_consoleConnected{ false }
{
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
}
PtySignalInputThread::~PtySignalInputThread()
{
// Manually terminate our thread during unittesting. Otherwise, the test
// will finish, but TAEF will not manually kill the test.
#ifdef UNIT_TESTING
TerminateThread(_hThread.get(), 0);
#endif
}
// Function Description:
// - Static function used for initializing an instance's ThreadProc.
// Arguments:
// - lpParameter - A pointer to the PtySignalInputTHread instance that should be called.
// Return Value:
// - The return value of the underlying instance's _InputThread
DWORD WINAPI PtySignalInputThread::StaticThreadProc(_In_ LPVOID lpParameter)
{
constauto pInstance = reinterpret_cast<PtySignalInputThread*>(lpParameter);
return pInstance->_InputThread();
}
// Method Description:
// - Tell us that there's a client attached to the console, so we can actually
// do something with the messages we receive now. Before this is set, there
// is no guarantee that a client has attached, so most parts of the console
// (in and screen buffers) haven't yet been initialized.
// - NOTE: Call under LockConsole() to ensure other threads have an opportunity
// to set early-work state.
// - We need to do this specifically on the thread with the message pump. If the
// window is created on another thread, then the window won't have a message
// pump associated with it, and a DPI change in the connected terminal could
// end up HANGING THE CONPTY (for example).
// Arguments:
// - <none>
// Return Value:
// - <none>
voidPtySignalInputThread::ConnectConsole() noexcept
{
_consoleConnected = true;
if (_earlyResize)
{
_DoResizeWindow(*_earlyResize);
}
if (_initialShowHide)
{
_DoShowHide(*_initialShowHide);
}
}
// Method Description:
// - Create our pseudo window. We're doing this here, instead of in
// ConnectConsole, because the window is created in
// ConsoleInputThreadProcWin32, before ConnectConsole is first called. Doing
// this here ensures that the window is first created with the initial owner
// set up (if so specified).
// - Refer to GH#13066 for details.
voidPtySignalInputThread::CreatePseudoWindow()
{
ServiceLocator::LocatePseudoWindow();
}
// Method Description:
// - The ThreadProc for the PTY Signal Input Thread.
// Return Value:
// - S_OK if the thread runs to completion.
// - Otherwise it may cause an application termination and never return.
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread() noexcept
try
{
constauto shutdown = wil::scope_exit([this]() {
_Shutdown();
});
for (;;)
{
PtySignal signalId;
if (!_GetData(&signalId, sizeof(signalId)))
{
return S_OK;
}
switch (signalId)
{
case PtySignal::ShowHideWindow:
{
ShowHideData msg = { 0 };
if (!_GetData(&msg, sizeof(msg)))
{
return S_OK;
}
_DoShowHide(msg);
break;
}
case PtySignal::ClearBuffer:
{
_DoClearBuffer();
break;
}
case PtySignal::ResizeWindow:
{
ResizeWindowData resizeMsg = { 0 };
if (!_GetData(&resizeMsg, sizeof(resizeMsg)))
{
return S_OK;
}
_DoResizeWindow(resizeMsg);
break;
}
case PtySignal::SetParent:
{
SetParentData reparentMessage = { 0 };
if (!_GetData(&reparentMessage, sizeof(reparentMessage)))
{
return S_OK;
}
_DoSetWindowParent(reparentMessage);
break;
}
default:
THROW_HR(E_UNEXPECTED);
}
}
}
CATCH_LOG_RETURN_HR(S_OK)
// Method Description:
// - Dispatches a resize window message to the rest of the console code
// Arguments:
// - data - Packet information containing width/height (size) information
// Return Value:
// - <none>
void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash the new size in the launchArgs.
// We'll later use the value in launchArgs to set up the console buffer
// We must be under lock here to ensure that someone else doesn't come in
// and set with `ConnectConsole` while we're looking and modifying this.
if (!_consoleConnected)
{
_earlyResize = data;
return;
}
_api.ResizeWindow(data.sx, data.sy);
}
voidPtySignalInputThread::_DoClearBuffer() const
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash the new size in the launchArgs.
// We'll later use the value in launchArgs to set up the console buffer
// We must be under lock here to ensure that someone else doesn't come in
// and set with `ConnectConsole` while we're looking and modifying this.
if (!_consoleConnected)
{
return;
}
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& screenInfo = gci.GetActiveOutputBuffer();
auto& stateMachine = screenInfo.GetStateMachine();
stateMachine.ProcessString(L"\x1b[H\x1b[2J");
}
voidPtySignalInputThread::_DoShowHide(const ShowHideData& data)
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// If the client app hasn't yet connected, stash our initial
// visibility for when we do. We default to not being visible - if a
// terminal wants the ConPTY windows to start "visible", then they
// should send a ShowHidePseudoConsole(..., true) to tell us to
// initially be visible.
//
// Notably, if they don't, then a ShowWindow(SW_HIDE) on the ConPTY
// HWND will initially do _nothing_, because the OS will think that
// the window is already hidden.
if (!_consoleConnected)
{
_initialShowHide = data;
return;
}
ServiceLocator::SetPseudoWindowVisibility(data.show);
}
// Method Description:
// - Update the owner of the pseudo-window we're using for the conpty HWND. This
// allows to mark the pseudoconsole windows as "owner" by the terminal HWND
// that's actually hosting them.
// - Refer to GH#2988
// Arguments:
// - data - Packet information containing owner HWND information
// Return Value:
// - <none>
voidPtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
ServiceLocator::SetPseudoWindowOwner(reinterpret_cast<HWND>(data.handle));
}
// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.
// Arguments:
// - pBuffer - Buffer to fill with data.
// - cbBuffer - Count of bytes in the given buffer.
// Return Value:
// - True if data was retrieved successfully. False otherwise.
[[nodiscard]] boolPtySignalInputThread::_GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer)
{
if (!_hFile)
{
returnfalse;
}
DWORD dwRead = 0;
if (FALSE == ReadFile(_hFile.get(), pBuffer, cbBuffer, &dwRead, nullptr))
{
if (constauto err = GetLastError(); err != ERROR_BROKEN_PIPE)
{
LOG_WIN32(err);
}
_hFile.reset();
returnfalse;
}
if (dwRead != cbBuffer)
{
returnfalse;
}
returntrue;
}
// Method Description:
// - Starts the PTY Signal input thread.
[[nodiscard]] HRESULT PtySignalInputThread::Start() noexcept
{
RETURN_LAST_ERROR_IF(!_hFile);
HANDLE hThread = nullptr;
// 0 is the right value, https://blogs.msdn.microsoft.com/oldnewthing/20040223-00/?p=40503
DWORD dwThreadId = 0;
hThread = CreateThread(nullptr,
0,
PtySignalInputThread::StaticThreadProc,
this,
0,
&dwThreadId);
RETURN_LAST_ERROR_IF_NULL(hThread);
_hThread.reset(hThread);
_dwThreadId = dwThreadId;
LOG_IF_FAILED(SetThreadDescription(hThread, L"ConPTY Signal Handler Thread"));
return S_OK;
}
// Method Description:
// - Perform a shutdown of the console. This happens when the signal pipe is
// broken, which means either the parent terminal process has died, or they
// called ClosePseudoConsole.
// Arguments:
// - <none>
// Return Value:
// - <none>
voidPtySignalInputThread::_Shutdown()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetVtIo()->SendCloseEvent();
}