From 4393fefb7130c5f54bc7dd8bfed97f82afb4e98c Mon Sep 17 00:00:00 2001 From: Zoey Riordan Date: Fri, 28 Feb 2020 14:46:35 -0800 Subject: [PATCH] fix mouse events in the wpf control (#4720) PR #4548 inadvertantly broke mouse button input in the WPF control. This happened due to the extra layer of HWND indirection. The fix is to move the mouse button handling down into the native control where the window messages are now being sent. --- .../PublicTerminalCore/HwndTerminal.cpp | 247 +++++++++++++++--- .../PublicTerminalCore/HwndTerminal.hpp | 15 +- .../WpfTerminalControl/TerminalContainer.cs | 17 -- 3 files changed, 220 insertions(+), 59 deletions(-) diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index 8c574934104..ee7d495c0d7 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "HwndTerminal.hpp" +#include #include "../../types/TermControlUiaProvider.hpp" #include #include "../../renderer/base/Renderer.hpp" @@ -33,6 +34,33 @@ LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc( { return UiaReturnRawElementProvider(hwnd, wParam, lParam, terminal->_GetUiaProvider()); } + break; + case WM_LBUTTONDOWN: + LOG_IF_FAILED(terminal->_StartSelection(lParam)); + return 0; + case WM_MOUSEMOVE: + if (WI_IsFlagSet(wParam, MK_LBUTTON)) + { + LOG_IF_FAILED(terminal->_MoveSelection(lParam)); + return 0; + } + break; + case WM_RBUTTONDOWN: + if (terminal->_terminal->IsSelectionActive()) + { + try + { + const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false); + LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true)); + terminal->_terminal->ClearSelection(); + } + CATCH_LOG(); + } + else + { + terminal->_PasteTextFromClipboard(); + } + return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); @@ -65,7 +93,8 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) : _actualFont{ DEFAULT_FONT_FACE, 0, 10, { 0, 14 }, CP_UTF8, false }, _uiaProvider{ nullptr }, _uiaProviderInitialized{ false }, - _currentDpi{ USER_DEFAULT_SCREEN_DPI } + _currentDpi{ USER_DEFAULT_SCREEN_DPI }, + _pfnWriteCallback{ nullptr } { HINSTANCE hInstance = wil::GetModuleInstanceHandle(); @@ -128,7 +157,7 @@ HRESULT HwndTerminal::Initialize() _terminal->Create(COORD{ 80, 25 }, 1000, *_renderer); _terminal->SetDefaultBackground(RGB(5, 27, 80)); _terminal->SetDefaultForeground(RGB(255, 255, 255)); - + _terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); }); localPointerToThread->EnablePainting(); return S_OK; @@ -139,27 +168,24 @@ void HwndTerminal::RegisterScrollCallback(std::function cal _terminal->SetScrollPositionChangedCallback(callback); } -void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*)) +void HwndTerminal::_WriteTextToConnection(const std::wstring& input) noexcept { - _terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { - const wchar_t* text = input.c_str(); - const size_t textChars = wcslen(text) + 1; - const size_t textBytes = textChars * sizeof(wchar_t); - wchar_t* callingText = nullptr; - - callingText = static_cast(::CoTaskMemAlloc(textBytes)); + if (!_pfnWriteCallback) + { + return; + } - if (callingText == nullptr) - { - callback(nullptr); - } - else - { - wcscpy_s(callingText, textChars, text); + try + { + auto callingText{ wil::make_cotaskmem_string(input.data(), input.size()) }; + _pfnWriteCallback(callingText.release()); + } + CATCH_LOG(); +} - callback(callingText); - } - }); +void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*)) +{ + _pfnWriteCallback = callback; } ::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept @@ -320,45 +346,54 @@ void _stdcall TerminalUserScroll(void* terminal, int viewTop) publicTerminal->_terminal->UserScrollViewport(viewTop); } -HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed) +HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept +try { - COORD terminalPosition = { cursorPosition }; + const bool altPressed = GetKeyState(VK_MENU) < 0; + COORD cursorPosition{ + GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam), + }; - const auto publicTerminal = static_cast(terminal); - const auto fontSize = publicTerminal->_actualFont.GetSize(); + const auto fontSize = this->_actualFont.GetSize(); RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0); RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0); - terminalPosition.X /= fontSize.X; - terminalPosition.Y /= fontSize.Y; + cursorPosition.X /= fontSize.X; + cursorPosition.Y /= fontSize.Y; - publicTerminal->_terminal->SetSelectionAnchor(terminalPosition); - publicTerminal->_terminal->SetBlockSelection(altPressed); + this->_terminal->SetSelectionAnchor(cursorPosition); + this->_terminal->SetBlockSelection(altPressed); - publicTerminal->_renderer->TriggerSelection(); + this->_renderer->TriggerSelection(); return S_OK; } +CATCH_RETURN(); -HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition) +HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept +try { - COORD terminalPosition = { cursorPosition }; + COORD cursorPosition{ + GET_X_LPARAM(lParam), + GET_Y_LPARAM(lParam), + }; - const auto publicTerminal = static_cast(terminal); - const auto fontSize = publicTerminal->_actualFont.GetSize(); + const auto fontSize = this->_actualFont.GetSize(); RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0); RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0); - terminalPosition.X /= fontSize.X; - terminalPosition.Y /= fontSize.Y; + cursorPosition.X /= fontSize.X; + cursorPosition.Y /= fontSize.Y; - publicTerminal->_terminal->SetSelectionEnd(terminalPosition); - publicTerminal->_renderer->TriggerSelection(); + this->_terminal->SetSelectionEnd(cursorPosition); + this->_renderer->TriggerSelection(); return S_OK; } +CATCH_RETURN(); void _stdcall TerminalClearSelection(void* terminal) { @@ -501,6 +536,144 @@ void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible) publicTerminal->_terminal->SetCursorVisible(visible); } +// Routine Description: +// - Copies the text given onto the global system clipboard. +// Arguments: +// - rows - Rows of text data to copy +// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise +HRESULT HwndTerminal::_CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting) +{ + std::wstring finalString; + + // Concatenate strings into one giant string to put onto the clipboard. + for (const auto& str : rows.text) + { + finalString += str; + } + + // allocate the final clipboard data + const size_t cchNeeded = finalString.size() + 1; + const size_t cbNeeded = sizeof(wchar_t) * cchNeeded; + wil::unique_hglobal globalHandle(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeeded)); + RETURN_LAST_ERROR_IF_NULL(globalHandle.get()); + + PWSTR pwszClipboard = static_cast(GlobalLock(globalHandle.get())); + RETURN_LAST_ERROR_IF_NULL(pwszClipboard); + + // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. + // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). + const HRESULT hr = StringCchCopyW(pwszClipboard, cchNeeded, finalString.data()); + GlobalUnlock(globalHandle.get()); + RETURN_IF_FAILED(hr); + + // Set global data to clipboard + RETURN_LAST_ERROR_IF(!OpenClipboard(_hwnd.get())); + + { // Clipboard Scope + auto clipboardCloser = wil::scope_exit([]() noexcept { + LOG_LAST_ERROR_IF(!CloseClipboard()); + }); + + RETURN_LAST_ERROR_IF(!EmptyClipboard()); + RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get())); + + if (fAlsoCopyFormatting) + { + const auto& fontData = _actualFont; + int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / this->_currentDpi; + const COLORREF bgColor = _terminal->GetBackgroundColor(_terminal->GetDefaultBrushColors()); + + std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor, "Hwnd Console Host"); + _CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format"); + + std::string RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); + _CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format"); + } + } + + // only free if we failed. + // the memory has to remain allocated if we successfully placed it on the clipboard. + // Releasing the smart pointer will leave it allocated as we exit scope. + globalHandle.release(); + + return S_OK; +} + +// Routine Description: +// - Copies the given string onto the global system clipboard in the specified format +// Arguments: +// - stringToCopy - The string to copy +// - lpszFormat - the name of the format +HRESULT HwndTerminal::_CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) +{ + const size_t cbData = stringToCopy.size() + 1; // +1 for '\0' + if (cbData) + { + wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData)); + RETURN_LAST_ERROR_IF_NULL(globalHandleData.get()); + + PSTR pszClipboardHTML = static_cast(GlobalLock(globalHandleData.get())); + RETURN_LAST_ERROR_IF_NULL(pszClipboardHTML); + + // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. + // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). + const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data()); + GlobalUnlock(globalHandleData.get()); + RETURN_IF_FAILED(hr2); + + UINT const CF_FORMAT = RegisterClipboardFormatW(lpszFormat); + RETURN_LAST_ERROR_IF(0 == CF_FORMAT); + + RETURN_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get())); + + // only free if we failed. + // the memory has to remain allocated if we successfully placed it on the clipboard. + // Releasing the smart pointer will leave it allocated as we exit scope. + globalHandleData.release(); + } + + return S_OK; +} + +void HwndTerminal::_PasteTextFromClipboard() noexcept +{ + // Get paste data from clipboard + if (!OpenClipboard(_hwnd.get())) + { + return; + } + + HANDLE ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT); + if (ClipboardDataHandle == nullptr) + { + CloseClipboard(); + return; + } + + PCWCH pwstr = static_cast(GlobalLock(ClipboardDataHandle)); + + _StringPaste(pwstr); + + GlobalUnlock(ClipboardDataHandle); + + CloseClipboard(); +} + +void HwndTerminal::_StringPaste(const wchar_t* const pData) noexcept +{ + if (pData == nullptr) + { + return; + } + + try + { + std::wstring text(pData); + _WriteTextToConnection(text); + } + CATCH_LOG(); +} + COORD HwndTerminal::GetFontSize() const { return _actualFont.GetSize(); diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp index c5e1e236782..172a462b898 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp @@ -28,8 +28,6 @@ __declspec(dllexport) HRESULT _stdcall TerminalTriggerResize(void* terminal, dou __declspec(dllexport) HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions); __declspec(dllexport) void _stdcall TerminalDpiChanged(void* terminal, int newDpi); __declspec(dllexport) void _stdcall TerminalUserScroll(void* terminal, int viewTop); -__declspec(dllexport) HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed); -__declspec(dllexport) HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition); __declspec(dllexport) void _stdcall TerminalClearSelection(void* terminal); __declspec(dllexport) const wchar_t* _stdcall TerminalGetSelection(void* terminal); __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal); @@ -69,7 +67,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo FontInfo _actualFont; int _currentDpi; bool _uiaProviderInitialized; - + std::function _pfnWriteCallback; ::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider; std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal; @@ -81,8 +79,6 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions); friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi); friend void _stdcall TerminalUserScroll(void* terminal, int viewTop); - friend HRESULT _stdcall TerminalStartSelection(void* terminal, COORD cursorPosition, bool altPressed); - friend HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition); friend void _stdcall TerminalClearSelection(void* terminal); friend const wchar_t* _stdcall TerminalGetSelection(void* terminal); friend bool _stdcall TerminalIsSelectionActive(void* terminal); @@ -91,7 +87,16 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi); friend void _stdcall TerminalBlinkCursor(void* terminal); friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible); + void _UpdateFont(int newDpi); + void _WriteTextToConnection(const std::wstring& text) noexcept; + HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting); + HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat); + void _PasteTextFromClipboard() noexcept; + void _StringPaste(const wchar_t* const pData) noexcept; + + HRESULT _StartSelection(LPARAM lParam) noexcept; + HRESULT _MoveSelection(LPARAM lParam) noexcept; IRawElementProviderSimple* _GetUiaProvider() noexcept; // Inherited via IControlAccessibilityInfo diff --git a/src/cascadia/WpfTerminalControl/TerminalContainer.cs b/src/cascadia/WpfTerminalControl/TerminalContainer.cs index b447108225c..e87a66101b1 100644 --- a/src/cascadia/WpfTerminalControl/TerminalContainer.cs +++ b/src/cascadia/WpfTerminalControl/TerminalContainer.cs @@ -230,23 +230,6 @@ private IntPtr TerminalContainer_MessageHook(IntPtr hwnd, int msg, IntPtr wParam this.Focus(); NativeMethods.SetFocus(this.hwnd); break; - case NativeMethods.WindowMessage.WM_LBUTTONDOWN: - this.LeftClickHandler((int)lParam); - break; - case NativeMethods.WindowMessage.WM_RBUTTONDOWN: - if (NativeMethods.TerminalIsSelectionActive(this.terminal)) - { - Clipboard.SetText(NativeMethods.TerminalGetSelection(this.terminal)); - } - else - { - this.connection.WriteInput(Clipboard.GetText()); - } - - break; - case NativeMethods.WindowMessage.WM_MOUSEMOVE: - this.MouseMoveHandler((int)wParam, (int)lParam); - break; case NativeMethods.WindowMessage.WM_KEYDOWN: NativeMethods.TerminalSetCursorVisible(this.terminal, true); NativeMethods.TerminalClearSelection(this.terminal);