diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp index 0b03612d9a..b078c5f1b9 100644 --- a/src/host/PtySignalInputThread.cpp +++ b/src/host/PtySignalInputThread.cpp @@ -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 diff --git a/src/host/VtInputThread.cpp b/src/host/VtInputThread.cpp index 3c359a3c8e..f094eae35e 100644 --- a/src/host/VtInputThread.cpp +++ b/src/host/VtInputThread.cpp @@ -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 capturedCPR) : _hFile{ std::move(hPipe) } { THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE); auto dispatch = std::make_unique(); - auto engine = std::make_unique(std::move(dispatch), inheritCursor); + auto engine = std::make_unique(std::move(dispatch), std::move(capturedCPR)); _pInputStateMachine = std::make_unique(std::move(engine)); } @@ -185,8 +185,7 @@ void VtInputThread::_InputThread() return S_OK; } -til::enumset VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept +InputStateMachineEngine& VtInputThread::GetInputStateMachineEngine() const noexcept { - const auto& engine = static_cast(_pInputStateMachine->Engine()); - return engine.WaitUntilDA1(timeout); + return static_cast(_pInputStateMachine->Engine()); } diff --git a/src/host/VtInputThread.hpp b/src/host/VtInputThread.hpp index 50405b5a25..bf07c1e4be 100644 --- a/src/host/VtInputThread.hpp +++ b/src/host/VtInputThread.hpp @@ -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 capturedCPR = nullptr); [[nodiscard]] HRESULT Start(); - til::enumset WaitUntilDA1(DWORD timeout) const noexcept; + VirtualTerminal::InputStateMachineEngine& GetInputStateMachineEngine() const noexcept; private: static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter); diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp index 53911496a0..876e3ac8bc 100644 --- a/src/host/VtIo.cpp +++ b/src/host/VtIo.cpp @@ -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" @@ -155,7 +156,9 @@ bool VtIo::IsUsingVt() const { if (IsValidHandle(_hInput.get())) { - _pVtInputThread = std::make_unique(std::move(_hInput), _lookingForCursorPosition); + _pVtInputThread = std::make_unique(std::move(_hInput), [this]() { + _cursorPositionReportReceived(); + }); } } CATCH_RETURN(); @@ -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) } @@ -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); } } @@ -226,6 +230,35 @@ bool VtIo::IsUsingVt() const return S_OK; } +void VtIo::RequestCursorPositionFromTerminal() +{ + if (_lookingForCursorPosition) + { + // 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; + return; + } + + _lookingForCursorPosition = true; + _pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR(); + + Writer writer{ this }; + writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR) + writer.Submit(); +} + +void VtIo::_cursorPositionReportReceived() +{ + _lookingForCursorPosition = false; + + if (_scheduleAnotherCPR) + { + _scheduleAnotherCPR = false; + RequestCursorPositionFromTerminal(); + } +} + void VtIo::SetDeviceAttributes(const til::enumset attributes) noexcept { _deviceAttributes = attributes; diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp index 6805fbc730..c375cfd843 100644 --- a/src/host/VtIo.hpp +++ b/src/host/VtIo.hpp @@ -61,6 +61,7 @@ namespace Microsoft::Console::VirtualTerminal bool IsUsingVt() const; [[nodiscard]] HRESULT StartIfNeeded(); + void RequestCursorPositionFromTerminal(); void SetDeviceAttributes(til::enumset attributes) noexcept; til::enumset GetDeviceAttributes() const noexcept; void SendCloseEvent(); @@ -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(); @@ -105,6 +106,7 @@ namespace Microsoft::Console::VirtualTerminal State _state = State::Uninitialized; bool _lookingForCursorPosition = false; + bool _scheduleAnotherCPR = false; bool _closeEventSent = false; int _corked = 0; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index aadcb0d5e0..58b0c864de 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -89,14 +89,24 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep return pair.action == code; } -InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR) : - _pDispatch(std::move(pDispatch)), - _lookingForDSR(lookingForDSR), - _doubleClickTime(std::chrono::milliseconds(GetDoubleClickTime())) +InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr pDispatch, std::function 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 InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept { uint64_t val = 0; @@ -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. @@ -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; diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index e91737d107..7583b24f6c 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -159,8 +159,10 @@ namespace Microsoft::Console::VirtualTerminal class InputStateMachineEngine : public IStateMachineEngine { public: - InputStateMachineEngine(std::unique_ptr pDispatch, const bool lookingForDSR = false); + InputStateMachineEngine(std::unique_ptr pDispatch, std::function capturedCPR = nullptr); + IInteractDispatch& GetDispatch() const noexcept; + void CaptureNextCPR() noexcept; til::enumset WaitUntilDA1(DWORD timeout) const noexcept; bool EncounteredWin32InputModeSequence() const noexcept override; @@ -189,7 +191,8 @@ namespace Microsoft::Console::VirtualTerminal private: const std::unique_ptr _pDispatch; std::atomic _deviceAttributes{ 0 }; - bool _lookingForDSR = false; + bool _lookingForCPR = false; + std::function _capturedCPR; bool _encounteredWin32InputModeSequence = false; bool _expectingStringTerminator = false; DWORD _mouseButtonState = 0; diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index cdfaf1ac1e..e1d657919c 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -2,39 +2,26 @@ // Licensed under the MIT license. #include "precomp.h" -#include "WexTestClass.h" -#include "../../inc/consoletaeftemplates.hpp" -#include "stateMachine.hpp" -#include "InputStateMachineEngine.hpp" -#include "ascii.hpp" -#include "../input/terminalInput.hpp" -#include "../../inc/unicode.hpp" -#include "../../interactivity/inc/EventSynthesis.hpp" - -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include "../../../interactivity/inc/EventSynthesis.hpp" #include "../../../interactivity/inc/VtApiRedirection.hpp" +#include "../../input/terminalInput.hpp" using namespace WEX::Common; using namespace WEX::Logging; using namespace WEX::TestExecution; -namespace Microsoft +namespace Microsoft::Console::VirtualTerminal { - namespace Console - { - namespace VirtualTerminal - { - class InputEngineTest; - class TestInteractDispatch; - }; - }; -}; + class InputEngineTest; + class TestInteractDispatch; +} using namespace Microsoft::Console::VirtualTerminal; bool IsShiftPressed(const DWORD modifierState) @@ -413,7 +400,6 @@ void InputEngineTest::C0Test() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); Log::Comment(L"Sending 0x0-0x19 to parser to make sure they're translated correctly back to C-key"); @@ -513,7 +499,6 @@ void InputEngineTest::AlphanumericTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); Log::Comment(L"Sending every printable ASCII character"); @@ -563,7 +548,6 @@ void InputEngineTest::RoundTripTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); // Send Every VKEY through the TerminalInput module, then take the char's @@ -626,7 +610,6 @@ void InputEngineTest::NonAsciiTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine.get()); testState._stateMachine = _stateMachine.get(); Log::Comment(L"Sending various non-ascii strings, and seeing what we get out"); @@ -679,11 +662,9 @@ void InputEngineTest::CursorPositioningTest() auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1); auto dispatch = std::make_unique(pfn, &testState); - VERIFY_IS_NOT_NULL(dispatch.get()); - auto inputEngine = std::make_unique(std::move(dispatch), true); - VERIFY_IS_NOT_NULL(inputEngine.get()); + auto inputEngine = std::make_unique(std::move(dispatch), []() {}); + inputEngine->CaptureNextCPR(); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); Log::Comment(NoThrowString().Format( @@ -724,7 +705,6 @@ void InputEngineTest::CSICursorBackTabTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); INPUT_RECORD inputRec; @@ -752,7 +732,6 @@ void InputEngineTest::EnhancedKeysTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); // The following vkeys should be handled as enhanced keys @@ -802,7 +781,6 @@ void InputEngineTest::SS3CursorKeyTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); // clang-format off @@ -846,7 +824,6 @@ void InputEngineTest::AltBackspaceTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); INPUT_RECORD inputRec; @@ -874,7 +851,6 @@ void InputEngineTest::AltCtrlDTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); INPUT_RECORD inputRec; @@ -923,7 +899,6 @@ void InputEngineTest::AltIntermediateTest() auto dispatch = std::make_unique(pfnInputStateMachineCallback, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(stateMachine); testState._stateMachine = stateMachine.get(); // Write a Alt+/, Ctrl+e pair to the input engine, then take its output and @@ -955,7 +930,6 @@ void InputEngineTest::AltBackspaceEnterTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); INPUT_RECORD inputRec; @@ -1051,7 +1025,6 @@ void InputEngineTest::VerifySGRMouseData(const std::vector_doubleClickTime = std::chrono::milliseconds(1000); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); SGR_PARAMS input; @@ -1282,7 +1255,6 @@ void InputEngineTest::CtrlAltZCtrlAltXTest() auto dispatch = std::make_unique(pfn, &testState); auto inputEngine = std::make_unique(std::move(dispatch)); auto _stateMachine = std::make_unique(std::move(inputEngine)); - VERIFY_IS_NOT_NULL(_stateMachine); testState._stateMachine = _stateMachine.get(); // This is a test for GH#4201. See that issue for more details.