Plumb Focus events through VT Input (#12900)

Further builds on #12799. #12799 assumes that the connection is prepared to receive FocusIn/FocusOut events as input. For ConPTY we can be relatively sure of that, but that's not _technically_ correct. In the hypothetical world where the connection is not a ConPTY connection, then the other side might not be expecting those sequences. 

This remedies the issue by
* ConPTY will always request focus event mode (from the terminal) when it starts up
* when a client tries to disable focus events in conpty, conpty is gonna note that internally, but never transmit that to the hosting terminal, to leave the terminal in focus event mode.
* `TerminalDispatch` and `ControlCore` are hooked up now to only send focus events when the Terminal is in focus event mode (which will be always for conpty)
* At this point, it was like, 4LOC in `terminalInput.cpp` to add support for focus events to conhost as well.

## checklist
* [x] closes #11682
  * This combined with #12515 will finally close out #2988 as well, but we can do that manually.
* [x] I work here
* [ ] There aren't tests for this. There probably should be.
This commit is contained in:
Mike Griese
2022-04-20 13:22:42 -05:00
committed by GitHub
parent 7af134cc7f
commit 87f5034db1
18 changed files with 108 additions and 9 deletions

View File

@@ -1708,13 +1708,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - This is related to work done for GH#2988.
void ControlCore::GotFocus()
{
_connection.WriteInput(L"\x1b[I");
_terminal->FocusChanged(true);
}
// See GotFocus.
void ControlCore::LostFocus()
{
_connection.WriteInput(L"\x1b[O");
_terminal->FocusChanged(false);
}
bool ControlCore::_isBackgroundTransparent()

View File

@@ -26,6 +26,8 @@ namespace Microsoft::Terminal::Core
virtual void TrySnapOnInput() = 0;
virtual void FocusChanged(const bool focused) = 0;
protected:
ITerminalInput() = default;
};

View File

@@ -746,6 +746,19 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro
return handledDown || handledUp;
}
// Method Description:
// - Tell the terminal input that we gained or lost focus. If the client
// requested focus events, this will send a message to them.
// - ConPTY ALWAYS wants focus events.
// Arguments:
// - focused: true if we're focused, false otherwise.
// Return Value:
// - none
void Terminal::FocusChanged(const bool focused) noexcept
{
_terminalInput->HandleFocus(focused);
}
// Method Description:
// - Invalidates the regions described in the given pattern tree for the rendering purposes
// Arguments:

View File

@@ -156,6 +156,8 @@ public:
bool IsTrackingMouseInput() const noexcept;
bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept;
void FocusChanged(const bool focused) noexcept override;
std::wstring GetHyperlinkAtPosition(const COORD position);
uint16_t GetHyperlinkIdAtPosition(const COORD position);
std::optional<interval_tree::IntervalTree<til::point, size_t>::interval> GetHyperlinkIntervalFromPosition(const COORD position);

View File

@@ -383,7 +383,6 @@ bool TerminalDispatch::EnableButtonEventMouseMode(const bool enabled)
//Routine Description:
// Enable Any Event mode - send all mouse events to the input.
//Arguments:
// - enabled - true to enable, false to disable.
// Return value:
@@ -394,6 +393,20 @@ bool TerminalDispatch::EnableAnyEventMouseMode(const bool enabled)
return true;
}
// Method Description:
// - Enables/disables focus event mode. A client may enable this if they want to
// receive focus events.
// - ConPTY always enables this mode and never disables it. For more details, see GH#12900.
// Arguments:
// - enabled - true to enable, false to disable.
// Return Value:
// - True if handled successfully. False otherwise.
bool TerminalDispatch::EnableFocusEventMode(const bool enabled)
{
_terminalApi.SetInputMode(TerminalInput::Mode::FocusEvent, enabled);
return true;
}
//Routine Description:
// Enable Alternate Scroll Mode - When in the Alt Buffer, send CUP and CUD on
// scroll up/down events instead of the usual sequences
@@ -631,6 +644,9 @@ bool TerminalDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param,
case DispatchTypes::ModeParams::SGR_EXTENDED_MODE:
success = EnableSGRExtendedMouseMode(enable);
break;
case DispatchTypes::ModeParams::FOCUS_EVENT_MODE:
success = EnableFocusEventMode(enable);
break;
case DispatchTypes::ModeParams::ALTERNATE_SCROLL:
success = EnableAlternateScroll(enable);
break;

View File

@@ -74,6 +74,7 @@ public:
bool EnableSGRExtendedMouseMode(const bool enabled) override; // ?1006
bool EnableButtonEventMouseMode(const bool enabled) override; // ?1002
bool EnableAnyEventMouseMode(const bool enabled) override; // ?1003
bool EnableFocusEventMode(const bool enabled) override; // ?1004
bool EnableAlternateScroll(const bool enabled) override; // ?1007
bool EnableXtermBracketedPasteMode(const bool enabled) override; // ?2004

View File

@@ -642,6 +642,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque<std::unique_ptr<IInputEvent>>&
inEvents.pop_front();
if (vtInputMode)
{
// GH#11682: TerminalInput::HandleKey can handle both KeyEvents and Focus events seamlessly
const bool handled = _termInput.HandleKey(inEvent.get());
if (handled)
{

View File

@@ -434,6 +434,11 @@ using namespace Microsoft::Console::Render;
return _Write("\x1b[?9001h");
}
[[nodiscard]] HRESULT VtEngine::_RequestFocusEventMode() noexcept
{
return _Write("\x1b[?1004h");
}
// Method Description:
// - Send a sequence to the connected terminal to switch to the alternate or main screen buffer.
// Arguments:

View File

@@ -524,6 +524,7 @@ void VtEngine::SetTerminalCursorTextPosition(const COORD cursor) noexcept
HRESULT VtEngine::RequestWin32Input() noexcept
{
RETURN_IF_FAILED(_RequestWin32Input());
RETURN_IF_FAILED(_RequestFocusEventMode());
RETURN_IF_FAILED(_Flush());
return S_OK;
}

View File

@@ -200,6 +200,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _RequestWin32Input() noexcept;
[[nodiscard]] HRESULT _SwitchScreenBuffer(const bool useAltBuffer) noexcept;
[[nodiscard]] HRESULT _RequestFocusEventMode() noexcept;
[[nodiscard]] virtual HRESULT _MoveCursor(const COORD coord) noexcept = 0;
[[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept;
[[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept;

View File

@@ -375,6 +375,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
VT200_MOUSE_MODE = DECPrivateMode(1000),
BUTTON_EVENT_MOUSE_MODE = DECPrivateMode(1002),
ANY_EVENT_MOUSE_MODE = DECPrivateMode(1003),
FOCUS_EVENT_MODE = DECPrivateMode(1004),
UTF8_EXTENDED_MODE = DECPrivateMode(1005),
SGR_EXTENDED_MODE = DECPrivateMode(1006),
ALTERNATE_SCROLL = DECPrivateMode(1007),

View File

@@ -79,6 +79,7 @@ public:
virtual bool EnableSGRExtendedMouseMode(const bool enabled) = 0; // ?1006
virtual bool EnableButtonEventMouseMode(const bool enabled) = 0; // ?1002
virtual bool EnableAnyEventMouseMode(const bool enabled) = 0; // ?1003
virtual bool EnableFocusEventMode(const bool enabled) = 0; // ?1004
virtual bool EnableAlternateScroll(const bool enabled) = 0; // ?1007
virtual bool EnableXtermBracketedPasteMode(const bool enabled) = 0; // ?2004
virtual bool SetColorTableEntry(const size_t tableIndex, const DWORD color) = 0; // OSCColorTable

View File

@@ -175,7 +175,6 @@ bool InteractDispatch::IsVtInputEnabled() const
// which will end up here. This will update the console's internal tracker if
// it's focused or not, as to match the end-terminal's state.
// - Used to call ConsoleControl(ConsoleSetForeground,...).
// - Full support for this sequence is tracked in GH#11682.
// Arguments:
// - focused: if the terminal is now focused
// Return Value:
@@ -234,11 +233,9 @@ bool InteractDispatch::FocusChanged(const bool focused) const
WI_UpdateFlag(gci.Flags, CONSOLE_HAS_FOCUS, shouldActuallyFocus);
gci.ProcessHandleList.ModifyConsoleProcessFocus(shouldActuallyFocus);
gci.pInputBuffer->Write(std::make_unique<FocusEvent>(focused));
}
// Does nothing outside of ConPTY. If there's a real HWND, then the HWND is solely in charge.
// Theoretically, this could be propagated as a focus event as well, to the
// input buffer. That should be considered when implementing GH#11682.
return true;
}

View File

@@ -1025,7 +1025,24 @@ bool AdaptDispatch::_SetInputMode(const TerminalInput::Mode mode, const bool ena
// us that SSH 7.7 _also_ requests mouse input and that can have a user interface
// impact on the actual connected terminal. We can't remove this check,
// because SSH <=7.7 is out in the wild on all versions of Windows <=2004.
return !(_pConApi->IsConsolePty() && _pConApi->IsVtInputEnabled());
// GH#12799 - If the app requested that we disable focus events, DON'T pass
// that through. ConPTY would _always_ like to know about focus events.
return !_pConApi->IsConsolePty() ||
!_pConApi->IsVtInputEnabled() ||
(!enable && mode == TerminalInput::Mode::FocusEvent);
// Another way of writing the above statement is:
//
// const bool inConpty = _pConApi->IsConsolePty();
// const bool shouldPassthrough = inConpty && _pConApi->IsVtInputEnabled();
// const bool disabledFocusEvents = inConpty && (!enable && mode == TerminalInput::Mode::FocusEvent);
// return !shouldPassthrough || disabledFocusEvents;
//
// It's like a "filter" left to right. Due to the early return via
// !IsConsolePty, once you're at the !enable part, IsConsolePty can only be
// true anymore.
}
// Routine Description:
@@ -1084,6 +1101,9 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
case DispatchTypes::ModeParams::SGR_EXTENDED_MODE:
success = EnableSGRExtendedMouseMode(enable);
break;
case DispatchTypes::ModeParams::FOCUS_EVENT_MODE:
success = EnableFocusEventMode(enable);
break;
case DispatchTypes::ModeParams::ALTERNATE_SCROLL:
success = EnableAlternateScroll(enable);
break;
@@ -2092,7 +2112,6 @@ bool AdaptDispatch::EnableButtonEventMouseMode(const bool enabled)
//Routine Description:
// Enable Any Event mode - send all mouse events to the input.
//Arguments:
// - enabled - true to enable, false to disable.
// Return value:
@@ -2102,6 +2121,21 @@ bool AdaptDispatch::EnableAnyEventMouseMode(const bool enabled)
return _SetInputMode(TerminalInput::Mode::AnyEventMouseTracking, enabled);
}
// Method Description:
// - Enables/disables focus event mode. A client may enable this if they want to
// receive focus events.
// - ConPTY always enables this mode and never disables it. Internally, we'll
// always set this mode, but conpty will never request this to be disabled by
// the hosting terminal.
// Arguments:
// - enabled - true to enable, false to disable.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::EnableFocusEventMode(const bool enabled)
{
return _SetInputMode(TerminalInput::Mode::FocusEvent, enabled);
}
//Routine Description:
// Enable Alternate Scroll Mode - When in the Alt Buffer, send CUP and CUD on
// scroll up/down events instead of the usual sequences

View File

@@ -107,6 +107,7 @@ namespace Microsoft::Console::VirtualTerminal
bool EnableSGRExtendedMouseMode(const bool enabled) override; // ?1006
bool EnableButtonEventMouseMode(const bool enabled) override; // ?1002
bool EnableAnyEventMouseMode(const bool enabled) override; // ?1003
bool EnableFocusEventMode(const bool enabled) override; // ?1004
bool EnableAlternateScroll(const bool enabled) override; // ?1007
bool EnableXtermBracketedPasteMode(const bool enabled) noexcept override; // ?2004
bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR

View File

@@ -72,6 +72,7 @@ public:
bool EnableSGRExtendedMouseMode(const bool /*enabled*/) override { return false; } // ?1006
bool EnableButtonEventMouseMode(const bool /*enabled*/) override { return false; } // ?1002
bool EnableAnyEventMouseMode(const bool /*enabled*/) override { return false; } // ?1003
bool EnableFocusEventMode(const bool /*enabled*/) override { return false; } // ?1004
bool EnableAlternateScroll(const bool /*enabled*/) override { return false; } // ?1007
bool EnableXtermBracketedPasteMode(const bool /*enabled*/) override { return false; } // ?2004
bool SetColorTableEntry(const size_t /*tableIndex*/, const DWORD /*color*/) override { return false; } // OSCColorTable

View File

@@ -522,6 +522,14 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
return false;
}
// GH#11682: If this was a focus event, we can handle this. Steal the
// focused state, and return true if we're actually in focus event mode.
if (pInEvent->EventType() == InputEventType::FocusEvent)
{
const auto& focusEvent = *static_cast<const FocusEvent* const>(pInEvent);
return HandleFocus(focusEvent.GetFocus());
}
// On key presses, prepare to translate to VT compatible sequences
if (pInEvent->EventType() != InputEventType::KeyEvent)
{
@@ -674,6 +682,16 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
return false;
}
bool TerminalInput::HandleFocus(const bool focused) noexcept
{
const bool enabled{ _inputMode.test(Mode::FocusEvent) };
if (enabled)
{
_SendInputSequence(focused ? L"\x1b[I" : L"\x1b[O");
}
return enabled;
}
// Routine Description:
// - Sends the given character to the shell.
// - Surrogate pairs are being aggregated by this function before being sent.

View File

@@ -34,6 +34,7 @@ namespace Microsoft::Console::VirtualTerminal
~TerminalInput() = default;
bool HandleKey(const IInputEvent* const pInEvent);
bool HandleFocus(const bool focused) noexcept;
enum class Mode : size_t
{
@@ -49,6 +50,8 @@ namespace Microsoft::Console::VirtualTerminal
ButtonEventMouseTracking,
AnyEventMouseTracking,
FocusEvent,
AlternateScroll
};