Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass through double clicks and hover events in Win32 mouse mode #10138

Merged
15 commits merged into from
May 24, 2021
45 changes: 37 additions & 8 deletions src/terminal/parser/InputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispat
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
_pDispatch(std::move(pDispatch)),
_lookingForDSR(lookingForDSR),
_pfnFlushToInputQueue(nullptr)
_pfnFlushToInputQueue(nullptr),
_doubleClickTime(std::chrono::milliseconds(GetDoubleClickTime()))
DHowett marked this conversation as resolved.
Show resolved Hide resolved
{
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
}
Expand Down Expand Up @@ -386,9 +387,11 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
DWORD buttonState = 0;
DWORD eventFlags = 0;
const size_t firstParameter = parameters.at(0).value_or(0);
const til::point uiPos{ parameters.at(1) - 1, parameters.at(2) - 1 };
DHowett marked this conversation as resolved.
Show resolved Hide resolved

modifierState = _GetSGRMouseModifierState(firstParameter);
success = _UpdateSGRMouseButtonState(id, firstParameter, buttonState, eventFlags);
success = success && _WriteMouseEvent(parameters.at(1), parameters.at(2), buttonState, modifierState, eventFlags);
success = _UpdateSGRMouseButtonState(id, firstParameter, buttonState, eventFlags, uiPos);
success = success && _WriteMouseEvent(uiPos, buttonState, modifierState, eventFlags);
break;
}
// case CsiActionCodes::DSR_DeviceStatusReportResponse:
Expand Down Expand Up @@ -723,10 +726,8 @@ bool InputStateMachineEngine::_WriteSingleKey(const short vkey, const DWORD modi
// - eventFlags - the type of mouse event to write to the mouse record.
// Return Value:
// - true iff we successfully wrote the keypress to the input callback.
bool InputStateMachineEngine::_WriteMouseEvent(const size_t column, const size_t line, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags)
bool InputStateMachineEngine::_WriteMouseEvent(const til::point uiPos, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags)
{
COORD uiPos = { gsl::narrow<short>(column) - 1, gsl::narrow<short>(line) - 1 };

INPUT_RECORD rgInput;
rgInput.EventType = MOUSE_EVENT;
rgInput.Event.MouseEvent.dwMousePosition = uiPos;
Expand Down Expand Up @@ -846,7 +847,8 @@ DWORD InputStateMachineEngine::_GetModifier(const size_t modifierParam) noexcept
bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
const size_t sgrEncoding,
DWORD& buttonState,
DWORD& eventFlags) noexcept
DWORD& eventFlags,
const til::point uiPos)
{
// Starting with the state from the last mouse event we received
buttonState = _mouseButtonState;
Expand All @@ -862,7 +864,7 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
// This retrieves the 2 MSBs and concatenates them to the 2 LSBs to create BBBB in binary
// This represents which button had a change in state
const auto buttonID = (sgrEncoding & 0x3) | ((sgrEncoding & 0xC0) >> 4);

const auto currentTime = std::chrono::steady_clock::now();
// Step 1: Translate which button was affected
// NOTE: if scrolled, having buttonFlag = 0 means
// we don't actually update the buttonState
Expand All @@ -871,6 +873,29 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
{
case CsiMouseButtonCodes::Left:
buttonFlag = FROM_LEFT_1ST_BUTTON_PRESSED;
// If this is a mouse down, check if it's a double click
// and also update our last clicked position and time
if (id == CsiActionCodes::MouseDown)
{
if (_lastMouseClickPos && _lastMouseClickTime &&
uiPos == _lastMouseClickPos &&
(std::chrono::steady_clock::now() - _lastMouseClickTime.value()) < _doubleClickTime)
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{
// This was a double click, set the flag and reset our trackers
// for last clicked position and time (this is so we don't send
// another double click on a third click)
eventFlags |= DOUBLE_CLICK;
_lastMouseClickPos.reset();
_lastMouseClickTime.reset();
}
else
{
// This was a single click, update our trackers for last
// clicked position and time
_lastMouseClickPos = uiPos;
_lastMouseClickTime = currentTime;
}
}
break;
case CsiMouseButtonCodes::Right:
buttonFlag = RIGHTMOST_BUTTON_PRESSED;
Expand All @@ -894,6 +919,10 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
eventFlags |= MOUSE_WHEELED;
break;
}
case CsiMouseButtonCodes::Released:
// hover event, we still want to send these but we don't
// need to do anything special here, so just break
break;
default:
// no detectable buttonID, so we can't update the state
return false;
Expand Down
8 changes: 6 additions & 2 deletions src/terminal/parser/InputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ namespace Microsoft::Console::VirtualTerminal
std::function<bool()> _pfnFlushToInputQueue;
bool _lookingForDSR;
DWORD _mouseButtonState = 0;
std::chrono::milliseconds _doubleClickTime;
std::optional<til::point> _lastMouseClickPos{};
std::optional<std::chrono::steady_clock::time_point> _lastMouseClickTime{};

DWORD _GetCursorKeysModifierState(const VTParameters parameters, const VTID id) noexcept;
DWORD _GetGenericKeysModifierState(const VTParameters parameters) noexcept;
Expand All @@ -181,15 +184,16 @@ namespace Microsoft::Console::VirtualTerminal
bool _UpdateSGRMouseButtonState(const VTID id,
const size_t sgrEncoding,
DWORD& buttonState,
DWORD& eventFlags) noexcept;
DWORD& eventFlags,
const til::point uiPos);
bool _GetGenericVkey(const GenericKeyIdentifiers identifier, short& vkey) const;
bool _GetCursorKeysVkey(const VTID id, short& vkey) const;
bool _GetSs3KeysVkey(const wchar_t wch, short& vkey) const;

bool _WriteSingleKey(const short vkey, const DWORD modifierState);
bool _WriteSingleKey(const wchar_t wch, const short vkey, const DWORD modifierState);

bool _WriteMouseEvent(const size_t column, const size_t line, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags);
bool _WriteMouseEvent(const til::point uiPos, const DWORD buttonState, const DWORD controlKeyState, const DWORD eventFlags);

void _GenerateWrappedSequence(const wchar_t wch,
const short vkey,
Expand Down
58 changes: 58 additions & 0 deletions src/terminal/parser/ut_parser/InputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TEST_METHOD(SGRMouseTest_Modifiers);
TEST_METHOD(SGRMouseTest_Movement);
TEST_METHOD(SGRMouseTest_Scroll);
TEST_METHOD(SGRMouseTest_DoubleClick);
TEST_METHOD(SGRMouseTest_Hover);
TEST_METHOD(CtrlAltZCtrlAltXTest);
TEST_METHOD(TestSs3Entry);
TEST_METHOD(TestSs3Immediate);
Expand Down Expand Up @@ -1249,6 +1251,62 @@ void InputEngineTest::SGRMouseTest_Scroll()
VerifySGRMouseData(testData);
}

void InputEngineTest::SGRMouseTest_DoubleClick()
{
// SGR_PARAMS serves as test input
// - the state of the buttons (constructed via InputStateMachineEngine::CsiMouseButtonCodes)
// - the modifiers for the mouse event (constructed via InputStateMachineEngine::CsiMouseModifierCodes)
// - the {x,y} position of the event on the viewport where the top-left is {1,1}
// - the direction of the mouse press (constructed via InputStateMachineEngine::CsiActionCodes)

// MOUSE_EVENT_PARAMS serves as expected output
// - buttonState
// - controlKeyState
// - mousePosition
// - eventFlags

// clang-format off
const std::vector<std::tuple<SGR_PARAMS, MOUSE_EVENT_PARAMS>> testData = {
// TEST INPUT EXPECTED OUTPUT
{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_1ST_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_1ST_BUTTON_PRESSED, 0, { 0, 0 }, 2 } },
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_1ST_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Left, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
};
// clang-format on

VerifySGRMouseData(testData);
}

void InputEngineTest::SGRMouseTest_Hover()
{
// SGR_PARAMS serves as test input
// - the state of the buttons (constructed via InputStateMachineEngine::CsiMouseButtonCodes)
// - the modifiers for the mouse event (constructed via InputStateMachineEngine::CsiMouseModifierCodes)
// - the {x,y} position of the event on the viewport where the top-left is {1,1}
// - the direction of the mouse press (constructed via InputStateMachineEngine::CsiActionCodes)

// MOUSE_EVENT_PARAMS serves as expected output
// - buttonState
// - controlKeyState
// - mousePosition
// - eventFlags

// clang-format off
const std::vector<std::tuple<SGR_PARAMS, MOUSE_EVENT_PARAMS>> testData = {
// TEST INPUT EXPECTED OUTPUT
{ { CsiMouseButtonCodes::Released, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Released, 0, { 2, 2 }, CsiActionCodes::MouseUp }, { 0, 0, { 1, 1 }, 0 } },
PankajBhojwani marked this conversation as resolved.
Show resolved Hide resolved
};
// clang-format on

VerifySGRMouseData(testData);
}

void InputEngineTest::CtrlAltZCtrlAltXTest()
{
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
Expand Down