@@ -79,7 +79,11 @@ using namespace Microsoft::Console::Interactivity;
79
79
const HANDLE OutHandle,
80
80
_In_opt_ const HANDLE SignalHandle)
81
81
{
82
- FAIL_FAST_IF_MSG (_initialized, " Someone attempted to double-_Initialize VtIo" );
82
+ if (_state != State::Uninitialized)
83
+ {
84
+ assert (false ); // Don't call initialize twice.
85
+ return E_UNEXPECTED;
86
+ }
83
87
84
88
_hInput.reset (InHandle);
85
89
_hOutput.reset (OutHandle);
@@ -95,47 +99,33 @@ using namespace Microsoft::Console::Interactivity;
95
99
}
96
100
}
97
101
98
- // The only way we're initialized is if the args said we're in conpty mode.
99
- // If the args say so, then at least one of in, out, or signal was specified
100
- _initialized = true ;
101
- return S_OK;
102
- }
103
-
104
- // Method Description:
105
- // - Create the VtEngine and the VtInputThread for this console.
106
- // MUST BE DONE AFTER CONSOLE IS INITIALIZED, to make sure we've gotten the
107
- // buffer size from the attached client application.
108
- // Arguments:
109
- // - <none>
110
- // Return Value:
111
- // S_OK if we initialized successfully,
112
- // S_FALSE if VtIo hasn't been initialized (or we're not in conpty mode)
113
- // otherwise an appropriate HRESULT indicating failure.
114
- [[nodiscard]] HRESULT VtIo::CreateIoHandlers () noexcept
115
- {
116
- if (!_initialized)
117
- {
118
- return S_FALSE;
119
- }
120
-
121
- // SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine.
122
- assert (ServiceLocator::LocateGlobals ().getConsoleInformation ().IsConsoleLocked ());
123
-
124
- try
102
+ // - Create and start the signal thread. The signal thread can be created
103
+ // independent of the i/o threads, and doesn't require a client first
104
+ // attaching to the console. We need to create it first and foremost,
105
+ // because it's possible that a terminal application could
106
+ // CreatePseudoConsole, then ClosePseudoConsole without ever attaching a
107
+ // client. Should that happen, we still need to exit.
108
+ if (IsValidHandle (_hSignal.get ()))
125
109
{
126
- if ( IsValidHandle (_hInput. get ()))
110
+ try
127
111
{
128
- _pVtInputThread = std::make_unique<VtInputThread>(std::move (_hInput), _lookingForCursorPosition);
112
+ _pPtySignalInputThread = std::make_unique<PtySignalInputThread>(std::move (_hSignal));
113
+
114
+ // Start it if it was successfully created.
115
+ RETURN_IF_FAILED (_pPtySignalInputThread->Start ());
129
116
}
117
+ CATCH_RETURN ();
130
118
}
131
- CATCH_RETURN ();
132
119
120
+ // The only way we're initialized is if the args said we're in conpty mode.
121
+ // If the args say so, then at least one of in, out, or signal was specified
122
+ _state = State::Initialized;
133
123
return S_OK;
134
124
}
135
125
136
126
bool VtIo::IsUsingVt () const
137
127
{
138
- return _initialized ;
128
+ return _state != State::Uninitialized ;
139
129
}
140
130
141
131
// Routine Description:
@@ -151,50 +141,64 @@ bool VtIo::IsUsingVt() const
151
141
[[nodiscard]] HRESULT VtIo::StartIfNeeded ()
152
142
{
153
143
// If we haven't been set up, do nothing (because there's nothing to start)
154
- if (!_initialized )
144
+ if (_state != State::Initialized )
155
145
{
156
146
return S_FALSE;
157
147
}
158
148
159
- if (_pVtInputThread)
149
+ _state = State::Starting;
150
+
151
+ // SetWindowVisibility uses the console lock to protect access to _pVtRenderEngine.
152
+ assert (ServiceLocator::LocateGlobals ().getConsoleInformation ().IsConsoleLocked ());
153
+
154
+ try
160
155
{
161
- LOG_IF_FAILED (_pVtInputThread->Start ());
156
+ if (IsValidHandle (_hInput.get ()))
157
+ {
158
+ _pVtInputThread = std::make_unique<VtInputThread>(std::move (_hInput), _lookingForCursorPosition);
159
+ }
162
160
}
161
+ CATCH_RETURN ();
163
162
163
+ if (_pVtInputThread)
164
164
{
165
- Writer writer{ this } ;
165
+ LOG_IF_FAILED (_pVtInputThread-> Start ()) ;
166
166
167
- // MSFT: 15813316
168
- // If the terminal application wants us to inherit the cursor position,
169
- // we're going to emit a VT sequence to ask for the cursor position.
170
- // If we get a response, the InteractDispatch will call SetCursorPosition,
171
- // which will call to our VtIo::SetCursorPosition method.
172
- //
173
- // By sending the request before sending the DA1 one, we can simply
174
- // wait for the DA1 response below and effectively wait for both.
175
- if (_lookingForCursorPosition)
176
167
{
177
- writer.WriteUTF8 (" \x1b [6n" ); // Cursor Position Report (DSR CPR)
178
- }
179
-
180
- // GH#4999 - Send a sequence to the connected terminal to request
181
- // win32-input-mode from them. This will enable the connected terminal to
182
- // send us full INPUT_RECORDs as input. If the terminal doesn't understand
183
- // this sequence, it'll just ignore it.
184
- writer.WriteUTF8 (
185
- " \x1b [c" // DA1 Report (Primary Device Attributes)
186
- " \x1b [?1004h" // Focus Event Mode
187
- " \x1b [?9001h" // Win32 Input Mode
188
- );
168
+ Writer writer{ this };
169
+
170
+ // MSFT: 15813316
171
+ // If the terminal application wants us to inherit the cursor position,
172
+ // we're going to emit a VT sequence to ask for the cursor position.
173
+ // If we get a response, the InteractDispatch will call SetCursorPosition,
174
+ // which will call to our VtIo::SetCursorPosition method.
175
+ //
176
+ // By sending the request before sending the DA1 one, we can simply
177
+ // wait for the DA1 response below and effectively wait for both.
178
+ if (_lookingForCursorPosition)
179
+ {
180
+ writer.WriteUTF8 (" \x1b [6n" ); // Cursor Position Report (DSR CPR)
181
+ }
189
182
190
- writer.Submit ();
191
- }
183
+ // GH#4999 - Send a sequence to the connected terminal to request
184
+ // win32-input-mode from them. This will enable the connected terminal to
185
+ // send us full INPUT_RECORDs as input. If the terminal doesn't understand
186
+ // this sequence, it'll just ignore it.
187
+ writer.WriteUTF8 (
188
+ " \x1b [c" // DA1 Report (Primary Device Attributes)
189
+ " \x1b [?1004h" // Focus Event Mode
190
+ " \x1b [?9001h" // Win32 Input Mode
191
+ );
192
+
193
+ writer.Submit ();
194
+ }
192
195
193
- {
194
- // Allow the input thread to momentarily gain the console lock.
195
- auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
196
- const auto suspension = gci.SuspendLock ();
197
- _deviceAttributes = _pVtInputThread->WaitUntilDA1 (3000 );
196
+ {
197
+ // Allow the input thread to momentarily gain the console lock.
198
+ auto & gci = ServiceLocator::LocateGlobals ().getConsoleInformation ();
199
+ const auto suspension = gci.SuspendLock ();
200
+ _deviceAttributes = _pVtInputThread->WaitUntilDA1 (3000 );
201
+ }
198
202
}
199
203
200
204
if (_pPtySignalInputThread)
@@ -208,6 +212,17 @@ bool VtIo::IsUsingVt() const
208
212
_pPtySignalInputThread->ConnectConsole ();
209
213
}
210
214
215
+ if (_state != State::Starting)
216
+ {
217
+ // Here's where we _could_ call CloseConsoleProcessState(), but this function
218
+ // only gets called once when the first client connects and CONSOLE_INITIALIZED
219
+ // is not set yet. The process list may already contain that first client,
220
+ // but since it hasn't finished connecting yet, it won't react to a CTRL_CLOSE_EVENT.
221
+ // Instead, we return an error here which will abort the connection setup.
222
+ return E_FAIL;
223
+ }
224
+
225
+ _state = State::Running;
211
226
return S_OK;
212
227
}
213
228
@@ -244,47 +259,21 @@ void VtIo::CreatePseudoWindow()
244
259
}
245
260
}
246
261
247
- // Method Description:
248
- // - Create and start the signal thread. The signal thread can be created
249
- // independent of the i/o threads, and doesn't require a client first
250
- // attaching to the console. We need to create it first and foremost,
251
- // because it's possible that a terminal application could
252
- // CreatePseudoConsole, then ClosePseudoConsole without ever attaching a
253
- // client. Should that happen, we still need to exit.
254
- // Arguments:
255
- // - <none>
256
- // Return Value:
257
- // - S_FALSE if we're not in VtIo mode,
258
- // S_OK if we succeeded,
259
- // otherwise an appropriate HRESULT indicating failure.
260
- [[nodiscard]] HRESULT VtIo::CreateAndStartSignalThread () noexcept
261
- {
262
- if (!_initialized)
263
- {
264
- return S_FALSE;
265
- }
266
-
267
- // If we were passed a signal handle, try to open it and make a signal reading thread.
268
- if (IsValidHandle (_hSignal.get ()))
269
- {
270
- try
271
- {
272
- _pPtySignalInputThread = std::make_unique<PtySignalInputThread>(std::move (_hSignal));
273
-
274
- // Start it if it was successfully created.
275
- RETURN_IF_FAILED (_pPtySignalInputThread->Start ());
276
- }
277
- CATCH_RETURN ();
278
- }
279
-
280
- return S_OK;
281
- }
282
-
283
262
void VtIo::SendCloseEvent ()
284
263
{
285
264
LockConsole ();
286
265
const auto unlock = wil::scope_exit ([] { UnlockConsole (); });
287
266
267
+ // If we're still in the process of starting up, and we're asked to shut down
268
+ // (broken pipe), `VtIo::StartIfNeeded()` will handle the cleanup for us.
269
+ // This can happen during the call to `WaitUntilDA1`, because we relinquish
270
+ // ownership of the console lock.
271
+ if (_state == State::Starting)
272
+ {
273
+ _state = State::StartupFailed;
274
+ return ;
275
+ }
276
+
288
277
// This function is called when the ConPTY signal pipe is closed (PtySignalInputThread) and when the input
289
278
// pipe is closed (VtIo). Usually these two happen at about the same time. This if condition is a bit of
290
279
// a premature optimization and prevents us from sending out a CTRL_CLOSE_EVENT right after another.
0 commit comments