Skip to content

Commit

Permalink
Pass through double clicks and hover events in Win32 mouse mode (#10138)
Browse files Browse the repository at this point in the history
Each mouse-down event's time and position is now stored, and if we
process a left-mouse-down event at the same position as the previous one
and within the double click time we set the double click flag. 

Also adds a case statement to `_UpdateSGRMouseButtonState` so that we
send hover events instead of ignoring them. 

Note: The 'right-click menu in far manager shows up at the wrong
location' bug still exists with this, as it seems to use the cursor
position as told by user32.

Related to #376

## Validation Steps Performed
Double click in far manager works, hover in far manager works (hovering
over items in the right-click menu correctly highlights them)
  • Loading branch information
PankajBhojwani committed May 24, 2021
1 parent dd348dc commit 0d61466
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 10 deletions.
47 changes: 39 additions & 8 deletions src/terminal/parser/InputStateMachineEngine.cpp
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()))
{
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 };

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 Down Expand Up @@ -894,6 +896,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 All @@ -908,6 +914,31 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
// NOTE: scroll events have buttonFlag = 0
// so this intentionally does nothing
buttonState |= buttonFlag;
// Check if this mouse down is a double click
// and also update our trackers for last clicked position, time and button
if (_lastMouseClickPos && _lastMouseClickTime && _lastMouseClickButton &&
uiPos == _lastMouseClickPos &&
(currentTime - _lastMouseClickTime.value()) < _doubleClickTime &&
buttonID == _lastMouseClickButton)
{
// This was a double click, set the flag and reset our trackers
// for last clicked position, time and button (this is so we don't send
// another double click on a third click)
eventFlags |= DOUBLE_CLICK;
_lastMouseClickPos.reset();
_lastMouseClickTime.reset();
_lastMouseClickButton.reset();
}
else if (buttonID == CsiMouseButtonCodes::Left ||
buttonID == CsiMouseButtonCodes::Right ||
buttonID == CsiMouseButtonCodes::Middle)
{
// This was a single click, update our trackers for last
// clicked position and time
_lastMouseClickPos = uiPos;
_lastMouseClickTime = currentTime;
_lastMouseClickButton = buttonID;
}
break;
case CsiActionCodes::MouseUp:
// clear flag
Expand Down
9 changes: 7 additions & 2 deletions src/terminal/parser/InputStateMachineEngine.hpp
Expand Up @@ -170,6 +170,10 @@ 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{};
std::optional<size_t> _lastMouseClickButton{};

DWORD _GetCursorKeysModifierState(const VTParameters parameters, const VTID id) noexcept;
DWORD _GetGenericKeysModifierState(const VTParameters parameters) noexcept;
Expand All @@ -181,15 +185,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
76 changes: 76 additions & 0 deletions src/terminal/parser/ut_parser/InputEngineTest.cpp
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,80 @@ 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 }, DOUBLE_CLICK } },
{ { 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 } },

{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_2ND_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_2ND_BUTTON_PRESSED, 0, { 0, 0 }, DOUBLE_CLICK } },
{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { FROM_LEFT_2ND_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Middle, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { RIGHTMOST_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { RIGHTMOST_BUTTON_PRESSED, 0, { 0, 0 }, DOUBLE_CLICK } },
{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },

{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { RIGHTMOST_BUTTON_PRESSED, 0, { 0, 0 }, 0 } },
{ { CsiMouseButtonCodes::Right, 0, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, 0 } },
};
// 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, CsiMouseModifierCodes::Drag, { 1, 1 }, CsiActionCodes::MouseUp }, { 0, 0, { 0, 0 }, MOUSE_MOVED } },
{ { CsiMouseButtonCodes::Released, CsiMouseModifierCodes::Drag, { 2, 2 }, CsiActionCodes::MouseUp }, { 0, 0, { 1, 1 }, MOUSE_MOVED } },
};
// clang-format on

VerifySGRMouseData(testData);
}

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

0 comments on commit 0d61466

Please sign in to comment.