From a6c14fc1be7e04c3086c838a8e0fda4022fde29f Mon Sep 17 00:00:00 2001 From: Zoey Riordan Date: Tue, 25 Feb 2020 13:37:49 -0800 Subject: [PATCH] fix mouse events in the wpf control --- .../PublicTerminalCore/HwndTerminal.cpp | 275 +++++++++++++++--- .../PublicTerminalCore/HwndTerminal.hpp | 15 +- .../WpfTerminalControl/TerminalContainer.cs | 17 -- 3 files changed, 250 insertions(+), 57 deletions(-) diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index 09d65c13fc1..a165450b694 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,36 @@ 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 ((wParam & 0x0001) == 1) + { + 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_HR(wil::ResultFromCaughtException()); + } + } + else + { + terminal->_PasteTextFromClipboard(); + } + return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); @@ -66,6 +97,7 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) : _uiaProvider{ nullptr }, _uiaProviderInitialized{ false }, _currentDpi{ USER_DEFAULT_SCREEN_DPI } +//_pfnWriteCallback{ nullptr } { HINSTANCE hInstance = wil::GetModuleInstanceHandle(); @@ -128,7 +160,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) { _WriteTextToConnection(input); }); localPointerToThread->EnablePainting(); return S_OK; @@ -139,27 +171,35 @@ void HwndTerminal::RegisterScrollCallback(std::function cal _terminal->SetScrollPositionChangedCallback(callback); } -void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*)) +void HwndTerminal::_WriteTextToConnection(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; + if (!_pfnWriteCallback) + { + return; + } - callingText = static_cast(::CoTaskMemAlloc(textBytes)); + 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; - if (callingText == nullptr) - { - callback(nullptr); - } - else - { - wcscpy_s(callingText, textChars, text); + callingText = static_cast(::CoTaskMemAlloc(textBytes)); - callback(callingText); - } - }); + if (callingText == nullptr) + { + _pfnWriteCallback(nullptr); + } + else + { + wcscpy_s(callingText, textChars, text); + + _pfnWriteCallback(callingText); + } +} + +void HwndTerminal::RegisterWriteCallback(const void _stdcall callback(wchar_t*)) +{ + _pfnWriteCallback = callback; } ::Microsoft::Console::Types::IUiaData* HwndTerminal::GetUiaData() const noexcept @@ -320,42 +360,61 @@ 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 { - COORD terminalPosition = { cursorPosition }; + 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->SetBoxSelection(altPressed); + try + { + this->_terminal->SetSelectionAnchor(cursorPosition); + this->_terminal->SetBoxSelection(altPressed); - publicTerminal->_renderer->TriggerSelection(); + this->_renderer->TriggerSelection(); + } + catch (...) + { + RETURN_HR(wil::ResultFromCaughtException()); + } return S_OK; } -HRESULT _stdcall TerminalMoveSelection(void* terminal, COORD cursorPosition) +HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept { - 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->SetEndSelectionPosition(terminalPosition); - publicTerminal->_renderer->TriggerSelection(); + try + { + this->_terminal->SetEndSelectionPosition(cursorPosition); + this->_renderer->TriggerSelection(); + } + catch (...) + { + RETURN_HR(wil::ResultFromCaughtException()); + } return S_OK; } @@ -501,6 +560,152 @@ 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) noexcept +{ + 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 = (PWSTR)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([]() { + 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) noexcept +{ + 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 = (PSTR)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 +{ + HANDLE ClipboardDataHandle; + + // Clear any selection or scrolling that may be active. + _terminal->ClearSelection(); + + // Get paste data from clipboard + if (!OpenClipboard(_hwnd.get())) + { + return; + } + + ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT); + if (ClipboardDataHandle == nullptr) + { + CloseClipboard(); + return; + } + + PWCHAR pwstr = (PWCHAR)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_HR(wil::ResultFromCaughtException()); + } +} + COORD HwndTerminal::GetFontSize() const { return _actualFont.GetSize(); diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp index c5e1e236782..d5ac3f4c2b2 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(std::wstring& text) noexcept; + HRESULT _CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting) noexcept; + HRESULT _CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) noexcept; + 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);