This commit introduces a small console-subsystem application whose sole job is to consume TerminalConnection.dll and hook it up to something other than Terminal. It is 99% of the way to a generic solution. I've introduced a stopgap in TerminalPage that makes sure we launch TerminalAzBridge using ConptyConnection instead of AzureConnection. As a bonus, this commit includes a class whose sole job it is to make reading VT input off a console handle not terrible. It returns you a string and dispatches window size change callbacks. Fixes #2267. Fixes #4589. Related to #2266 (since pwsh needs better VT).
88 lines
3.4 KiB
C++
88 lines
3.4 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "ConsoleInputReader.h"
|
|
#include "unicode.hpp"
|
|
|
|
ConsoleInputReader::ConsoleInputReader(HANDLE handle) :
|
|
_handle(handle)
|
|
{
|
|
_buffer.resize(BufferSize);
|
|
_convertedString.reserve(BufferSize);
|
|
}
|
|
|
|
void ConsoleInputReader::SetWindowSizeChangedCallback(std::function<void()> callback)
|
|
{
|
|
_windowSizeChangedCallback = std::move(callback);
|
|
}
|
|
|
|
std::optional<std::wstring_view> ConsoleInputReader::Read()
|
|
{
|
|
DWORD readCount{ 0 };
|
|
|
|
_convertedString.clear();
|
|
while (_convertedString.empty())
|
|
{
|
|
_buffer.resize(BufferSize);
|
|
BOOL succeeded =
|
|
ReadConsoleInputW(_handle, _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), &readCount);
|
|
if (!succeeded)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
_buffer.resize(readCount);
|
|
for (auto it = _buffer.begin(); it != _buffer.end(); ++it)
|
|
{
|
|
if (it->EventType == WINDOW_BUFFER_SIZE_EVENT && _windowSizeChangedCallback)
|
|
{
|
|
_windowSizeChangedCallback();
|
|
}
|
|
else if (it->EventType == KEY_EVENT)
|
|
{
|
|
const auto& keyEvent = it->Event.KeyEvent;
|
|
if (keyEvent.bKeyDown || (!keyEvent.bKeyDown && keyEvent.wVirtualKeyCode == VK_MENU))
|
|
{
|
|
// Got a high surrogate at the end of the buffer
|
|
if (IS_HIGH_SURROGATE(keyEvent.uChar.UnicodeChar))
|
|
{
|
|
_highSurrogate.emplace(keyEvent.uChar.UnicodeChar);
|
|
continue; // we've consumed it -- only dispatch it if we get a low
|
|
}
|
|
|
|
if (IS_LOW_SURROGATE(keyEvent.uChar.UnicodeChar))
|
|
{
|
|
// No matter what we do, we want to destructively consume the high surrogate
|
|
if (const auto oldHighSurrogate{ std::exchange(_highSurrogate, std::nullopt) })
|
|
{
|
|
_convertedString.push_back(*_highSurrogate);
|
|
}
|
|
else
|
|
{
|
|
// If we get a low without a high surrogate, we've done everything we can.
|
|
// This is an illegal state.
|
|
_convertedString.push_back(UNICODE_REPLACEMENT);
|
|
continue; // onto the next event
|
|
}
|
|
}
|
|
|
|
// (\0 with a scancode is probably a modifier key, not a VT input key)
|
|
if (keyEvent.uChar.UnicodeChar != L'\0' || keyEvent.wVirtualScanCode == 0)
|
|
{
|
|
if (_highSurrogate) // non-destructive: we don't want to set it to nullopt needlessly for every character
|
|
{
|
|
// If we get a high surrogate *here*, we didn't find a low surrogate.
|
|
// This state is also illegal.
|
|
_convertedString.push_back(UNICODE_REPLACEMENT);
|
|
_highSurrogate.reset();
|
|
}
|
|
_convertedString.push_back(keyEvent.uChar.UnicodeChar);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return _convertedString;
|
|
}
|