Add support for VT horizontal mouse wheel events (#19248)

This adds support for horizontal mouse wheel events (`WM_MOUSEHWHEEL`).
With this change, applications running in the terminal can now receive
and respond to horizontal scroll inputs from the mouse/trackpad.

Closes #19245
Closes #10329
This commit is contained in:
Ayman Bagabas 2025-09-11 19:48:49 -04:00 committed by GitHub
parent eb16eb26ab
commit 814f78ed2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 134 additions and 67 deletions

View File

@ -485,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers // - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
// - delta: the mouse wheel delta that triggered this event. // - delta: the mouse wheel delta that triggered this event.
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta, const Core::Point delta,
const Core::Point pixelPosition, const Core::Point pixelPosition,
const Control::MouseButtonState buttonState) const Control::MouseButtonState buttonState)
{ {
@ -506,9 +506,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// PointerPoint to work with. So, we're just going to do a // PointerPoint to work with. So, we're just going to do a
// mousewheel event manually // mousewheel event manually
return _sendMouseEventHelper(terminalPosition, return _sendMouseEventHelper(terminalPosition,
WM_MOUSEWHEEL, delta.Y != 0 ? WM_MOUSEWHEEL : WM_MOUSEHWHEEL,
modifiers, modifiers,
::base::saturated_cast<short>(delta), ::base::saturated_cast<short>(delta.Y != 0 ? delta.Y : delta.X),
buttonState); buttonState);
} }
@ -517,15 +517,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity()) if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity())
{ {
_mouseTransparencyHandler(delta); _mouseTransparencyHandler(delta.Y);
} }
else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom()) else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom())
{ {
_mouseZoomHandler(delta); _mouseZoomHandler(delta.Y);
} }
else else
{ {
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown)); _mouseScrollHandler(delta.Y, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
} }
return false; return false;
} }
@ -659,7 +659,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core->IsVtMouseModeEnabled(); return _core->IsVtMouseModeEnabled();
} }
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta) bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta)
{ {
// If the user is holding down Shift, suppress mouse events // If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality // TODO GH#4875: disable/customize this functionality
@ -667,7 +667,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
return false; return false;
} }
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta); if (delta.Y != 0)
{
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta.Y);
}
else
{
return _core->ShouldSendAlternateScroll(WM_MOUSEHWHEEL, delta.X);
}
} }
// Method Description: // Method Description:

View File

@ -74,7 +74,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TouchReleased(); void TouchReleased();
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const int32_t delta, const Core::Point delta,
const Core::Point pixelPosition, const Core::Point pixelPosition,
const Control::MouseButtonState state); const Control::MouseButtonState state);
@ -153,7 +153,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _hyperlinkHandler(const std::wstring_view uri); void _hyperlinkHandler(const std::wstring_view uri);
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers); bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta); bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta);
til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell); til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell);

View File

@ -60,7 +60,7 @@ namespace Microsoft.Terminal.Control
void TouchReleased(); void TouchReleased();
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers, Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
Int32 delta, Microsoft.Terminal.Core.Point delta,
Microsoft.Terminal.Core.Point pixelPosition, Microsoft.Terminal.Core.Point pixelPosition,
MouseButtonState state); MouseButtonState state);

View File

@ -11,6 +11,6 @@ namespace Microsoft.Terminal.Control
[uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")] [uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")]
interface IMouseWheelListener interface IMouseWheelListener
{ {
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown); Boolean OnMouseWheel(Windows.Foundation.Point coord, Microsoft.Terminal.Core.Point delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
} }
} }

View File

@ -2160,15 +2160,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
RestorePointerCursor.raise(*this, nullptr); RestorePointerCursor.raise(*this, nullptr);
const auto point = args.GetCurrentPoint(*this); const auto point = args.GetCurrentPoint(*this);
// GH#10329 - we don't need to handle horizontal scrolls. Only vertical ones. auto delta = point.Properties().MouseWheelDelta();
// So filter out the horizontal ones.
if (point.Properties().IsHorizontalMouseWheel())
{
return;
}
auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() }, auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() },
point.Properties().MouseWheelDelta(), point.Properties().IsHorizontalMouseWheel() ?
Core::Point{ delta, 0 } :
Core::Point{ 0, delta },
_toTerminalOrigin(point.Position()), _toTerminalOrigin(point.Position()),
TermControl::GetPressedMouseButtons(point)); TermControl::GetPressedMouseButtons(point));
if (result) if (result)
@ -2188,7 +2184,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - delta: the mouse wheel delta that triggered this event. // - delta: the mouse wheel delta that triggered this event.
// - state: the state for each of the mouse buttons individually (pressed/unpressed) // - state: the state for each of the mouse buttons individually (pressed/unpressed)
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location, bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
const int32_t delta, const Core::Point delta,
const bool leftButtonDown, const bool leftButtonDown,
const bool midButtonDown, const bool midButtonDown,
const bool rightButtonDown) const bool rightButtonDown)

View File

@ -148,7 +148,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown); bool OnMouseWheel(const Windows::Foundation::Point location, const winrt::Microsoft::Terminal::Core::Point delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
~TermControl(); ~TermControl();

View File

@ -170,7 +170,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here. // The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
30, Core::Point{ 0, 30 },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
} }
@ -188,7 +188,7 @@ namespace ControlUnitTests
// The mouse location and buttons don't matter here. // The mouse location and buttons don't matter here.
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
-30, Core::Point{ 0, -30 },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
} }
@ -245,7 +245,7 @@ namespace ControlUnitTests
expectedTop = 20; expectedTop = 20;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
@ -254,18 +254,18 @@ namespace ControlUnitTests
{ {
expectedTop--; expectedTop--;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
} }
Log::Comment(L"Scrolling up more should do nothing"); Log::Comment(L"Scrolling up more should do nothing");
expectedTop = 0; expectedTop = 0;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
@ -275,7 +275,7 @@ namespace ControlUnitTests
Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i)); Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i));
expectedTop++; expectedTop++;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
-WHEEL_DELTA, Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition)); Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition));
@ -283,11 +283,11 @@ namespace ControlUnitTests
Log::Comment(L"Scrolling down more should do nothing"); Log::Comment(L"Scrolling down more should do nothing");
expectedTop = 21; expectedTop = 21;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
-WHEEL_DELTA, Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
-WHEEL_DELTA, Core::Point{ 0, -WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
buttonState); buttonState);
} }
@ -444,7 +444,7 @@ namespace ControlUnitTests
Log::Comment(L"Scroll up a line, with the left mouse button selected"); Log::Comment(L"Scroll up a line, with the left mouse button selected");
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
cursorPosition1.to_core_point(), cursorPosition1.to_core_point(),
leftMouseDown); leftMouseDown);
@ -492,55 +492,55 @@ namespace ControlUnitTests
const Core::Point mousePos{ 0, 0 }; const Core::Point mousePos{ 0, 0 };
Control::MouseButtonState state{}; Control::MouseButtonState state{};
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, " Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, "
L"we'll round the internal scrollbar position to scrolling to the next row."); L"we'll round the internal scrollbar position to scrolling to the next row.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(20, core->ScrollOffset()); VERIFY_ARE_EQUAL(20, core->ScrollOffset());
Log::Comment(L"Jump to line 5, so we can scroll down from there."); Log::Comment(L"Jump to line 5, so we can scroll down from there.");
interactivity->UpdateScrollbar(5); interactivity->UpdateScrollbar(5);
VERIFY_ARE_EQUAL(5, core->ScrollOffset()); VERIFY_ARE_EQUAL(5, core->ScrollOffset());
Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta."); Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta.");
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 1/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset()); VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 2/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(5, core->ScrollOffset()); VERIFY_ARE_EQUAL(5, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 3/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset()); VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 4/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset()); VERIFY_ARE_EQUAL(6, core->ScrollOffset());
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 5/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(6, core->ScrollOffset()); VERIFY_ARE_EQUAL(6, core->ScrollOffset());
Log::Comment(L"Jump to the bottom."); Log::Comment(L"Jump to the bottom.");
interactivity->UpdateScrollbar(21); interactivity->UpdateScrollbar(21);
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position."); Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position.");
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n")); conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
VERIFY_ARE_EQUAL(22, core->ScrollOffset()); VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset()); VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
VERIFY_ARE_EQUAL(22, core->ScrollOffset()); VERIFY_ARE_EQUAL(22, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5 interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
VERIFY_ARE_EQUAL(21, core->ScrollOffset()); VERIFY_ARE_EQUAL(21, core->ScrollOffset());
} }
@ -709,7 +709,7 @@ namespace ControlUnitTests
{ {
expectedTop--; expectedTop--;
interactivity->MouseWheel(modifiers, interactivity->MouseWheel(modifiers,
WHEEL_DELTA, Core::Point{ 0, WHEEL_DELTA },
Core::Point{ 0, 0 }, Core::Point{ 0, 0 },
noMouseDown); noMouseDown);
} }

View File

@ -745,7 +745,7 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&,
// - delta: the wheel delta that triggered this event. // - delta: the wheel delta that triggered this event.
// Return Value: // Return Value:
// - <none> // - <none>
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta) void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta)
{ {
if (_windowLogic) if (_windowLogic)
{ {

View File

@ -73,7 +73,7 @@ private:
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender, void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg); const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta); void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta);
void _WindowActivated(bool activated); void _WindowActivated(bool activated);
void _WindowMoved(); void _WindowMoved();

View File

@ -595,6 +595,7 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
WindowCloseButtonClicked.raise(); WindowCloseButtonClicked.raise();
return 0; return 0;
} }
case WM_MOUSEHWHEEL:
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
try try
{ {
@ -623,7 +624,11 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
const auto scale = GetCurrentDpiScale(); const auto scale = GetCurrentDpiScale();
const winrt::Windows::Foundation::Point real{ relative.x / scale, relative.y / scale }; const winrt::Windows::Foundation::Point real{ relative.x / scale, relative.y / scale };
const auto wheelDelta = static_cast<short>(HIWORD(wparam)); winrt::Microsoft::Terminal::Core::Point wheelDelta{ 0, static_cast<int32_t>(HIWORD(wparam)) };
if (message == WM_MOUSEHWHEEL)
{
std::swap(wheelDelta.X, wheelDelta.Y);
}
// Raise an event, so any listeners can handle the mouse wheel event manually. // Raise an event, so any listeners can handle the mouse wheel event manually.
MouseScrolled.raise(real, wheelDelta); MouseScrolled.raise(real, wheelDelta);

View File

@ -74,7 +74,7 @@ public:
til::event<winrt::delegate<>> DragRegionClicked; til::event<winrt::delegate<>> DragRegionClicked;
til::event<winrt::delegate<>> WindowCloseButtonClicked; til::event<winrt::delegate<>> WindowCloseButtonClicked;
til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, int32_t)>> MouseScrolled; til::event<winrt::delegate<void(winrt::Windows::Foundation::Point, winrt::Microsoft::Terminal::Core::Point)>> MouseScrolled;
til::event<winrt::delegate<void(bool)>> WindowActivated; til::event<winrt::delegate<void(bool)>> WindowActivated;
til::event<winrt::delegate<void()>> NotifyNotificationIconPressed; til::event<winrt::delegate<void()>> NotifyNotificationIconPressed;
til::event<winrt::delegate<void()>> NotifyWindowHidden; til::event<winrt::delegate<void()>> NotifyWindowHidden;

View File

@ -567,7 +567,7 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo,
if (!fShiftPressed && !pSelection->IsInSelectingState()) if (!fShiftPressed && !pSelection->IsInSelectingState())
{ {
short sDelta = 0; short sDelta = 0;
if (Message == WM_MOUSEWHEEL) if (Message == WM_MOUSEWHEEL || Message == WM_MOUSEHWHEEL)
{ {
sDelta = GET_WHEEL_DELTA_WPARAM(wParam); sDelta = GET_WHEEL_DELTA_WPARAM(wParam);
} }

View File

@ -127,10 +127,13 @@ public:
wch = L'!'; wch = L'!';
break; break;
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEWHEEL")); Log::Comment(NoThrowString().Format(L"MOUSEWHEEL"));
wch = L'`' + (sScrollDelta > 0 ? 0 : 1); wch = L'`' + (sScrollDelta > 0 ? 0 : 1);
break; break;
case WM_MOUSEHWHEEL:
Log::Comment(NoThrowString().Format(L"MOUSEHWHEEL"));
wch = L'b' + (sScrollDelta > 0 ? 1 : 0);
break;
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
default: default:
Log::Comment(NoThrowString().Format(L"DEFAULT")); Log::Comment(NoThrowString().Format(L"DEFAULT"));
@ -168,9 +171,11 @@ public:
result = 3 + 0x20; // we add 0x20 to hover events, which are all encoded as WM_MOUSEMOVE events result = 3 + 0x20; // we add 0x20 to hover events, which are all encoded as WM_MOUSEMOVE events
break; break;
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 64 : 65); result = (sScrollDelta > 0 ? 64 : 65);
break; break;
case WM_MOUSEHWHEEL:
result = (sScrollDelta > 0 ? 67 : 66);
break;
default: default:
result = 0; result = 0;
break; break;
@ -582,6 +587,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down"); Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[B"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {})); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[B"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[C"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1B[D"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Enable cursor keys mode"); Log::Comment(L"Enable cursor keys mode");
mouseInput.SetInputMode(TerminalInput::Mode::CursorKey, true); mouseInput.SetInputMode(TerminalInput::Mode::CursorKey, true);
@ -591,6 +602,12 @@ public:
Log::Comment(L"Test mouse wheel scrolling down"); Log::Comment(L"Test mouse wheel scrolling down");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOB"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {})); VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOB"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling right");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOC"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, WHEEL_DELTA, {}));
Log::Comment(L"Test mouse wheel scrolling left");
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1BOD"), mouseInput.HandleMouse({ 0, 0 }, WM_MOUSEHWHEEL, noModifierKeys, -WHEEL_DELTA, {}));
Log::Comment(L"Confirm no effect when scroll mode is disabled"); Log::Comment(L"Confirm no effect when scroll mode is disabled");
mouseInput.UseAlternateScreenBuffer(); mouseInput.UseAlternateScreenBuffer();
mouseInput.SetInputMode(TerminalInput::Mode::AlternateScroll, false); mouseInput.SetInputMode(TerminalInput::Mode::AlternateScroll, false);

View File

@ -163,9 +163,11 @@ static constexpr wchar_t _windowsButtonToXEncoding(const unsigned int button,
xvalue = 1; xvalue = 1;
break; break;
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41; xvalue = delta > 0 ? 0x40 : 0x41;
break; break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default: default:
xvalue = 0; xvalue = 0;
break; break;
@ -221,9 +223,11 @@ static constexpr int _windowsButtonToSGREncoding(const unsigned int button,
xvalue = 3; xvalue = 3;
break; break;
case WM_MOUSEWHEEL: case WM_MOUSEWHEEL:
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x40 : 0x41; xvalue = delta > 0 ? 0x40 : 0x41;
break; break;
case WM_MOUSEHWHEEL:
xvalue = delta > 0 ? 0x43 : 0x42;
break;
default: default:
xvalue = 0; xvalue = 0;
break; break;
@ -378,7 +382,7 @@ TerminalInput::OutputType TerminalInput::HandleMouse(const til::point position,
if (ShouldSendAlternateScroll(button, delta)) if (ShouldSendAlternateScroll(button, delta))
{ {
return _makeAlternateScrollOutput(delta); return _makeAlternateScrollOutput(button, delta);
} }
return {}; return {};
@ -493,7 +497,9 @@ bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const s
// - Sends a sequence to the input corresponding to cursor up / down depending on the sScrollDelta. // - Sends a sequence to the input corresponding to cursor up / down depending on the sScrollDelta.
// Parameters: // Parameters:
// - delta: The scroll wheel delta of the input event // - delta: The scroll wheel delta of the input event
TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const short delta) const TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const unsigned int button, const short delta) const
{
if (button == WM_MOUSEWHEEL)
{ {
if (delta > 0) if (delta > 0)
{ {
@ -504,3 +510,19 @@ TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const short
return MakeOutput(_keyMap.at(VK_DOWN)); return MakeOutput(_keyMap.at(VK_DOWN));
} }
} }
else if (button == WM_MOUSEHWHEEL)
{
if (delta > 0)
{
return MakeOutput(_keyMap.at(VK_RIGHT));
}
else
{
return MakeOutput(_keyMap.at(VK_LEFT));
}
}
else
{
return {};
}
}

View File

@ -111,7 +111,7 @@ namespace Microsoft::Console::VirtualTerminal
[[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _GenerateUtf8Sequence(til::point position, unsigned int button, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isRelease, bool isHover, short modifierKeyState, short delta); [[nodiscard]] OutputType _GenerateSGRSequence(til::point position, unsigned int button, bool isRelease, bool isHover, short modifierKeyState, short delta);
[[nodiscard]] OutputType _makeAlternateScrollOutput(short delta) const; [[nodiscard]] OutputType _makeAlternateScrollOutput(unsigned int button, short delta) const;
static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept; static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
#pragma endregion #pragma endregion

View File

@ -897,6 +897,22 @@ bool InputStateMachineEngine::_UpdateSGRMouseButtonState(const VTID id,
eventFlags |= MOUSE_WHEELED; eventFlags |= MOUSE_WHEELED;
break; break;
} }
case CsiMouseButtonCodes::ScrollLeft:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_BACKWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::ScrollRight:
{
// set high word to proper scroll direction
// scroll intensity is assumed to be constant value
buttonState |= SCROLL_DELTA_FORWARD;
eventFlags |= MOUSE_HWHEELED;
break;
}
case CsiMouseButtonCodes::Released: case CsiMouseButtonCodes::Released:
// hover event, we still want to send these but we don't // hover event, we still want to send these but we don't
// need to do anything special here, so just break // need to do anything special here, so just break

View File

@ -111,6 +111,8 @@ namespace Microsoft::Console::VirtualTerminal
Released = 3, Released = 3,
ScrollForward = 4, ScrollForward = 4,
ScrollBack = 5, ScrollBack = 5,
ScrollLeft = 6,
ScrollRight = 7,
}; };
constexpr unsigned short CsiMouseModifierCode_Drag = 32; constexpr unsigned short CsiMouseModifierCode_Drag = 32;

View File

@ -1196,6 +1196,8 @@ void InputEngineTest::SGRMouseTest_Scroll()
// TEST INPUT EXPECTED OUTPUT // TEST INPUT EXPECTED OUTPUT
{ { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } }, { { CsiMouseButtonCodes::ScrollForward, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } }, { { CsiMouseButtonCodes::ScrollBack, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_WHEELED } },
{ { CsiMouseButtonCodes::ScrollLeft, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_BACKWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
{ { CsiMouseButtonCodes::ScrollRight, 0, { 1, 1 }, CsiActionCodes::MouseDown }, { SCROLL_DELTA_FORWARD, 0, { 0, 0 }, MOUSE_HWHEELED } },
}; };
// clang-format on // clang-format on
VerifySGRMouseData(testData); VerifySGRMouseData(testData);