Skip to content

ConPTY: Emit DSR CPR on resize #19089

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/host/PtySignalInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
}

_api.ResizeWindow(data.sx, data.sy);

auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
gci.GetVtIo()->RequestCursorPositionFromTerminal();
}

void PtySignalInputThread::_DoClearBuffer(const bool keepCursorRow) const
Expand Down
9 changes: 4 additions & 5 deletions src/host/VtInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ using namespace Microsoft::Console::VirtualTerminal;
// - hPipe - a handle to the file representing the read end of the VT pipe.
// - inheritCursor - a bool indicating if the state machine should expect a
// cursor positioning sequence. See MSFT:15681311.
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor) :
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR) :
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK if this approach is ideal.

_hFile{ std::move(hPipe) }
{
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);

auto dispatch = std::make_unique<InteractDispatch>();
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), inheritCursor);
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), std::move(capturedCPR));
_pInputStateMachine = std::make_unique<StateMachine>(std::move(engine));
}

Expand Down Expand Up @@ -185,8 +185,7 @@ void VtInputThread::_InputThread()
return S_OK;
}

til::enumset<DeviceAttribute, uint64_t> VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept
InputStateMachineEngine& VtInputThread::GetInputStateMachineEngine() const noexcept
{
const auto& engine = static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
return engine.WaitUntilDA1(timeout);
return static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
}
5 changes: 3 additions & 2 deletions src/host/VtInputThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ namespace Microsoft::Console
{
namespace VirtualTerminal
{
class InputStateMachineEngine;
enum class DeviceAttribute : uint64_t;
}

class VtInputThread
{
public:
VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor);
VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR = nullptr);

[[nodiscard]] HRESULT Start();
til::enumset<VirtualTerminal::DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;
VirtualTerminal::InputStateMachineEngine& GetInputStateMachineEngine() const noexcept;

private:
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);
Expand Down
36 changes: 34 additions & 2 deletions src/host/VtIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "output.h" // CloseConsoleProcessState
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../renderer/base/renderer.hpp"
#include "../terminal/parser/InputStateMachineEngine.hpp"
#include "../types/inc/CodepointWidthDetector.hpp"
#include "../types/inc/utils.hpp"

Expand Down Expand Up @@ -155,7 +156,9 @@ bool VtIo::IsUsingVt() const
{
if (IsValidHandle(_hInput.get()))
{
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), [this]() {
_cursorPositionReportReceived();
});
}
}
CATCH_RETURN();
Expand All @@ -177,6 +180,7 @@ bool VtIo::IsUsingVt() const
// wait for the DA1 response below and effectively wait for both.
if (_lookingForCursorPosition)
{
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
}

Expand All @@ -197,7 +201,7 @@ bool VtIo::IsUsingVt() const
// Allow the input thread to momentarily gain the console lock.
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
const auto suspension = gci.SuspendLock();
_deviceAttributes = _pVtInputThread->WaitUntilDA1(3000);
_deviceAttributes = _pVtInputThread->GetInputStateMachineEngine().WaitUntilDA1(3000);
}
}

Expand Down Expand Up @@ -226,6 +230,34 @@ bool VtIo::IsUsingVt() const
return S_OK;
}

void VtIo::RequestCursorPositionFromTerminal()
{
if (!_lookingForCursorPosition)
{
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
Writer writer{ this };
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
writer.Submit();
}
else
{
// By delaying sending another DSR CPR until we received a response to the previous one,
// we debounce our requests to the terminal. We don't want to flood it unnecessarily.
_scheduleAnotherCPR = true;
}
}

void VtIo::_cursorPositionReportReceived()
{
_lookingForCursorPosition = false;

if (_scheduleAnotherCPR)
{
_scheduleAnotherCPR = false;
RequestCursorPositionFromTerminal();
}
}

void VtIo::SetDeviceAttributes(const til::enumset<DeviceAttribute, uint64_t> attributes) noexcept
{
_deviceAttributes = attributes;
Expand Down
4 changes: 3 additions & 1 deletion src/host/VtIo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace Microsoft::Console::VirtualTerminal
bool IsUsingVt() const;
[[nodiscard]] HRESULT StartIfNeeded();

void RequestCursorPositionFromTerminal();
void SetDeviceAttributes(til::enumset<DeviceAttribute, uint64_t> attributes) noexcept;
til::enumset<DeviceAttribute, uint64_t> GetDeviceAttributes() const noexcept;
void SendCloseEvent();
Expand All @@ -77,7 +78,7 @@ namespace Microsoft::Console::VirtualTerminal
};

[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);

void _cursorPositionReportReceived();
void _uncork();
void _flushNow();

Expand Down Expand Up @@ -105,6 +106,7 @@ namespace Microsoft::Console::VirtualTerminal

State _state = State::Uninitialized;
bool _lookingForCursorPosition = false;
bool _scheduleAnotherCPR = false;
bool _closeEventSent = false;
int _corked = 0;

Expand Down
40 changes: 26 additions & 14 deletions src/terminal/parser/InputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,24 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep
return pair.action == code;
}

InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
_pDispatch(std::move(pDispatch)),
_lookingForDSR(lookingForDSR),
_doubleClickTime(std::chrono::milliseconds(GetDoubleClickTime()))
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR) :
_pDispatch{ std::move(pDispatch) },
_capturedCPR{ std::move(capturedCPR) },
_doubleClickTime{ std::chrono::milliseconds(GetDoubleClickTime()) }
{
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
}

IInteractDispatch& InputStateMachineEngine::GetDispatch() const noexcept
{
return *_pDispatch.get();
}

void InputStateMachineEngine::CaptureNextCPR() noexcept
{
_lookingForCPR = true;
}

til::enumset<DeviceAttribute, uint64_t> InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept
{
uint64_t val = 0;
Expand Down Expand Up @@ -413,13 +423,19 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
// The F3 case is special - it shares a code with the DeviceStatusResponse.
// If we're looking for that response, then do that, and break out.
// Else, fall though to the _GetCursorKeysModifierState handler.
if (_lookingForDSR)
if (_lookingForCPR)
{
_pDispatch->MoveCursor(parameters.at(0), parameters.at(1));
// Right now we're only looking for on initial cursor
// position response. After that, only look for F3.
_lookingForDSR = false;
return true;
_lookingForCPR = false;
_capturedCPR();

const auto y = parameters.at(0).value();
const auto x = parameters.at(1).value();

if (y > 0 && x > 0)
{
_pDispatch->MoveCursor(y, x);
return true;
}
}
// Heuristic: If the hosting terminal used the win32 input mode, chances are high
// that this is a CPR requested by the terminal application as opposed to a F3 key.
Expand Down Expand Up @@ -491,10 +507,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter

_deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed);
til::atomic_notify_all(_deviceAttributes);

// VtIo first sends a DSR CPR and then a DA1 request.
// If we encountered a DA1 response here, the DSR request is definitely done now.
_lookingForDSR = false;
return true;
}
return false;
Expand Down
7 changes: 5 additions & 2 deletions src/terminal/parser/InputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,10 @@ namespace Microsoft::Console::VirtualTerminal
class InputStateMachineEngine : public IStateMachineEngine
{
public:
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR = false);
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR = nullptr);

IInteractDispatch& GetDispatch() const noexcept;
void CaptureNextCPR() noexcept;
til::enumset<DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;

bool EncounteredWin32InputModeSequence() const noexcept override;
Expand Down Expand Up @@ -189,7 +191,8 @@ namespace Microsoft::Console::VirtualTerminal
private:
const std::unique_ptr<IInteractDispatch> _pDispatch;
std::atomic<uint64_t> _deviceAttributes{ 0 };
bool _lookingForDSR = false;
bool _lookingForCPR = false;
std::function<void()> _capturedCPR;
bool _encounteredWin32InputModeSequence = false;
bool _expectingStringTerminator = false;
DWORD _mouseButtonState = 0;
Expand Down
Loading
Loading