Account for WHEEL_DELTA when dispatching VT mouse wheel events (#6843)

By storing up the accumulated delta in the mouse input handler, we can
enlighten both conhost and terminal about wheel events that are less
than one line in size. Previously, we had a workaround in conhost that
clamped small scroll deltas to a whole line, which made trackpad
scrolling unimaginably fast. Terminal didn't make this mistake, but it
also didn't handle delta accumulation . . . which resulted in the same
behavior.

MouseInput will now wait until it's received WHEEL_DELTA (well-known
constant, value 120) worth of scrolling delta before it dispatches a
single scroll event.

Future considerations may include sending multiple wheel button events
for every *multiple* of WHEEL_DELTA, but that would be a slightly larger
refactoring that I'm not yet ready to undertake.

There's a chance that we should be dividing WHEEL_DELTA by the system's
"number of lines to scroll at once" setting, because on trackpads
conhost now scrolls a little _slow_. I think the only way to determine
whether this is palatable is to just ship it.

Fixes #6184.
This commit is contained in:
Dustin L. Howett 2020-07-09 16:24:17 -07:00 committed by GitHub
parent 695ebffca1
commit fc083296b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 21 deletions

View file

@ -630,20 +630,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
short sDelta = 0;
if (Message == WM_MOUSEWHEEL)
{
short sWheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// For most devices, we'll get mouse events as multiples of
// WHEEL_DELTA, where WHEEL_DELTA represents a single scroll unit
// But sometimes, things like trackpads will scroll in finer
// measurements. In this case, the VT mouse scrolling wouldn't work.
// So if that happens, ensure we scroll at least one time.
if (abs(sWheelDelta) < WHEEL_DELTA)
{
sDelta = sWheelDelta < 0 ? -1 : 1;
}
else
{
sDelta = sWheelDelta / WHEEL_DELTA;
}
sDelta = GET_WHEEL_DELTA_WPARAM(wParam);
}
if (HandleTerminalMouseEvent(MousePosition, Message, GET_KEYSTATE_WPARAM(wParam), sDelta))

View file

@ -514,7 +514,7 @@ public:
TEST_METHOD(ScrollWheelTests)
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:sScrollDelta", L"{0, -1, 1, 100, -10000, 32736}")
TEST_METHOD_PROPERTY(L"Data:sScrollDelta", L"{-120, 120, -10000, 32736}")
TEST_METHOD_PROPERTY(L"Data:uiModifierKeystate", L"{0x0000, 0x0004, 0x0008}")
END_TEST_METHOD_PROPERTIES()
@ -606,31 +606,31 @@ public:
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1B[A";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, 1));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
Log::Comment(L"Test mouse wheel scrolling down");
s_pwszInputExpected = L"\x1B[B";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -1));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA));
Log::Comment(L"Enable cursor keys mode");
mouseInput->ChangeCursorKeysMode(true);
Log::Comment(L"Test mouse wheel scrolling up");
s_pwszInputExpected = L"\x1BOA";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, 1));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
Log::Comment(L"Test mouse wheel scrolling down");
s_pwszInputExpected = L"\x1BOB";
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -1));
VERIFY_IS_TRUE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA));
Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput->UseAlternateScreenBuffer();
mouseInput->EnableAlternateScroll(false);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, 1));
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
Log::Comment(L"Confirm no effect when using the main buffer");
mouseInput->UseMainScreenBuffer();
mouseInput->EnableAlternateScroll(true);
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, 1));
VERIFY_IS_FALSE(mouseInput->HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, WHEEL_DELTA));
}
};

View file

@ -4,6 +4,7 @@
#include "precomp.h"
#include <windows.h>
#include "terminalInput.hpp"
#include "../types/inc/utils.hpp"
using namespace Microsoft::Console::VirtualTerminal;
@ -63,6 +64,17 @@ static constexpr bool _isHoverMsg(const unsigned int buttonCode) noexcept
return buttonCode == WM_MOUSEMOVE;
}
// Routine Description:
// - Determines if the input windows message code describes a mouse wheel event
// Parameters:
// - buttonCode - the message to decode.
// Return value:
// - true iff buttonCode is a wheel event to translate
static constexpr bool _isWheelMsg(const unsigned int buttonCode) noexcept
{
return buttonCode == WM_MOUSEWHEEL || buttonCode == WM_MOUSEHWHEEL;
}
// Routine Description:
// - Determines if the input windows message code describes a button press
// (either down or doubleclick)
@ -293,6 +305,32 @@ bool TerminalInput::HandleMouse(const COORD position,
const short modifierKeyState,
const short delta)
{
if (Utils::Sign(delta) != Utils::Sign(_mouseInputState.accumulatedDelta))
{
// This works for wheel and non-wheel events and transitioning between wheel/non-wheel.
// Non-wheel events have a delta of 0, which will fail to match the sign on
// a real wheel event or the accumulated delta. Wheel events will be either + or -
// and we only want to accumulate them if they match in sign.
_mouseInputState.accumulatedDelta = 0;
}
if (_isWheelMsg(button))
{
_mouseInputState.accumulatedDelta += delta;
if (std::abs(_mouseInputState.accumulatedDelta) < WHEEL_DELTA)
{
// If we're accumulating button presses of the same type, *and* those presses are
// on the wheel, accumulate delta until we hit the amount required to dispatch one
// "line" worth of scroll.
// Mark the event as "handled" if we would have otherwise emitted a scroll event.
return IsTrackingMouseInput() || _ShouldSendAlternateScroll(button, delta);
}
// We're ready to send this event through, but first we need to clear the accumulated;
// delta. Otherwise, we'll dispatch every subsequent sub-delta event as its own event.
_mouseInputState.accumulatedDelta = 0;
}
bool success = false;
if (_ShouldSendAlternateScroll(button, delta))
{

View file

@ -109,6 +109,7 @@ namespace Microsoft::Console::VirtualTerminal
bool inAlternateBuffer{ false };
COORD lastPos{ -1, -1 };
unsigned int lastButton{ 0 };
int accumulatedDelta{ 0 };
};
MouseInputState _mouseInputState;

View file

@ -15,6 +15,14 @@ Author(s):
namespace Microsoft::Console::Utils
{
// Function Description:
// - Returns -1, 0 or +1 to indicate the sign of the passed-in value.
template<typename T>
constexpr int Sign(T val) noexcept
{
return (T{ 0 } < val) - (val < T{ 0 });
}
bool IsValidHandle(const HANDLE handle) noexcept;
// Function Description: