Skip to content

Commit b53d7df

Browse files
aymanbagabasDHowett
authored andcommitted
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 (cherry picked from commit 814f78e) Service-Card-Id: PVTI_lADOAF3p4s4BBcTlzgep1sM Service-Version: 1.24
1 parent 1194ad0 commit b53d7df

18 files changed

+134
-67
lines changed

src/cascadia/TerminalControl/ControlInteractivity.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
485485
// - modifiers: The modifiers pressed during this event, in the form of a VirtualKeyModifiers
486486
// - delta: the mouse wheel delta that triggered this event.
487487
bool ControlInteractivity::MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
488-
const int32_t delta,
488+
const Core::Point delta,
489489
const Core::Point pixelPosition,
490490
const Control::MouseButtonState buttonState)
491491
{
@@ -506,9 +506,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
506506
// PointerPoint to work with. So, we're just going to do a
507507
// mousewheel event manually
508508
return _sendMouseEventHelper(terminalPosition,
509-
WM_MOUSEWHEEL,
509+
delta.Y != 0 ? WM_MOUSEWHEEL : WM_MOUSEHWHEEL,
510510
modifiers,
511-
::base::saturated_cast<short>(delta),
511+
::base::saturated_cast<short>(delta.Y != 0 ? delta.Y : delta.X),
512512
buttonState);
513513
}
514514

@@ -517,15 +517,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
517517

518518
if (ctrlPressed && shiftPressed && _core->Settings().ScrollToChangeOpacity())
519519
{
520-
_mouseTransparencyHandler(delta);
520+
_mouseTransparencyHandler(delta.Y);
521521
}
522522
else if (ctrlPressed && !shiftPressed && _core->Settings().ScrollToZoom())
523523
{
524-
_mouseZoomHandler(delta);
524+
_mouseZoomHandler(delta.Y);
525525
}
526526
else
527527
{
528-
_mouseScrollHandler(delta, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
528+
_mouseScrollHandler(delta.Y, pixelPosition, WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown));
529529
}
530530
return false;
531531
}
@@ -659,15 +659,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation
659659
return _core->IsVtMouseModeEnabled();
660660
}
661661

662-
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta)
662+
bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta)
663663
{
664664
// If the user is holding down Shift, suppress mouse events
665665
// TODO GH#4875: disable/customize this functionality
666666
if (modifiers.IsShiftPressed())
667667
{
668668
return false;
669669
}
670-
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta);
670+
if (delta.Y != 0)
671+
{
672+
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta.Y);
673+
}
674+
else
675+
{
676+
return _core->ShouldSendAlternateScroll(WM_MOUSEHWHEEL, delta.X);
677+
}
671678
}
672679

673680
// Method Description:

src/cascadia/TerminalControl/ControlInteractivity.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
7474
void TouchReleased();
7575

7676
bool MouseWheel(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
77-
const int32_t delta,
77+
const Core::Point delta,
7878
const Core::Point pixelPosition,
7979
const Control::MouseButtonState state);
8080

@@ -153,7 +153,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
153153

154154
void _hyperlinkHandler(const std::wstring_view uri);
155155
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
156-
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta);
156+
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const Core::Point delta);
157157

158158
til::point _getTerminalPosition(const til::point pixelPosition, bool roundToNearestCell);
159159

src/cascadia/TerminalControl/ControlInteractivity.idl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ namespace Microsoft.Terminal.Control
6060
void TouchReleased();
6161

6262
Boolean MouseWheel(Microsoft.Terminal.Core.ControlKeyStates modifiers,
63-
Int32 delta,
63+
Microsoft.Terminal.Core.Point delta,
6464
Microsoft.Terminal.Core.Point pixelPosition,
6565
MouseButtonState state);
6666

src/cascadia/TerminalControl/IMouseWheelListener.idl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ namespace Microsoft.Terminal.Control
1111
[uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")]
1212
interface IMouseWheelListener
1313
{
14-
Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
14+
Boolean OnMouseWheel(Windows.Foundation.Point coord, Microsoft.Terminal.Core.Point delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown);
1515
}
1616
}

src/cascadia/TerminalControl/TermControl.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,15 +2164,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
21642164
RestorePointerCursor.raise(*this, nullptr);
21652165

21662166
const auto point = args.GetCurrentPoint(*this);
2167-
// GH#10329 - we don't need to handle horizontal scrolls. Only vertical ones.
2168-
// So filter out the horizontal ones.
2169-
if (point.Properties().IsHorizontalMouseWheel())
2170-
{
2171-
return;
2172-
}
2173-
2167+
auto delta = point.Properties().MouseWheelDelta();
21742168
auto result = _interactivity.MouseWheel(ControlKeyStates{ args.KeyModifiers() },
2175-
point.Properties().MouseWheelDelta(),
2169+
point.Properties().IsHorizontalMouseWheel() ?
2170+
Core::Point{ delta, 0 } :
2171+
Core::Point{ 0, delta },
21762172
_toTerminalOrigin(point.Position()),
21772173
TermControl::GetPressedMouseButtons(point));
21782174
if (result)
@@ -2192,7 +2188,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
21922188
// - delta: the mouse wheel delta that triggered this event.
21932189
// - state: the state for each of the mouse buttons individually (pressed/unpressed)
21942190
bool TermControl::OnMouseWheel(const Windows::Foundation::Point location,
2195-
const int32_t delta,
2191+
const Core::Point delta,
21962192
const bool leftButtonDown,
21972193
const bool midButtonDown,
21982194
const bool rightButtonDown)

src/cascadia/TerminalControl/TermControl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
147147

148148
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
149149

150-
bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
150+
bool OnMouseWheel(const Windows::Foundation::Point location, const winrt::Microsoft::Terminal::Core::Point delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown);
151151

152152
~TermControl();
153153

src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ namespace ControlUnitTests
170170

171171
// The mouse location and buttons don't matter here.
172172
interactivity->MouseWheel(modifiers,
173-
30,
173+
Core::Point{ 0, 30 },
174174
Core::Point{ 0, 0 },
175175
buttonState);
176176
}
@@ -188,7 +188,7 @@ namespace ControlUnitTests
188188

189189
// The mouse location and buttons don't matter here.
190190
interactivity->MouseWheel(modifiers,
191-
-30,
191+
Core::Point{ 0, -30 },
192192
Core::Point{ 0, 0 },
193193
buttonState);
194194
}
@@ -245,7 +245,7 @@ namespace ControlUnitTests
245245
expectedTop = 20;
246246

247247
interactivity->MouseWheel(modifiers,
248-
WHEEL_DELTA,
248+
Core::Point{ 0, WHEEL_DELTA },
249249
Core::Point{ 0, 0 },
250250
buttonState);
251251

@@ -254,18 +254,18 @@ namespace ControlUnitTests
254254
{
255255
expectedTop--;
256256
interactivity->MouseWheel(modifiers,
257-
WHEEL_DELTA,
257+
Core::Point{ 0, WHEEL_DELTA },
258258
Core::Point{ 0, 0 },
259259
buttonState);
260260
}
261261
Log::Comment(L"Scrolling up more should do nothing");
262262
expectedTop = 0;
263263
interactivity->MouseWheel(modifiers,
264-
WHEEL_DELTA,
264+
Core::Point{ 0, WHEEL_DELTA },
265265
Core::Point{ 0, 0 },
266266
buttonState);
267267
interactivity->MouseWheel(modifiers,
268-
WHEEL_DELTA,
268+
Core::Point{ 0, WHEEL_DELTA },
269269
Core::Point{ 0, 0 },
270270
buttonState);
271271

@@ -275,19 +275,19 @@ namespace ControlUnitTests
275275
Log::Comment(NoThrowString().Format(L"---scroll down #%d---", i));
276276
expectedTop++;
277277
interactivity->MouseWheel(modifiers,
278-
-WHEEL_DELTA,
278+
Core::Point{ 0, -WHEEL_DELTA },
279279
Core::Point{ 0, 0 },
280280
buttonState);
281281
Log::Comment(NoThrowString().Format(L"internal scrollbar pos:%f", interactivity->_internalScrollbarPosition));
282282
}
283283
Log::Comment(L"Scrolling down more should do nothing");
284284
expectedTop = 21;
285285
interactivity->MouseWheel(modifiers,
286-
-WHEEL_DELTA,
286+
Core::Point{ 0, -WHEEL_DELTA },
287287
Core::Point{ 0, 0 },
288288
buttonState);
289289
interactivity->MouseWheel(modifiers,
290-
-WHEEL_DELTA,
290+
Core::Point{ 0, -WHEEL_DELTA },
291291
Core::Point{ 0, 0 },
292292
buttonState);
293293
}
@@ -444,7 +444,7 @@ namespace ControlUnitTests
444444

445445
Log::Comment(L"Scroll up a line, with the left mouse button selected");
446446
interactivity->MouseWheel(modifiers,
447-
WHEEL_DELTA,
447+
Core::Point{ 0, WHEEL_DELTA },
448448
cursorPosition1.to_core_point(),
449449
leftMouseDown);
450450

@@ -492,55 +492,55 @@ namespace ControlUnitTests
492492
const Core::Point mousePos{ 0, 0 };
493493
Control::MouseButtonState state{};
494494

495-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
495+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
496496
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
497497

498498
Log::Comment(L"Scroll up 4 more times. Once we're at 3/5 scrolls, "
499499
L"we'll round the internal scrollbar position to scrolling to the next row.");
500-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
500+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
501501
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
502-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
502+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
503503
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
504-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
504+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
505505
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
506-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
506+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
507507
VERIFY_ARE_EQUAL(20, core->ScrollOffset());
508508

509509
Log::Comment(L"Jump to line 5, so we can scroll down from there.");
510510
interactivity->UpdateScrollbar(5);
511511
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
512512
Log::Comment(L"Scroll down 5 times, at which point we should accumulate a whole row of delta.");
513-
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 1/5
513+
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 1/5
514514
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
515-
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 2/5
515+
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 2/5
516516
VERIFY_ARE_EQUAL(5, core->ScrollOffset());
517-
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 3/5
517+
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 3/5
518518
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
519-
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 4/5
519+
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 4/5
520520
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
521-
interactivity->MouseWheel(modifiers, -delta, mousePos, state); // 5/5
521+
interactivity->MouseWheel(modifiers, Core::Point{ 0, -delta }, mousePos, state); // 5/5
522522
VERIFY_ARE_EQUAL(6, core->ScrollOffset());
523523

524524
Log::Comment(L"Jump to the bottom.");
525525
interactivity->UpdateScrollbar(21);
526526
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
527527
Log::Comment(L"Scroll a bit, then emit a line of text. We should reset our internal scroll position.");
528-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
528+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
529529
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
530-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
530+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
531531
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
532532

533533
conn->WriteInput(winrt_wstring_to_array_view(L"Foo\r\n"));
534534
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
535-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 1/5
535+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 1/5
536536
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
537-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 2/5
537+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 2/5
538538
VERIFY_ARE_EQUAL(22, core->ScrollOffset());
539-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 3/5
539+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 3/5
540540
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
541-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 4/5
541+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 4/5
542542
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
543-
interactivity->MouseWheel(modifiers, delta, mousePos, state); // 5/5
543+
interactivity->MouseWheel(modifiers, Core::Point{ 0, delta }, mousePos, state); // 5/5
544544
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
545545
}
546546

@@ -709,7 +709,7 @@ namespace ControlUnitTests
709709
{
710710
expectedTop--;
711711
interactivity->MouseWheel(modifiers,
712-
WHEEL_DELTA,
712+
Core::Point{ 0, WHEEL_DELTA },
713713
Core::Point{ 0, 0 },
714714
noMouseDown);
715715
}

src/cascadia/WindowsTerminal/AppHost.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ void AppHost::_RaiseVisualBell(const winrt::Windows::Foundation::IInspectable&,
745745
// - delta: the wheel delta that triggered this event.
746746
// Return Value:
747747
// - <none>
748-
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta)
748+
void AppHost::_WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta)
749749
{
750750
if (_windowLogic)
751751
{

src/cascadia/WindowsTerminal/AppHost.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class AppHost : public std::enable_shared_from_this<AppHost>
7373

7474
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
7575
const winrt::Windows::Foundation::IInspectable& arg);
76-
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const int32_t delta);
76+
void _WindowMouseWheeled(const winrt::Windows::Foundation::Point coord, const winrt::Microsoft::Terminal::Core::Point delta);
7777
void _WindowActivated(bool activated);
7878
void _WindowMoved();
7979

src/cascadia/WindowsTerminal/IslandWindow.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
595595
WindowCloseButtonClicked.raise();
596596
return 0;
597597
}
598+
case WM_MOUSEHWHEEL:
598599
case WM_MOUSEWHEEL:
599600
try
600601
{
@@ -623,7 +624,11 @@ void IslandWindow::_OnGetMinMaxInfo(const WPARAM /*wParam*/, const LPARAM lParam
623624
const auto scale = GetCurrentDpiScale();
624625
const winrt::Windows::Foundation::Point real{ relative.x / scale, relative.y / scale };
625626

626-
const auto wheelDelta = static_cast<short>(HIWORD(wparam));
627+
winrt::Microsoft::Terminal::Core::Point wheelDelta{ 0, static_cast<int32_t>(HIWORD(wparam)) };
628+
if (message == WM_MOUSEHWHEEL)
629+
{
630+
std::swap(wheelDelta.X, wheelDelta.Y);
631+
}
627632

628633
// Raise an event, so any listeners can handle the mouse wheel event manually.
629634
MouseScrolled.raise(real, wheelDelta);

0 commit comments

Comments
 (0)