diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index c2618b0bc7..811831edea 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -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() diff --git a/src/cascadia/TerminalCore/ITerminalInput.hpp b/src/cascadia/TerminalCore/ITerminalInput.hpp index c2c7c54ca1..1db443f51d 100644 --- a/src/cascadia/TerminalCore/ITerminalInput.hpp +++ b/src/cascadia/TerminalCore/ITerminalInput.hpp @@ -26,6 +26,8 @@ namespace Microsoft::Terminal::Core virtual void TrySnapOnInput() = 0; + virtual void FocusChanged(const bool focused) = 0; + protected: ITerminalInput() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 81fb6c7edb..d3910abbc5 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -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: diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index de84a964c9..af491396d7 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -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> GetHyperlinkIntervalFromPosition(const COORD position); diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index b380acdba1..082b85b7e1 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -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; diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index d358552b78..a86417cdd1 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -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 diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp index 8283ef213e..b2a00fdba5 100644 --- a/src/host/inputBuffer.cpp +++ b/src/host/inputBuffer.cpp @@ -642,6 +642,7 @@ void InputBuffer::_WriteBuffer(_Inout_ std::deque>& 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) { diff --git a/src/renderer/vt/VtSequences.cpp b/src/renderer/vt/VtSequences.cpp index 05fd6f9518..56939fb175 100644 --- a/src/renderer/vt/VtSequences.cpp +++ b/src/renderer/vt/VtSequences.cpp @@ -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: diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp index 1f3cf51646..3669a0440e 100644 --- a/src/renderer/vt/state.cpp +++ b/src/renderer/vt/state.cpp @@ -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; } diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index cd8a3d5d73..cd1d44fff4 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -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; diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 5a048aafb5..48af68e336 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -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), diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index a097bb9d68..0433d9662d 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -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 diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index ace72b0711..25a440352d 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -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(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; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 2e1bf05022..6bf29254e9 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -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 diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index becb318024..3cf677b194 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -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 diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 01e9ed2083..386d1ced71 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -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 diff --git a/src/terminal/input/terminalInput.cpp b/src/terminal/input/terminalInput.cpp index 1408ad56cb..1210432c6d 100644 --- a/src/terminal/input/terminalInput.cpp +++ b/src/terminal/input/terminalInput.cpp @@ -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(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. diff --git a/src/terminal/input/terminalInput.hpp b/src/terminal/input/terminalInput.hpp index 0093269cbc..8f0ac5dcbe 100644 --- a/src/terminal/input/terminalInput.hpp +++ b/src/terminal/input/terminalInput.hpp @@ -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 };