diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt index 60b57274c84..43f6b97325d 100644 --- a/.github/actions/spelling/allow/allow.txt +++ b/.github/actions/spelling/allow/allow.txt @@ -44,6 +44,7 @@ inlined It'd kje libfuzzer +libuv liga lje Llast diff --git a/src/terminal/adapter/ut_adapter/inputTest.cpp b/src/terminal/adapter/ut_adapter/inputTest.cpp index 070e2f5d053..ace7de68019 100644 --- a/src/terminal/adapter/ut_adapter/inputTest.cpp +++ b/src/terminal/adapter/ut_adapter/inputTest.cpp @@ -38,6 +38,7 @@ class Microsoft::Console::VirtualTerminal::InputTest static void s_TerminalInputTestNullCallback(_In_ std::deque>& inEvents); TEST_METHOD(TerminalInputTests); + TEST_METHOD(TestFocusEvents); TEST_METHOD(TerminalInputModifierKeyTests); TEST_METHOD(TerminalInputNullKeyTests); TEST_METHOD(DifferentModifiersTest); @@ -300,10 +301,62 @@ void InputTest::TerminalInputTests() inputEvent = IInputEvent::Create(irUnhandled); VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify MENU_EVENT was NOT handled."); - Log::Comment(L"Testing FOCUS_EVENT"); - irUnhandled.EventType = FOCUS_EVENT; - inputEvent = IInputEvent::Create(irUnhandled); - VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT was NOT handled."); + // Testing FOCUS_EVENTs is handled by TestFocusEvents +} + +void InputTest::TestFocusEvents() +{ + // GH#12900, #13238 + // Focus events that come in from the API should never be translated to VT sequences. + // We're relying on the fact that the INPUT_RECORD version of the ctor is only called by the API + const auto pInput = new TerminalInput(s_TerminalInputTestCallback); + + INPUT_RECORD irTest = { 0 }; + irTest.EventType = FOCUS_EVENT; + + { + irTest.Event.FocusEvent.bSetFocus = false; + auto inputEvent = IInputEvent::Create(irTest); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled."); + } + { + irTest.Event.FocusEvent.bSetFocus = true; + auto inputEvent = IInputEvent::Create(irTest); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled."); + } + { + auto inputEvent = std::make_unique(false); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FocusEvent from any other source was NOT handled."); + } + { + auto inputEvent = std::make_unique(true); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FocusEvent from any other source was NOT handled."); + } + + Log::Comment(L"Enable focus event handling"); + + pInput->SetInputMode(TerminalInput::Mode::FocusEvent, true); + + { + irTest.Event.FocusEvent.bSetFocus = false; + auto inputEvent = IInputEvent::Create(irTest); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled."); + } + { + irTest.Event.FocusEvent.bSetFocus = true; + auto inputEvent = IInputEvent::Create(irTest); + VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify FOCUS_EVENT from API was NOT handled."); + } + { + s_expectedInput = L"\x1b[O"; + auto inputEvent = std::make_unique(false); + VERIFY_ARE_EQUAL(true, pInput->HandleKey(inputEvent.get()), L"Verify FocusEvent from any other source was handled."); + } + { + s_expectedInput = L"\x1b[I"; + auto inputEvent = std::make_unique(true); + VERIFY_ARE_EQUAL(true, pInput->HandleKey(inputEvent.get()), L"Verify FocusEvent from any other source was handled."); + } } void InputTest::TerminalInputModifierKeyTests() diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index d06a5e0e0b0..79b5ef74f5f 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -527,6 +527,14 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent) if (pInEvent->EventType() == InputEventType::FocusEvent) { const auto& focusEvent = *static_cast(pInEvent); + + // BODGY + // GH#13238 - Filter out focus events that came from the API. + if (focusEvent.CameFromApi()) + { + return false; + } + return HandleFocus(focusEvent.GetFocus()); } diff --git a/src/types/FocusEvent.cpp b/src/types/FocusEvent.cpp index d850fff50fd..1b194842a5f 100644 --- a/src/types/FocusEvent.cpp +++ b/src/types/FocusEvent.cpp @@ -1,5 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +// +// BODGY: GH#13238 +// +// It appears that some applications (libuv) like to send a FOCUS_EVENT_RECORD +// as a way to jiggle the input handle. Focus events really shouldn't ever be +// sent via the API, they don't really do anything internally. However, focus +// events in the input buffer do get translated by the TerminalInput to VT +// sequences if we're in the right input mode. +// +// To not prevent libuv from jiggling the handle with a focus event, and also +// make sure that we don't erroneously translate that to a sequence of +// characters, we're going to filter out focus events that came from the API +// when translating to VT. #include "precomp.h" #include "inc/IInputEvent.hpp" diff --git a/src/types/inc/IInputEvent.hpp b/src/types/inc/IInputEvent.hpp index d98f176795a..74258454836 100644 --- a/src/types/inc/IInputEvent.hpp +++ b/src/types/inc/IInputEvent.hpp @@ -514,12 +514,14 @@ class FocusEvent : public IInputEvent { public: constexpr FocusEvent(const FOCUS_EVENT_RECORD& record) : - _focus{ !!record.bSetFocus } + _focus{ !!record.bSetFocus }, + _cameFromApi{ true } { } constexpr FocusEvent(const bool focus) : - _focus{ focus } + _focus{ focus }, + _cameFromApi{ false } { } @@ -539,8 +541,15 @@ class FocusEvent : public IInputEvent void SetFocus(const bool focus) noexcept; + // BODGY - see FocusEvent.cpp for details. + constexpr bool CameFromApi() const noexcept + { + return _cameFromApi; + } + private: bool _focus; + bool _cameFromApi; #ifdef UNIT_TESTING friend std::wostream& operator<<(std::wostream& stream, const FocusEvent* const pFocusEvent);