-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Properly implement WaitForData for ReadConsoleInput #18228
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -492,8 +492,7 @@ size_t InputBuffer::Prepend(const std::span<const INPUT_RECORD>& inEvents) | |
return STATUS_SUCCESS; | ||
} | ||
|
||
_vtInputShouldSuppress = true; | ||
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; }); | ||
const auto wakeup = _wakeupReadersOnExit(); | ||
|
||
// read all of the records out of the buffer, then write the | ||
// prepend ones, then write the original set. We need to do it | ||
|
@@ -503,35 +502,15 @@ size_t InputBuffer::Prepend(const std::span<const INPUT_RECORD>& inEvents) | |
std::deque<INPUT_RECORD> existingStorage; | ||
existingStorage.swap(_storage); | ||
|
||
// We will need this variable to pass to _WriteBuffer so it can attempt to determine wait status. | ||
// However, because we swapped the storage out from under it with an empty deque, it will always | ||
// return true after the first one (as it is filling the newly emptied backing deque.) | ||
// Then after the second one, because we've inserted some input, it will always say false. | ||
auto unusedWaitStatus = false; | ||
|
||
// write the prepend records | ||
size_t prependEventsWritten; | ||
_WriteBuffer(inEvents, prependEventsWritten, unusedWaitStatus); | ||
FAIL_FAST_IF(!(unusedWaitStatus)); | ||
_WriteBuffer(inEvents, prependEventsWritten); | ||
|
||
for (const auto& event : existingStorage) | ||
{ | ||
_storage.push_back(event); | ||
} | ||
|
||
// We need to set the wait event if there were 0 events in the | ||
// input queue when we started. | ||
// Because we did interesting manipulation of the wait queue | ||
// in order to prepend, we can't trust what _WriteBuffer said | ||
// and instead need to set the event if the original backing | ||
// buffer (the one we swapped out at the top) was empty | ||
// when this whole thing started. | ||
if (existingStorage.empty()) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
} | ||
WakeUpReadersWaitingForData(); | ||
|
||
return prependEventsWritten; | ||
} | ||
catch (...) | ||
|
@@ -575,21 +554,9 @@ size_t InputBuffer::Write(const std::span<const INPUT_RECORD>& inEvents) | |
return 0; | ||
} | ||
|
||
_vtInputShouldSuppress = true; | ||
auto resetVtInputSuppress = wil::scope_exit([&]() { _vtInputShouldSuppress = false; }); | ||
|
||
// Write to buffer. | ||
const auto wakeup = _wakeupReadersOnExit(); | ||
size_t EventsWritten; | ||
bool SetWaitEvent; | ||
_WriteBuffer(inEvents, EventsWritten, SetWaitEvent); | ||
|
||
if (SetWaitEvent) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
} | ||
|
||
// Alert any writers waiting for space. | ||
WakeUpReadersWaitingForData(); | ||
_WriteBuffer(inEvents, EventsWritten); | ||
return EventsWritten; | ||
} | ||
catch (...) | ||
|
@@ -607,16 +574,8 @@ try | |
return; | ||
} | ||
|
||
const auto initiallyEmptyQueue = _storage.empty(); | ||
|
||
const auto wakeup = _wakeupReadersOnExit(); | ||
_writeString(text); | ||
|
||
if (initiallyEmptyQueue && !_storage.empty()) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
} | ||
|
||
WakeUpReadersWaitingForData(); | ||
} | ||
CATCH_LOG() | ||
|
||
|
@@ -625,29 +584,26 @@ CATCH_LOG() | |
// input buffer and the next application will suddenly get a "\x1b[I" sequence in their input. See GH#13238. | ||
void InputBuffer::WriteFocusEvent(bool focused) noexcept | ||
{ | ||
const auto wakeup = _wakeupReadersOnExit(); | ||
|
||
if (IsInVirtualTerminalInputMode()) | ||
{ | ||
if (const auto out = _termInput.HandleFocus(focused)) | ||
{ | ||
_HandleTerminalInputCallback(*out); | ||
_writeString(*out); | ||
} | ||
} | ||
else | ||
{ | ||
// This is a mini-version of Write(). | ||
const auto wasEmpty = _storage.empty(); | ||
_storage.push_back(SynthesizeFocusEvent(focused)); | ||
if (wasEmpty) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
} | ||
WakeUpReadersWaitingForData(); | ||
} | ||
} | ||
|
||
// Returns true when mouse input started. You should then capture the mouse and produce further events. | ||
bool InputBuffer::WriteMouseEvent(til::point position, const unsigned int button, const short keyState, const short wheelDelta) | ||
{ | ||
const auto wakeup = _wakeupReadersOnExit(); | ||
|
||
if (IsInVirtualTerminalInputMode()) | ||
{ | ||
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx | ||
|
@@ -669,7 +625,7 @@ bool InputBuffer::WriteMouseEvent(til::point position, const unsigned int button | |
|
||
if (const auto out = _termInput.HandleMouse(position, button, keyState, wheelDelta, state)) | ||
{ | ||
_HandleTerminalInputCallback(*out); | ||
_writeString(*out); | ||
return true; | ||
} | ||
} | ||
|
@@ -690,6 +646,22 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event) | |
return ctrlButNotAlt && event.wVirtualKeyCode == L'S'; | ||
} | ||
|
||
void InputBuffer::_wakeupReadersImpl(bool initiallyEmpty) | ||
{ | ||
if (!_storage.empty()) | ||
{ | ||
// It would be fine to call SetEvent() unconditionally, | ||
// but technically we only need to ResetEvent() if the buffer is empty, | ||
// and SetEvent() once it stopped being so, which is what this code does. | ||
if (initiallyEmpty) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
} | ||
|
||
WakeUpReadersWaitingForData(); | ||
} | ||
} | ||
|
||
// Routine Description: | ||
// - Coalesces input events and transfers them to storage queue. | ||
// Arguments: | ||
|
@@ -702,13 +674,11 @@ static bool IsPauseKey(const KEY_EVENT_RECORD& event) | |
// Note: | ||
// - The console lock must be held when calling this routine. | ||
// - will throw on failure | ||
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten, _Out_ bool& setWaitEvent) | ||
void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _Out_ size_t& eventsWritten) | ||
{ | ||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); | ||
|
||
eventsWritten = 0; | ||
setWaitEvent = false; | ||
const auto initiallyEmptyQueue = _storage.empty(); | ||
const auto initialInEventsSize = inEvents.size(); | ||
const auto vtInputMode = IsInVirtualTerminalInputMode(); | ||
|
||
|
@@ -739,7 +709,7 @@ void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _O | |
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly | ||
if (const auto out = _termInput.HandleKey(inEvent)) | ||
{ | ||
_HandleTerminalInputCallback(*out); | ||
_writeString(*out); | ||
eventsWritten++; | ||
continue; | ||
} | ||
|
@@ -759,10 +729,6 @@ void InputBuffer::_WriteBuffer(const std::span<const INPUT_RECORD>& inEvents, _O | |
_storage.push_back(inEvent); | ||
++eventsWritten; | ||
} | ||
if (initiallyEmptyQueue && !_storage.empty()) | ||
{ | ||
setWaitEvent = true; | ||
} | ||
} | ||
|
||
// Routine Description:: | ||
|
@@ -826,36 +792,6 @@ bool InputBuffer::IsInVirtualTerminalInputMode() const | |
return WI_IsFlagSet(InputMode, ENABLE_VIRTUAL_TERMINAL_INPUT); | ||
} | ||
|
||
// Routine Description: | ||
// - Handler for inserting key sequences into the buffer when the terminal emulation layer | ||
// has determined a key can be converted appropriately into a sequence of inputs | ||
// Arguments: | ||
// - inEvents - Series of input records to insert into the buffer | ||
// Return Value: | ||
// - <none> | ||
void InputBuffer::_HandleTerminalInputCallback(const TerminalInput::StringType& text) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second fix. It's also redundant with |
||
{ | ||
try | ||
{ | ||
if (text.empty()) | ||
{ | ||
return; | ||
} | ||
|
||
_writeString(text); | ||
|
||
if (!_vtInputShouldSuppress) | ||
{ | ||
ServiceLocator::LocateGlobals().hInputEvent.SetEvent(); | ||
WakeUpReadersWaitingForData(); | ||
} | ||
} | ||
catch (...) | ||
{ | ||
LOG_HR(wil::ResultFromCaughtException()); | ||
} | ||
} | ||
|
||
void InputBuffer::_writeString(const std::wstring_view& text) | ||
{ | ||
for (const auto& wch : text) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,7 +107,7 @@ try | |
*pReplyStatus = _pInputBuffer->Read(_outEvents, | ||
amountToRead, | ||
false, | ||
false, | ||
true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first fix. |
||
fIsUnicode, | ||
false); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,6 +182,7 @@ constexpr T saturate(auto val) | |
*pInputReadHandleData, | ||
a->Unicode, | ||
fIsPeek, | ||
fIsWaitAllowed, | ||
waiter); | ||
|
||
// We must return the number of records in the message payload (to alert the client) | ||
|
@@ -191,30 +192,13 @@ constexpr T saturate(auto val) | |
size_t cbWritten; | ||
LOG_IF_FAILED(SizeTMult(outEvents.size(), sizeof(INPUT_RECORD), &cbWritten)); | ||
|
||
if (nullptr != waiter.get()) | ||
if (waiter) | ||
{ | ||
// In some circumstances, the read may have told us to wait because it didn't have data, | ||
// but the client explicitly asked us to return immediate. In that case, we'll convert the | ||
// wait request into a "0 bytes found, OK". | ||
|
||
if (fIsWaitAllowed) | ||
{ | ||
hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); | ||
if (SUCCEEDED(hr)) | ||
{ | ||
*pbReplyPending = TRUE; | ||
hr = CONSOLE_STATUS_WAIT; | ||
} | ||
} | ||
Comment on lines
-200
to
-208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes no sense IMO to check this here if we can just pass down |
||
else | ||
hr = ConsoleWaitQueue::s_CreateWait(m, waiter.release()); | ||
if (SUCCEEDED(hr)) | ||
{ | ||
// If wait isn't allowed and the routine generated a | ||
// waiter, say there was nothing to be | ||
// retrieved right now. | ||
// The waiter will be auto-freed in the smart pointer. | ||
|
||
cbWritten = 0; | ||
hr = S_OK; | ||
*pbReplyPending = TRUE; | ||
hr = CONSOLE_STATUS_WAIT; | ||
} | ||
} | ||
else | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much cleaner and safer. Also, it's more correct. We would previously call
WakeUpReadersWaitingForData
unconditionally but the storage could still be empty. This error has probably been in conhost since v1.