Skip to content

Commit c55aca5

Browse files
authored
ConPTY: Emit DSR CPR on resize (#19089)
This will help terminals with a reflow behavior unlike the one implemented in ConPTY, such as VS Code. Closes #18725 ## Validation Steps Performed * Tested under a debugger ✅ * Use PowerShell 5 and reflow lines in the ConPTY buffer until they're pushed outside the top viewport. Then type something in the prompt. Cursor is in a consistent position, even if slightly off. ✅
1 parent 666a75b commit c55aca5

File tree

8 files changed

+93
-68
lines changed

8 files changed

+93
-68
lines changed

src/host/PtySignalInputThread.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,9 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
184184
}
185185

186186
_api.ResizeWindow(data.sx, data.sy);
187+
188+
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
189+
gci.GetVtIo()->RequestCursorPositionFromTerminal();
187190
}
188191

189192
void PtySignalInputThread::_DoClearBuffer(const bool keepCursorRow) const

src/host/VtInputThread.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ using namespace Microsoft::Console::VirtualTerminal;
2121
// - hPipe - a handle to the file representing the read end of the VT pipe.
2222
// - inheritCursor - a bool indicating if the state machine should expect a
2323
// cursor positioning sequence. See MSFT:15681311.
24-
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor) :
24+
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR) :
2525
_hFile{ std::move(hPipe) }
2626
{
2727
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
2828

2929
auto dispatch = std::make_unique<InteractDispatch>();
30-
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), inheritCursor);
30+
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), std::move(capturedCPR));
3131
_pInputStateMachine = std::make_unique<StateMachine>(std::move(engine));
3232
}
3333

@@ -185,8 +185,7 @@ void VtInputThread::_InputThread()
185185
return S_OK;
186186
}
187187

188-
til::enumset<DeviceAttribute, uint64_t> VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept
188+
InputStateMachineEngine& VtInputThread::GetInputStateMachineEngine() const noexcept
189189
{
190-
const auto& engine = static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
191-
return engine.WaitUntilDA1(timeout);
190+
return static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
192191
}

src/host/VtInputThread.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ namespace Microsoft::Console
2020
{
2121
namespace VirtualTerminal
2222
{
23+
class InputStateMachineEngine;
2324
enum class DeviceAttribute : uint64_t;
2425
}
2526

2627
class VtInputThread
2728
{
2829
public:
29-
VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor);
30+
VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR = nullptr);
3031

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

3435
private:
3536
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);

src/host/VtIo.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "output.h" // CloseConsoleProcessState
1212
#include "../interactivity/inc/ServiceLocator.hpp"
1313
#include "../renderer/base/renderer.hpp"
14+
#include "../terminal/parser/InputStateMachineEngine.hpp"
1415
#include "../types/inc/CodepointWidthDetector.hpp"
1516
#include "../types/inc/utils.hpp"
1617

@@ -155,7 +156,9 @@ bool VtIo::IsUsingVt() const
155156
{
156157
if (IsValidHandle(_hInput.get()))
157158
{
158-
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
159+
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), [this]() {
160+
_cursorPositionReportReceived();
161+
});
159162
}
160163
}
161164
CATCH_RETURN();
@@ -177,6 +180,7 @@ bool VtIo::IsUsingVt() const
177180
// wait for the DA1 response below and effectively wait for both.
178181
if (_lookingForCursorPosition)
179182
{
183+
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
180184
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
181185
}
182186

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

@@ -226,6 +230,35 @@ bool VtIo::IsUsingVt() const
226230
return S_OK;
227231
}
228232

233+
void VtIo::RequestCursorPositionFromTerminal()
234+
{
235+
if (_lookingForCursorPosition)
236+
{
237+
// By delaying sending another DSR CPR until we received a response to the previous one,
238+
// we debounce our requests to the terminal. We don't want to flood it unnecessarily.
239+
_scheduleAnotherCPR = true;
240+
return;
241+
}
242+
243+
_lookingForCursorPosition = true;
244+
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
245+
246+
Writer writer{ this };
247+
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
248+
writer.Submit();
249+
}
250+
251+
void VtIo::_cursorPositionReportReceived()
252+
{
253+
_lookingForCursorPosition = false;
254+
255+
if (_scheduleAnotherCPR)
256+
{
257+
_scheduleAnotherCPR = false;
258+
RequestCursorPositionFromTerminal();
259+
}
260+
}
261+
229262
void VtIo::SetDeviceAttributes(const til::enumset<DeviceAttribute, uint64_t> attributes) noexcept
230263
{
231264
_deviceAttributes = attributes;

src/host/VtIo.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ namespace Microsoft::Console::VirtualTerminal
6161
bool IsUsingVt() const;
6262
[[nodiscard]] HRESULT StartIfNeeded();
6363

64+
void RequestCursorPositionFromTerminal();
6465
void SetDeviceAttributes(til::enumset<DeviceAttribute, uint64_t> attributes) noexcept;
6566
til::enumset<DeviceAttribute, uint64_t> GetDeviceAttributes() const noexcept;
6667
void SendCloseEvent();
@@ -77,7 +78,7 @@ namespace Microsoft::Console::VirtualTerminal
7778
};
7879

7980
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
80-
81+
void _cursorPositionReportReceived();
8182
void _uncork();
8283
void _flushNow();
8384

@@ -105,6 +106,7 @@ namespace Microsoft::Console::VirtualTerminal
105106

106107
State _state = State::Uninitialized;
107108
bool _lookingForCursorPosition = false;
109+
bool _scheduleAnotherCPR = false;
108110
bool _closeEventSent = false;
109111
int _corked = 0;
110112

src/terminal/parser/InputStateMachineEngine.cpp

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,24 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep
8989
return pair.action == code;
9090
}
9191

92-
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
93-
_pDispatch(std::move(pDispatch)),
94-
_lookingForDSR(lookingForDSR),
95-
_doubleClickTime(std::chrono::milliseconds(GetDoubleClickTime()))
92+
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR) :
93+
_pDispatch{ std::move(pDispatch) },
94+
_capturedCPR{ std::move(capturedCPR) },
95+
_doubleClickTime{ std::chrono::milliseconds(GetDoubleClickTime()) }
9696
{
9797
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
9898
}
9999

100+
IInteractDispatch& InputStateMachineEngine::GetDispatch() const noexcept
101+
{
102+
return *_pDispatch.get();
103+
}
104+
105+
void InputStateMachineEngine::CaptureNextCPR() noexcept
106+
{
107+
_lookingForCPR = true;
108+
}
109+
100110
til::enumset<DeviceAttribute, uint64_t> InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept
101111
{
102112
uint64_t val = 0;
@@ -413,13 +423,19 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
413423
// The F3 case is special - it shares a code with the DeviceStatusResponse.
414424
// If we're looking for that response, then do that, and break out.
415425
// Else, fall though to the _GetCursorKeysModifierState handler.
416-
if (_lookingForDSR)
426+
if (_lookingForCPR)
417427
{
418-
_pDispatch->MoveCursor(parameters.at(0), parameters.at(1));
419-
// Right now we're only looking for on initial cursor
420-
// position response. After that, only look for F3.
421-
_lookingForDSR = false;
422-
return true;
428+
_lookingForCPR = false;
429+
_capturedCPR();
430+
431+
const auto y = parameters.at(0).value();
432+
const auto x = parameters.at(1).value();
433+
434+
if (y > 0 && x > 0)
435+
{
436+
_pDispatch->MoveCursor(y, x);
437+
return true;
438+
}
423439
}
424440
// Heuristic: If the hosting terminal used the win32 input mode, chances are high
425441
// that this is a CPR requested by the terminal application as opposed to a F3 key.
@@ -491,10 +507,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
491507

492508
_deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed);
493509
til::atomic_notify_all(_deviceAttributes);
494-
495-
// VtIo first sends a DSR CPR and then a DA1 request.
496-
// If we encountered a DA1 response here, the DSR request is definitely done now.
497-
_lookingForDSR = false;
498510
return true;
499511
}
500512
return false;

src/terminal/parser/InputStateMachineEngine.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,10 @@ namespace Microsoft::Console::VirtualTerminal
159159
class InputStateMachineEngine : public IStateMachineEngine
160160
{
161161
public:
162-
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR = false);
162+
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR = nullptr);
163163

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

166168
bool EncounteredWin32InputModeSequence() const noexcept override;
@@ -189,7 +191,8 @@ namespace Microsoft::Console::VirtualTerminal
189191
private:
190192
const std::unique_ptr<IInteractDispatch> _pDispatch;
191193
std::atomic<uint64_t> _deviceAttributes{ 0 };
192-
bool _lookingForDSR = false;
194+
bool _lookingForCPR = false;
195+
std::function<void()> _capturedCPR;
193196
bool _encounteredWin32InputModeSequence = false;
194197
bool _expectingStringTerminator = false;
195198
DWORD _mouseButtonState = 0;

0 commit comments

Comments
 (0)