Skip to content

Commit

Permalink
fix mouse events in the wpf control (#4720)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ZoeyR authored Feb 28, 2020
1 parent 161fe60 commit 4393fef
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 59 deletions.
247 changes: 210 additions & 37 deletions src/cascadia/PublicTerminalCore/HwndTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "pch.h"
#include "HwndTerminal.hpp"
#include <windowsx.h>
#include "../../types/TermControlUiaProvider.hpp"
#include <DefaultSettings.h>
#include "../../renderer/base/Renderer.hpp"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;
Expand All @@ -139,27 +168,24 @@ void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> 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<wchar_t*>(::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
Expand Down Expand Up @@ -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<const HwndTerminal*>(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<const HwndTerminal*>(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)
{
Expand Down Expand Up @@ -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<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([]() 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<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
{
// Get paste data from clipboard
if (!OpenClipboard(_hwnd.get()))
{
return;
}

HANDLE ClipboardDataHandle = GetClipboardData(CF_UNICODETEXT);
if (ClipboardDataHandle == nullptr)
{
CloseClipboard();
return;
}

PCWCH pwstr = static_cast<PCWCH>(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();
Expand Down
15 changes: 10 additions & 5 deletions src/cascadia/PublicTerminalCore/HwndTerminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -69,7 +67,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
FontInfo _actualFont;
int _currentDpi;
bool _uiaProviderInitialized;

std::function<void(wchar_t*)> _pfnWriteCallback;
::Microsoft::WRL::ComPtr<::Microsoft::Terminal::TermControlUiaProvider> _uiaProvider;

std::unique_ptr<::Microsoft::Terminal::Core::Terminal> _terminal;
Expand All @@ -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);
Expand All @@ -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
Expand Down
17 changes: 0 additions & 17 deletions src/cascadia/WpfTerminalControl/TerminalContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 4393fef

Please sign in to comment.