diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index c550839bf91..c2618b0bc7f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -1696,6 +1696,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + // Method Description: + // - When the control gains focus, it needs to tell ConPTY about this. + // Usually, these sequences are reserved for applications that + // specifically request SET_FOCUS_EVENT_MOUSE, ?1004h. ConPTY uses this + // sequence REGARDLESS to communicate if the control was focused or not. + // - Even if a client application disables this mode, the Terminal & conpty + // should always request this from the hosting terminal (and just ignore + // internally to ConPTY). + // - Full support for this sequence is tracked in GH#11682. + // - This is related to work done for GH#2988. + void ControlCore::GotFocus() + { + _connection.WriteInput(L"\x1b[I"); + } + + // See GotFocus. + void ControlCore::LostFocus() + { + _connection.WriteInput(L"\x1b[O"); + } + bool ControlCore::_isBackgroundTransparent() { // If we're: diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index fa4135c2653..0d265375bc6 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -81,6 +81,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation void PasteText(const winrt::hstring& hstr); bool CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference& formats); + void GotFocus(); + void LostFocus(); + void ToggleShaderEffects(); void AdjustOpacity(const double adjustment); void ResumeRendering(); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index 015942e4ac1..24fb712f1d1 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -111,6 +111,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation THROW_IF_FAILED(_uiaEngine->Enable()); } + _core->GotFocus(); + _updateSystemParameterSettings(); } @@ -120,6 +122,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation { THROW_IF_FAILED(_uiaEngine->Disable()); } + + _core->LostFocus(); } // Method Description diff --git a/src/host/CursorBlinker.cpp b/src/host/CursorBlinker.cpp index f3aee7a4798..e15456d2e1c 100644 --- a/src/host/CursorBlinker.cpp +++ b/src/host/CursorBlinker.cpp @@ -82,8 +82,10 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo) const noexcept auto& cursor = buffer.GetCursor(); auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto* const pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier(); + const bool inConpty{ gci.IsInVtIoMode() }; - if (!WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) + // GH#2988: ConPTY can now be focused, but it doesn't need to do any of this work either. + if (inConpty || !WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)) { goto DoScroll; } diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp index e19526cccf5..52ac5f0b2a9 100644 --- a/src/server/IoDispatchers.cpp +++ b/src/server/IoDispatchers.cpp @@ -456,7 +456,22 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API return pReceiveMsg; } - gci.ProcessHandleList.ModifyConsoleProcessFocus(WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS)); + // For future code archeologists: GH#2988 + // + // Here, the console calls ConsoleControl(ConsoleSetForeground,...) with a + // flag depending on if the console is focused or not. This is surprisingly + // load bearing. This allows windows spawned by console processes to bring + // themselves to the foreground _when the console is focused_. + // (Historically, this is also called in the WndProc, when focus changes). + // + // Notably, before 2022, ConPTY was _never_ focused, so windows could never + // bring themselves to the foreground when run from a ConPTY console. We're + // not blanket granting the SetForeground right to all console apps when run + // in ConPTY. It's the responsibility of the hosting terminal emulator to + // always tell ConPTY when a particular instance is focused. + const bool hasFocus{ WI_IsFlagSet(gci.Flags, CONSOLE_HAS_FOCUS) }; + const auto grantSetForeground{ hasFocus }; + gci.ProcessHandleList.ModifyConsoleProcessFocus(grantSetForeground); // Create the handles. diff --git a/src/terminal/adapter/IInteractDispatch.hpp b/src/terminal/adapter/IInteractDispatch.hpp index acbaf5ff23a..97b22b3edde 100644 --- a/src/terminal/adapter/IInteractDispatch.hpp +++ b/src/terminal/adapter/IInteractDispatch.hpp @@ -42,5 +42,7 @@ namespace Microsoft::Console::VirtualTerminal const size_t col) = 0; virtual bool IsVtInputEnabled() const = 0; + + virtual bool FocusChanged(const bool focused) const = 0; }; } diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index 32a30c6f927..c93092c4102 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -181,3 +181,32 @@ bool InteractDispatch::IsVtInputEnabled() const { return _pConApi->IsVtInputEnabled(); } + +// Method Description: +// - Inform the console that the window is focused. This is used by ConPTY. +// Terminals can send ConPTY a FocusIn/FocusOut sequence on the input pipe, +// 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: +// - true always. +bool InteractDispatch::FocusChanged(const bool focused) const +{ + // When we do GH#11682, we should make sure that ConPTY requests this mode + // from the terminal when it starts up, and ConPTY never unsets that flag. + // It should only ever internally disable the events from flowing to the + // client application. + + auto& g = ServiceLocator::LocateGlobals(); + auto& gci = g.getConsoleInformation(); + WI_UpdateFlag(gci.Flags, CONSOLE_HAS_FOCUS, focused); + gci.ProcessHandleList.ModifyConsoleProcessFocus(focused); + + // 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/InteractDispatch.hpp b/src/terminal/adapter/InteractDispatch.hpp index d903d83e9c6..e521244eb2e 100644 --- a/src/terminal/adapter/InteractDispatch.hpp +++ b/src/terminal/adapter/InteractDispatch.hpp @@ -35,6 +35,8 @@ namespace Microsoft::Console::VirtualTerminal bool IsVtInputEnabled() const override; + bool FocusChanged(const bool focused) const override; + private: std::unique_ptr _pConApi; }; diff --git a/src/terminal/parser/InputStateMachineEngine.cpp b/src/terminal/parser/InputStateMachineEngine.cpp index 40a9d0c4094..7fcec6b92da 100644 --- a/src/terminal/parser/InputStateMachineEngine.cpp +++ b/src/terminal/parser/InputStateMachineEngine.cpp @@ -362,9 +362,14 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter // win32-input-mode, and if they did, then we'll just translate the // INPUT_RECORD back to the same sequence we say here later on, when the // client reads it. + // + // Focus events in conpty are special, so don't flush those through either. + // See GH#12799, GH#12900 for details if (_pDispatch->IsVtInputEnabled() && _pfnFlushToInputQueue && - id != CsiActionCodes::Win32KeyboardInput) + id != CsiActionCodes::Win32KeyboardInput && + id != CsiActionCodes::FocusIn && + id != CsiActionCodes::FocusOut) { return _pfnFlushToInputQueue(); } @@ -426,6 +431,12 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter case CsiActionCodes::DTTERM_WindowManipulation: success = _pDispatch->WindowManipulation(parameters.at(0), parameters.at(1), parameters.at(2)); break; + case CsiActionCodes::FocusIn: + success = _pDispatch->FocusChanged(true); + break; + case CsiActionCodes::FocusOut: + success = _pDispatch->FocusChanged(false); + break; case CsiActionCodes::Win32KeyboardInput: { // Use WriteCtrlKey here, even for keys that _aren't_ control keys, diff --git a/src/terminal/parser/InputStateMachineEngine.hpp b/src/terminal/parser/InputStateMachineEngine.hpp index 8409617204a..c4a814a1eeb 100644 --- a/src/terminal/parser/InputStateMachineEngine.hpp +++ b/src/terminal/parser/InputStateMachineEngine.hpp @@ -58,6 +58,8 @@ namespace Microsoft::Console::VirtualTerminal ArrowLeft = VTID("D"), Home = VTID("H"), End = VTID("F"), + FocusIn = VTID("I"), + FocusOut = VTID("O"), MouseDown = VTID(">&)> _pfnWriteInputCallback; TestState* _testState; // non-ownership pointer @@ -399,6 +401,11 @@ bool TestInteractDispatch::IsVtInputEnabled() const return true; } +bool TestInteractDispatch::FocusChanged(const bool /*focused*/) const +{ + return false; +} + void InputEngineTest::C0Test() { auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);