Skip to content

Commit

Permalink
Synthesize VT mouse events and add mouse support to Terminal (#4859)
Browse files Browse the repository at this point in the history
## Summary of the Pull Request
Make TerminalControl synthesize mouse events and Terminal send them to
the TerminalInput's MouseInput module.

The implementation here takes significant inspiration from how we handle
KeyEvents.

## References
Closes #545 - VT Mouse Mode (Terminal)
References #376 - VT Mouse Mode (ConPty)

### TerminalControl
- `_TrySendMouseEvent` attempts to send a mouse event via TermInput.
  Similar to `_TrySendKeyEvent`
- Use the above function to try and send the mouse event _before_
  deciding to modify the selection

### TerminalApi
- Hookup (re)setting the various modes to handle VT Input
- Terminal is _always_ in VT Input mode (important for #4856)

### TerminalDispatch
- Hookup (re)setting the various modes to handle VT Input

### TerminalInput
- Convert the mouse input position from viewport position to buffer
  position
- Then send it over to the MouseInput in TerminalInput to actually do it
  (#4848)

## Validation Steps Performed
Tests should still pass.
  • Loading branch information
carlos-zamora authored Mar 13, 2020
1 parent 93b31f6 commit ae71dce
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ using namespace winrt::Windows::UI::Xaml::Input;
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::ViewManagement;
using namespace winrt::Windows::UI::Input;
using namespace winrt::Windows::System;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace winrt::Windows::ApplicationModel::DataTransfer;
Expand Down Expand Up @@ -812,6 +813,72 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
e.Handled(true);
}

// Method Description:
// - Send this particular mouse event to the terminal.
// See Terminal::SendMouseEvent for more information.
// Arguments:
// - point: the PointerPoint object representing a mouse event from our XAML input handler
bool TermControl::_TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point)
{
const auto props = point.Properties();

// Get the terminal position relative to the viewport
const auto terminalPosition = _GetTerminalPosition(point.Position());

// Which mouse button changed state (and how)
unsigned int uiButton{};
switch (props.PointerUpdateKind())
{
case PointerUpdateKind::LeftButtonPressed:
uiButton = WM_LBUTTONDOWN;
break;
case PointerUpdateKind::LeftButtonReleased:
uiButton = WM_LBUTTONUP;
break;
case PointerUpdateKind::MiddleButtonPressed:
uiButton = WM_MBUTTONDOWN;
break;
case PointerUpdateKind::MiddleButtonReleased:
uiButton = WM_MBUTTONUP;
break;
case PointerUpdateKind::RightButtonPressed:
uiButton = WM_RBUTTONDOWN;
break;
case PointerUpdateKind::RightButtonReleased:
uiButton = WM_RBUTTONUP;
break;
default:
uiButton = WM_MOUSEMOVE;
}

// Mouse wheel data
const short sWheelDelta = ::base::saturated_cast<short>(props.MouseWheelDelta());
if (sWheelDelta != 0 && !props.IsHorizontalMouseWheel())
{
// if we have a mouse wheel delta and it wasn't a horizontal wheel motion
uiButton = WM_MOUSEWHEEL;
}

const auto modifiers = _GetPressedModifierKeys();
return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta);
}

// Method Description:
// - Checks if we can send vt mouse input.
// Arguments:
// - point: the PointerPoint object representing a mouse event from our XAML input handler
bool TermControl::_CanSendVTMouseInput()
{
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
const auto modifiers = _GetPressedModifierKeys();
if (modifiers.IsShiftPressed())
{
return false;
}
return _terminal->IsTrackingMouseInput();
}

// Method Description:
// - handle a mouse click event. Begin selection process.
// Arguments:
Expand Down Expand Up @@ -847,6 +914,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto altEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Menu));
const auto shiftEnabled = WI_IsFlagSet(modifiers, static_cast<uint32_t>(VirtualKeyModifiers::Shift));

if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}

if (point.Properties().IsLeftButtonPressed())
{
// A single left click from out of focus should only focus.
Expand Down Expand Up @@ -927,8 +1001,21 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto ptr = args.Pointer();
const auto point = args.GetCurrentPoint(*this);

if (!_focused)
{
args.Handled(true);
return;
}

if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}

if (point.Properties().IsLeftButtonPressed())
{
_clickDrag = true;
Expand Down Expand Up @@ -1015,6 +1102,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse || ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Pen)
{
if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}

// Only a left click release when copy on select is active should perform a copy.
// Right clicks and middle clicks should not need to do anything when released.
if (_terminal->IsCopyOnSelectActive() && point.Properties().PointerUpdateKind() == Windows::UI::Input::PointerUpdateKind::LeftButtonReleased)
Expand Down Expand Up @@ -1056,6 +1150,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
Input::PointerRoutedEventArgs const& args)
{
const auto point = args.GetCurrentPoint(*this);

if (_CanSendVTMouseInput())
{
_TrySendMouseEvent(point);
args.Handled(true);
return;
}

const auto delta = point.Properties().MouseWheelDelta();

// Get the state of the Ctrl & Shift keys
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
bool _CanSendVTMouseInput();

const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ namespace Microsoft::Terminal::Core
virtual bool SetDefaultForeground(const DWORD color) noexcept = 0;
virtual bool SetDefaultBackground(const DWORD color) noexcept = 0;

virtual bool SetCursorKeysMode(const bool applicationMode) noexcept = 0;
virtual bool SetKeypadMode(const bool applicationMode) noexcept = 0;
virtual bool EnableVT200MouseMode(const bool enabled) noexcept = 0;
virtual bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableSGRExtendedMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableButtonEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAnyEventMouseMode(const bool enabled) noexcept = 0;
virtual bool EnableAlternateScrollMode(const bool enabled) noexcept = 0;

virtual bool IsVtInputEnabled() const = 0;

protected:
ITerminalApi() = default;
};
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/ITerminalInput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace Microsoft::Terminal::Core
ITerminalInput& operator=(ITerminalInput&&) = default;

virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
virtual bool SendCharEvent(const wchar_t ch) = 0;

// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
Expand Down
37 changes: 37 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,17 @@ void Terminal::TrySnapOnInput()
}
}

// Routine Description:
// - Relays if we are tracking mouse input
// Parameters:
// - <none>
// Return value:
// - true, if we are tracking mouse input. False, otherwise
bool Terminal::IsTrackingMouseInput() const noexcept
{
return _terminalInput->IsTrackingMouseInput();
}

// Method Description:
// - Send this particular key event to the terminal. The terminal will translate
// the key and the modifiers pressed into the appropriate VT sequence for that
Expand Down Expand Up @@ -403,6 +414,32 @@ bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlK
return translated && manuallyHandled;
}

// Method Description:
// - Send this particular mouse event to the terminal. The terminal will translate
// the button and the modifiers pressed into the appropriate VT sequence for that
// mouse event. If we do translate the key, we'll return true. In that case, the
// event should NOT be processed any further. If we return false, the event
// was NOT translated, and we should instead use the event normally
// Arguments:
// - viewportPos: the position of the mouse event relative to the viewport origin.
// - uiButton: the WM mouse button event code
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
// - wheelDelta: the amount that the scroll wheel changed (should be 0 unless button is a WM_MOUSE*WHEEL)
// Return Value:
// - true if we translated the key event, and it should not be processed any further.
// - false if we did not translate the key, and it should be processed into a character.
bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta)
{
// viewportPos must be within the dimensions of the viewport
const auto viewportDimensions = _mutableViewport.Dimensions();
if (viewportPos.X < 0 || viewportPos.X >= viewportDimensions.X || viewportPos.Y < 0 || viewportPos.Y >= viewportDimensions.Y)
{
return false;
}

return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
}

bool Terminal::SendCharEvent(const wchar_t ch)
{
return _terminalInput->HandleChar(ch);
Expand Down
13 changes: 13 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,31 @@ class Microsoft::Terminal::Core::Terminal final :
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;
bool SetDefaultForeground(const COLORREF color) noexcept override;
bool SetDefaultBackground(const COLORREF color) noexcept override;

bool SetCursorKeysMode(const bool applicationMode) noexcept override;
bool SetKeypadMode(const bool applicationMode) noexcept override;
bool EnableVT200MouseMode(const bool enabled) noexcept override;
bool EnableUTF8ExtendedMouseMode(const bool enabled) noexcept override;
bool EnableSGRExtendedMouseMode(const bool enabled) noexcept override;
bool EnableButtonEventMouseMode(const bool enabled) noexcept override;
bool EnableAnyEventMouseMode(const bool enabled) noexcept override;
bool EnableAlternateScrollMode(const bool enabled) noexcept override;

bool IsVtInputEnabled() const noexcept override;
#pragma endregion

#pragma region ITerminalInput
// These methods are defined in Terminal.cpp
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
bool SendCharEvent(const wchar_t ch) override;

[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
void UserScrollViewport(const int viewTop) override;
int GetScrollOffset() noexcept override;

void TrySnapOnInput() override;
bool IsTrackingMouseInput() const noexcept;
#pragma endregion

#pragma region IBaseData(base to IRenderData and IUiaData)
Expand Down
54 changes: 54 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,57 @@ try
return true;
}
CATCH_LOG_RETURN_FALSE()

bool Terminal::SetCursorKeysMode(const bool applicationMode) noexcept
{
_terminalInput->ChangeCursorKeysMode(applicationMode);
return true;
}

bool Terminal::SetKeypadMode(const bool applicationMode) noexcept
{
_terminalInput->ChangeKeypadMode(applicationMode);
return true;
}

bool Terminal::EnableVT200MouseMode(const bool enabled) noexcept
{
_terminalInput->EnableDefaultTracking(enabled);
return true;
}

bool Terminal::EnableUTF8ExtendedMouseMode(const bool enabled) noexcept
{
_terminalInput->SetUtf8ExtendedMode(enabled);
return true;
}

bool Terminal::EnableSGRExtendedMouseMode(const bool enabled) noexcept
{
_terminalInput->SetSGRExtendedMode(enabled);
return true;
}

bool Terminal::EnableButtonEventMouseMode(const bool enabled) noexcept
{
_terminalInput->EnableButtonEventTracking(enabled);
return true;
}

bool Terminal::EnableAnyEventMouseMode(const bool enabled) noexcept
{
_terminalInput->EnableAnyEventTracking(enabled);
return true;
}

bool Terminal::EnableAlternateScrollMode(const bool enabled) noexcept
{
_terminalInput->EnableAlternateScroll(enabled);
return true;
}

bool Terminal::IsVtInputEnabled() const noexcept
{
// We should never be getting this call in Terminal.
FAIL_FAST();
}
Loading

0 comments on commit ae71dce

Please sign in to comment.