Repository: microsoft/terminal (language: cpp)

Issue:
Using SetConsoleMode to set/clear ENABLE_MOUSE_INPUT does not take effect (unless a WriteConsoleW call occurs as well)
### Windows Terminal version

1.18.1462.0

### Windows build number

10.0.19045.3208

### Other Software

I've provided a minimal standalone repro here to eliminate dependencies on anything other than Terminal.
But for completeness: this was discovered in the context of [Clink](https://github.com/chrisant996/clink).


### Steps to reproduce

This is a regression in 1.18; the problem does not exist in 1.17 and earlier.

Minimal repro program is available at https://github.com/chrisant996/repro_enable_mouse_input.
The repo also includes a pre-built code-signed `Repro.exe` executable.

**See the Readme.md file in that repo for full details and repro instructions.**

1. Start Windows Terminal 1.18
2. Run the `Repro.exe` program from the [repro_enable_mouse_input](https://github.com/chrisant996/repro_enable_mouse_input) repo (you can build it yourself from the sources, or you can run the pre-built code-signed copy)
3. Move the mouse over the terminal window; you should see no output
4. Click the primary mouse button on the terminal window; you should see no output
5. Hold <kbd>Ctrl</kbd>+<kbd>Alt</kbd> and move the mouse or click the mouse button

### Expected Behavior

- You should see output acknowledging that ReadConsoleInputW received mouse input records.


### Actual Behavior

- Terminal 1.18 -- there is no output (incorrect behavior).
- Terminal 1.17 -- the output reports received mouse input records (correct behavior).


Discussion / hints:
@zadjii-msft this is likely due to the flushing changes you made in #14677 

Fix patch:
diff --git a/src/host/VtIo.cpp b/src/host/VtIo.cpp
index 5d33e607931..cb31fa407e8 100644
--- a/src/host/VtIo.cpp
+++ b/src/host/VtIo.cpp
@@ -255,7 +255,6 @@ bool VtIo::IsUsingVt() const
         {
             g.pRender->AddRenderEngine(_pVtRenderEngine.get());
             g.getConsoleInformation().GetActiveOutputBuffer().SetTerminalConnection(_pVtRenderEngine.get());
-            g.getConsoleInformation().GetActiveInputBuffer()->SetTerminalConnection(_pVtRenderEngine.get());
 
             // Force the whole window to be put together first.
             // We don't really need the handle, we just want to leverage the setup steps.
@@ -497,6 +496,18 @@ void VtIo::EndResize()
     }
 }
 
+// The name of this method is an analogy to TCP_CORK. It instructs
+// the VT renderer to stop flushing its buffer to the output pipe.
+// Don't forget to uncork it!
+void VtIo::CorkRenderer(bool corked) const noexcept
+{
+    _pVtRenderEngine->Cork(corked);
+    if (!corked)
+    {
+        LOG_IF_FAILED(ServiceLocator::LocateGlobals().pRender->PaintFrame());
+    }
+}
+
 #ifdef UNIT_TESTING
 // Method Description:
 // - This is a test helper method. It can be used to trick VtIo into responding
@@ -547,3 +558,12 @@ bool VtIo::IsResizeQuirkEnabled() const
     }
     return S_OK;
 }
+
+[[nodiscard]] HRESULT VtIo::RequestMouseMode(bool enable) const noexcept
+{
+    if (_pVtRenderEngine)
+    {
+        return _pVtRenderEngine->RequestMouseMode(enable);
+    }
+    return S_OK;
+}
diff --git a/src/host/VtIo.hpp b/src/host/VtIo.hpp
index a15653ad084..3e23350d153 100644
--- a/src/host/VtIo.hpp
+++ b/src/host/VtIo.hpp
@@ -43,6 +43,8 @@ namespace Microsoft::Console::VirtualTerminal
         void BeginResize();
         void EndResize();
 
+        void CorkRenderer(bool corked) const noexcept;
+
 #ifdef UNIT_TESTING
         void EnableConptyModeForTests(std::unique_ptr<Microsoft::Console::Render::VtEngine> vtRenderEngine);
 #endif
@@ -50,6 +52,7 @@ namespace Microsoft::Console::VirtualTerminal
         bool IsResizeQuirkEnabled() const;
 
         [[nodiscard]] HRESULT ManuallyClearScrollback() const noexcept;
+        [[nodiscard]] HRESULT RequestMouseMode(bool enable) const noexcept;
 
         void CreatePseudoWindow();
         void SetWindowVisibility(bool showOrHide) noexcept;
diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp
index d9858c7e003..bf9e32ed38b 100644
--- a/src/host/_stream.cpp
+++ b/src/host/_stream.cpp
@@ -334,17 +334,24 @@ try
         return CONSOLE_STATUS_WAIT;
     }
 
-    auto restoreVtQuirk{
-        wil::scope_exit([&]() { screenInfo.ResetIgnoreLegacyEquivalentVTAttributes(); })
-    };
-
+    const auto vtIo = ServiceLocator::LocateGlobals().getConsoleInformation().GetVtIo();
+    const auto restoreVtQuirk = wil::scope_exit([&]() {
+        if (requiresVtQuirk)
+        {
+            screenInfo.ResetIgnoreLegacyEquivalentVTAttributes();
+        }
+        if (vtIo->IsUsingVt())
+        {
+            vtIo->CorkRenderer(false);
+        }
+    });
     if (requiresVtQuirk)
     {
         screenInfo.SetIgnoreLegacyEquivalentVTAttributes();
     }
-    else
+    if (vtIo->IsUsingVt())
     {
-        restoreVtQuirk.release();
+        vtIo->CorkRenderer(true);
     }
 
     const std::wstring_view str{ pwchBuffer, *pcbBuffer / sizeof(WCHAR) };
diff --git a/src/host/getset.cpp b/src/host/getset.cpp
index 5c2954f4fa4..eec9f720db8 100644
--- a/src/host/getset.cpp
+++ b/src/host/getset.cpp
@@ -368,17 +368,19 @@ void ApiRoutines::GetNumberOfConsoleMouseButtonsImpl(ULONG& buttons) noexcept
             WI_ClearFlag(gci.Flags, CONSOLE_USE_PRIVATE_FLAGS);
         }
 
-        const auto newQuickEditMode{ WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE) };
-
-        // Mouse input should be received when mouse mode is on and quick edit mode is off
-        // (for more information regarding the quirks of mouse mode and why/how it relates
-        //  to quick edit mode, see GH#9970)
-        const auto oldMouseMode{ !oldQuickEditMode && WI_IsFlagSet(context.InputMode, ENABLE_MOUSE_INPUT) };
-        const auto newMouseMode{ !newQuickEditMode && WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) };
-
-        if (oldMouseMode != newMouseMode)
+        if (gci.IsInVtIoMode())
         {
-            gci.GetActiveInputBuffer()->PassThroughWin32MouseRequest(newMouseMode);
+            // Mouse input should be received when mouse mode is on and quick edit mode is off
+            // (for more information regarding the quirks of mouse mode and why/how it relates
+            //  to quick edit mode, see GH#9970)
+            const auto newQuickEditMode{ WI_IsFlagSet(gci.Flags, CONSOLE_QUICK_EDIT_MODE) };
+            const auto oldMouseMode{ !oldQuickEditMode && WI_IsFlagSet(context.InputMode, ENABLE_MOUSE_INPUT) };
+            const auto newMouseMode{ !newQuickEditMode && WI_IsFlagSet(mode, ENABLE_MOUSE_INPUT) };
+
+            if (oldMouseMode != newMouseMode)
+            {
+                LOG_IF_FAILED(gci.GetVtIo()->RequestMouseMode(newMouseMode));
+            }
         }
 
         context.InputMode = mode;
diff --git a/src/host/inputBuffer.cpp b/src/host/inputBuffer.cpp
index a4ed5000a89..3a4e556e440 100644
--- a/src/host/inputBuffer.cpp
+++ b/src/host/inputBuffer.cpp
@@ -25,8 +25,7 @@ using namespace Microsoft::Console;
 // Return Value:
 // - A new instance of InputBuffer
 InputBuffer::InputBuffer() :
-    InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE },
-    _pTtyConnection(nullptr)
+    InputMode{ INPUT_BUFFER_DEFAULT_INPUT_MODE }
 {
     // initialize buffer header
     fInComposition = false;
@@ -342,26 +341,6 @@ void InputBuffer::FlushAllButKeys()
     _storage.erase(newEnd, _storage.end());
 }
 
-void InputBuffer::SetTerminalConnection(_In_ Render::VtEngine* const pTtyConnection)
-{
-    this->_pTtyConnection = pTtyConnection;
-}
-
-void InputBuffer::PassThroughWin32MouseRequest(bool enable)
-{
-    if (_pTtyConnection)
-    {
-        if (enable)
-        {
-            LOG_IF_FAILED(_pTtyConnection->WriteTerminalW(L"\x1b[?1003;1006h"));
-        }
-        else
-        {
-            LOG_IF_FAILED(_pTtyConnection->WriteTerminalW(L"\x1b[?1003;1006l"));
-        }
-    }
-}
-
 // Routine Description:
 // - This routine reads from the input buffer.
 // - It can convert returned data to through the currently set Input CP, it can optionally return a wait condition
diff --git a/src/host/inputBuffer.hpp b/src/host/inputBuffer.hpp
index 0044cb89d27..ec7cd75b082 100644
--- a/src/host/inputBuffer.hpp
+++ b/src/host/inputBuffer.hpp
@@ -63,8 +63,6 @@ class InputBuffer final : public ConsoleObjectHeader
 
     bool IsInVirtualTerminalInputMode() const;
     Microsoft::Console::VirtualTerminal::TerminalInput& GetTerminalInput();
-    void SetTerminalConnection(_In_ Microsoft::Console::Render::VtEngine* const pTtyConnection);
-    void PassThroughWin32MouseRequest(bool enable);
 
 private:
     enum class ReadingMode : uint8_t
@@ -86,7 +84,6 @@ class InputBuffer final : public ConsoleObjectHeader
     INPUT_RECORD _writePartialByteSequence{};
     bool _writePartialByteSequenceAvailable = false;
     Microsoft::Console::VirtualTerminal::TerminalInput _termInput;
-    Microsoft::Console::Render::VtEngine* _pTtyConnection;
 
     // This flag is used in _HandleTerminalInputCallback
     // If the InputBuffer leads to a _HandleTerminalInputCallback call,
diff --git a/src/renderer/vt/XtermEngine.cpp b/src/renderer/vt/XtermEngine.cpp
index c1a0d838852..14822351585 100644
--- a/src/renderer/vt/XtermEngine.cpp
+++ b/src/renderer/vt/XtermEngine.cpp
@@ -555,7 +555,8 @@ CATCH_RETURN();
     {
         RETURN_IF_FAILED(_Write("\x1b[2t"));
     }
-    return _Flush();
+    _Flush();
+    return S_OK;
 }
 
 // Method Description:
diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp
index 1c7f3fe498f..8969b04a39d 100644
--- a/src/renderer/vt/invalidate.cpp
+++ b/src/renderer/vt/invalidate.cpp
@@ -120,12 +120,7 @@ CATCH_RETURN();
         _circled = circled;
     }
 
-    // If we flushed for any reason other than circling (i.e, a sequence that we
-    // didn't understand), we don't need to push the buffer out on EndPaint.
-    _noFlushOnEnd = !circled;
-
     _trace.TraceTriggerCircling(*pForcePaint);
-
     return S_OK;
 }
 
diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp
index d261b1c1624..801390085c0 100644
--- a/src/renderer/vt/paint.cpp
+++ b/src/renderer/vt/paint.cpp
@@ -94,22 +94,7 @@ using namespace Microsoft::Console::Types;
         RETURN_IF_FAILED(_MoveCursor(_deferredCursorPos));
     }
 
-    // If this frame was triggered because we encountered a VT sequence which
-    // required the buffered state to get printed, we don't want to flush this
-    // frame to the pipe. That might result in us rendering half the output of a
-    // particular frame (as emitted by the client).
-    //
-    // Instead, we'll leave this frame in _buffer, and just keep appending to
-    // it as needed.
-    if (_noFlushOnEnd) [[unlikely]]
-    {
-        _noFlushOnEnd = false;
-    }
-    else
-    {
-        RETURN_IF_FAILED(_Flush());
-    }
-
+    _Flush();
     return S_OK;
 }
 
diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp
index d5db02afba9..b360b7b93a6 100644
--- a/src/renderer/vt/state.cpp
+++ b/src/renderer/vt/state.cpp
@@ -46,7 +46,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
     _circled(false),
     _firstPaint(true),
     _skipCursor(false),
-    _exitResult{ S_OK },
     _terminalOwner{ nullptr },
     _newBottomLine{ false },
     _deferredCursorPos{ INVALID_COORDS },
@@ -136,25 +135,39 @@ CATCH_RETURN();
     CATCH_RETURN();
 }
 
-[[nodiscard]] HRESULT VtEngine::_Flush() noexcept
+void VtEngine::_Flush() noexcept
+{
+    if (!_corked && !_buffer.empty())
+    {
+        _flushImpl();
+    }
+}
+
+// _corked is often true and separating _flushImpl() out allows _flush() to be inlined.
+void VtEngine::_flushImpl() noexcept
 {
     if (_hFile)
     {
-        auto fSuccess = !!WriteFile(_hFile.get(), _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), nullptr, nullptr);
+        const auto fSuccess = WriteFile(_hFile.get(), _buffer.data(), gsl::narrow_cast<DWORD>(_buffer.size()), nullptr, nullptr);
         _buffer.clear();
         if (!fSuccess)
         {
-            _exitResult = HRESULT_FROM_WIN32(GetLastError());
+            LOG_LAST_ERROR();
             _hFile.reset();
             if (_terminalOwner)
             {
                 _terminalOwner->CloseOutput();
             }
-            return _exitResult;
         }
     }
+}
 
-    return S_OK;
+// The name of this method is an analogy to TCP_CORK. It instructs
+// the VT renderer to stop flushing its buffer to the output pipe.
+// Don't forget to uncork it!
+void VtEngine::Cork(bool corked) noexcept
+{
+    _corked = corked;
 }
 
 // Method Description:
@@ -425,7 +438,7 @@ void VtEngine::SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const
 HRESULT VtEngine::RequestCursor() noexcept
 {
     RETURN_IF_FAILED(_RequestCursor());
-    RETURN_IF_FAILED(_Flush());
+    _Flush();
     return S_OK;
 }
 
@@ -546,13 +559,20 @@ HRESULT VtEngine::RequestWin32Input() noexcept
     // in the connected terminal after passing through an RIS sequence.
     RETURN_IF_FAILED(_RequestWin32Input());
     RETURN_IF_FAILED(_RequestFocusEventMode());
-    RETURN_IF_FAILED(_Flush());
+    _Flush();
     return S_OK;
 }
 
 HRESULT VtEngine::SwitchScreenBuffer(const bool useAltBuffer) noexcept
 {
     RETURN_IF_FAILED(_SwitchScreenBuffer(useAltBuffer));
-    RETURN_IF_FAILED(_Flush());
+    _Flush();
     return S_OK;
 }
+
+HRESULT VtEngine::RequestMouseMode(const bool enable) noexcept
+{
+    const auto status = _WriteFormatted(FMT_COMPILE("\x1b[?1003;1006{}"), enable ? 'h' : 'l');
+    _Flush();
+    return status;
+}
diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp
index a9f2c76cf93..208918407da 100644
--- a/src/renderer/vt/vtrenderer.hpp
+++ b/src/renderer/vt/vtrenderer.hpp
@@ -90,6 +90,8 @@ namespace Microsoft::Console::Render
         [[nodiscard]] HRESULT RequestWin32Input() noexcept;
         [[nodiscard]] virtual HRESULT SetWindowVisibility(const bool showOrHide) noexcept = 0;
         [[nodiscard]] HRESULT SwitchScreenBuffer(const bool useAltBuffer) noexcept;
+        [[nodiscard]] HRESULT RequestMouseMode(bool enable) noexcept;
+        void Cork(bool corked) noexcept;
 
     protected:
         wil::unique_hfile _hFile;
@@ -127,7 +129,6 @@ namespace Microsoft::Console::Render
         bool _newBottomLine;
         til::point _deferredCursorPos;
 
-        HRESULT _exitResult;
         Microsoft::Console::VirtualTerminal::VtIo* _terminalOwner;
 
         Microsoft::Console::VirtualTerminal::RenderTracing _trace;
@@ -139,12 +140,13 @@ namespace Microsoft::Console::Render
 
         bool _resizeQuirk{ false };
         bool _passthrough{ false };
-        bool _noFlushOnEnd{ false };
+        bool _corked{ false };
         std::optional<TextColor> _newBottomLineBG{ std::nullopt };
 
         [[nodiscard]] HRESULT _WriteFill(const size_t n, const char c) noexcept;
         [[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
-        [[nodiscard]] HRESULT _Flush() noexcept;
+        void _Flush() noexcept;
+        void _flushImpl() noexcept;
 
         template<typename S, typename... Args>
         [[nodiscard]] HRESULT _WriteFormatted(S&& format, Args&&... args)


Test patch: