wpf: use the new TSF implementation (#18861)

This fixes two issues in the WPF terminal control:
- The emoji picker and other IME candidate windows didn't show up in the
right place
- Submitting an emoji via the emoji picker would result in two win32
input mode events with a VK of 65535 and the surrogate pair halves.

I am not sure I did the right thing with the thread TSF handle...

(cherry picked from commit 06f736bebe84eda0c34b935a875eebe031a899b7)
Service-Card-Id: PVTI_lADOAF3p4s4AxadtzgZ-Sw0
Service-Version: 1.23
This commit is contained in:
Dustin L. Howett 2025-05-02 18:30:52 -05:00 committed by Dustin L. Howett
parent 5ddbb9897b
commit c2f9191fc3
2 changed files with 106 additions and 0 deletions

View File

@ -19,6 +19,79 @@ using namespace ::Microsoft::Terminal::Core;
static LPCWSTR term_window_class = L"HwndTerminalClass";
STDMETHODIMP HwndTerminal::TsfDataProvider::QueryInterface(REFIID, void**) noexcept
{
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE HwndTerminal::TsfDataProvider::AddRef() noexcept
{
return 1;
}
ULONG STDMETHODCALLTYPE HwndTerminal::TsfDataProvider::Release() noexcept
{
return 1;
}
HWND HwndTerminal::TsfDataProvider::GetHwnd()
{
return _terminal->GetHwnd();
}
RECT HwndTerminal::TsfDataProvider::GetViewport()
{
const auto hwnd = GetHwnd();
RECT rc;
GetClientRect(hwnd, &rc);
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclientrect
// > The left and top members are zero. The right and bottom members contain the width and height of the window.
// --> We can turn the client rect into a screen-relative rect by adding the left/top position.
ClientToScreen(hwnd, reinterpret_cast<POINT*>(&rc));
rc.right += rc.left;
rc.bottom += rc.top;
return rc;
}
RECT HwndTerminal::TsfDataProvider::GetCursorPosition()
{
// Convert from columns/rows to pixels.
til::point cursorPos;
til::size fontSize;
{
const auto lock = _terminal->_terminal->LockForReading();
cursorPos = _terminal->_terminal->GetCursorPosition(); // measured in terminal cells
fontSize = _terminal->_actualFont.GetSize(); // measured in pixels, not DIP
}
POINT ptSuggestion = {
.x = cursorPos.x * fontSize.width,
.y = cursorPos.y * fontSize.height,
};
ClientToScreen(GetHwnd(), &ptSuggestion);
// Final measurement should be in pixels
return {
.left = ptSuggestion.x,
.top = ptSuggestion.y,
.right = ptSuggestion.x + fontSize.width,
.bottom = ptSuggestion.y + fontSize.height,
};
}
void HwndTerminal::TsfDataProvider::HandleOutput(std::wstring_view text)
{
_terminal->_WriteTextToConnection(text);
}
Microsoft::Console::Render::Renderer* HwndTerminal::TsfDataProvider::GetRenderer()
{
return _terminal->_renderer.get();
}
// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx
// "If the high-order bit is 1, the key is down; otherwise, it is up."
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
@ -242,6 +315,7 @@ try
{
// As a rule, detach resources from the Terminal before shutting them down.
// This ensures that teardown is reentrant.
_tsfHandle = {};
// Shut down the renderer (and therefore the thread) before we implode
_renderer.reset();
@ -941,6 +1015,16 @@ void __stdcall TerminalSetFocus(void* terminal)
{
LOG_IF_FAILED(uiaEngine->Enable());
}
publicTerminal->_FocusTSF();
}
void HwndTerminal::_FocusTSF() noexcept
{
if (!_tsfHandle)
{
_tsfHandle = Microsoft::Console::TSF::Handle::Create();
_tsfHandle.AssociateFocus(&_tsfDataProvider);
}
}
void __stdcall TerminalKillFocus(void* terminal)

View File

@ -6,6 +6,7 @@
#include "../../buffer/out/textBuffer.hpp"
#include "../../renderer/inc/FontInfoDesired.hpp"
#include "../../types/IControlAccessibilityInfo.h"
#include "../../tsf/Handle.h"
namespace Microsoft::Console::Render::Atlas
{
@ -85,6 +86,21 @@ public:
static LRESULT CALLBACK HwndTerminalWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
private:
struct TsfDataProvider : public Microsoft::Console::TSF::IDataProvider
{
TsfDataProvider(HwndTerminal* t) :
_terminal(t) {}
virtual ~TsfDataProvider() = default;
STDMETHODIMP TsfDataProvider::QueryInterface(REFIID, void**) noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::AddRef() noexcept override;
ULONG STDMETHODCALLTYPE TsfDataProvider::Release() noexcept override;
HWND GetHwnd() override;
RECT GetViewport() override;
RECT GetCursorPosition() override;
void HandleOutput(std::wstring_view text) override;
Microsoft::Console::Render::Renderer* GetRenderer() override;
HwndTerminal* _terminal;
};
wil::unique_hwnd _hwnd;
FontInfoDesired _desiredFont;
FontInfo _actualFont;
@ -106,6 +122,10 @@ private:
std::optional<til::point> _lastMouseClickPos;
std::optional<til::point> _singleClickTouchdownPos;
// _tsfHandle uses _tsfDataProvider. Destructors run from bottom to top; this maintains correct destruction order.
TsfDataProvider _tsfDataProvider{ this };
Microsoft::Console::TSF::Handle _tsfHandle;
friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
friend HRESULT _stdcall TerminalTriggerResize(_In_ void* terminal, _In_ til::CoordType width, _In_ til::CoordType height, _Out_ til::size* dimensions);
friend HRESULT _stdcall TerminalTriggerResizeWithDimension(_In_ void* terminal, _In_ til::size dimensions, _Out_ til::size* dimensionsInPixels);
@ -129,6 +149,8 @@ private:
HRESULT _CopyToSystemClipboard(const std::string& stringToCopy, LPCWSTR lpszFormat) const;
void _PasteTextFromClipboard() noexcept;
void _FocusTSF() noexcept;
const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept;
HRESULT _StartSelection(LPARAM lParam) noexcept;
HRESULT _MoveSelection(LPARAM lParam) noexcept;