diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index d0ecdb7895a..f9916c1e69e 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -136,6 +136,7 @@ namespace Microsoft.Terminal.Control void ToggleMarkMode(); Boolean SwitchSelectionEndpoint(); Boolean ExpandSelectionToWord(); + Boolean IsVtMouseModeEnabled(); void ClearBuffer(ClearBufferType clearType); void SetHoveredCell(Microsoft.Terminal.Core.Point terminalPosition); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index bd309117971..667944bc0d4 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -364,6 +364,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } } + else if (WI_IsFlagSet(buttonState, MouseButtonState::IsMiddleButtonDown)) + { + const auto action = _core->Settings().MiddleClickAction(); + if (action == Control::MiddleClickAction::Paste) + { + RequestPasteTextFromClipboard(); + } + } } void ControlInteractivity::TouchPressed(const Core::Point contactPoint) diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 4682668e92d..0b522721aab 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -29,6 +29,13 @@ namespace Microsoft.Terminal.Control MinGW, }; + enum MiddleClickAction + { + Pan = 0, + Paste, + None, + }; + // Class Description: // TerminalSettings encapsulates all settings that control the // TermControl's behavior. In these settings there is both the entirety @@ -77,6 +84,7 @@ namespace Microsoft.Terminal.Control PathTranslationStyle PathTranslationStyle { get; }; String DragDropDelimiter { get; }; + Microsoft.Terminal.Control.MiddleClickAction MiddleClickAction { get; }; // NOTE! When adding something here, make sure to update ControlProperties.h too! }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index f549dc62584..a914e796a8b 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1995,6 +1995,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation else { const auto cursorPosition = point.Position(); + if (point.Properties().IsMiddleButtonPressed() && !_core.IsVtMouseModeEnabled()) + { + const auto action = _core.Settings().MiddleClickAction(); + if (action == Control::MiddleClickAction::Pan) + { + _isPanning = true; + _panningAnchorPos = cursorPosition; + } + } _interactivity.PointerPressed(point.PointerId(), TermControl::GetPressedMouseButtons(point), TermControl::GetPointerUpdateKind(point), @@ -2019,7 +2028,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation return; } - RestorePointerCursor.raise(*this, nullptr); + if (!_isPanning) + { + RestorePointerCursor.raise(*this, nullptr); + } const auto ptr = args.Pointer(); const auto point = args.GetCurrentPoint(*this); @@ -2041,11 +2053,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation ControlKeyStates(args.KeyModifiers()), pixelPosition); + if (_isPanning && _panningAnchorPos) + { + const auto dy = cursorPosition.Y - _panningAnchorPos->Y; + constexpr auto MinPanDist = 5.0; + + if (const auto window = CoreWindow::GetForCurrentThread()) + { + if (std::abs(dy) > MinPanDist) + { + window.PointerCursor(CoreCursor(CoreCursorType::SizeNorthSouth, 1)); + } + else + { + window.PointerCursor(CoreCursor(CoreCursorType::SizeAll, 1)); + } + } + + if (std::abs(dy) > MinPanDist) + { + const auto scrollDist = dy > 0 ? (dy - MinPanDist) : (dy + MinPanDist); + const auto newVelocity = _GetAutoScrollSpeed(std::abs(scrollDist)) * (dy > 0 ? 1.0 : -1.0); + _TryStartAutoScroll(point, newVelocity); + } + else + { + _TryStopAutoScroll(ptr.PointerId()); + } + } // GH#9109 - Only start an auto-scroll when the drag actually // started within our bounds. Otherwise, someone could start a drag // outside the terminal control, drag into the padding, and trick us // into starting to scroll. - if (!suppressFurtherHandling && _focused && _pointerPressedInBounds && point.Properties().IsLeftButtonPressed()) + else if (!suppressFurtherHandling && _focused && _pointerPressedInBounds && point.Properties().IsLeftButtonPressed()) { // We want to find the distance relative to the bounds of the // SwapChainPanel, not the entire control. If they drag out of @@ -2124,6 +2164,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation _interactivity.TouchReleased(); } + if (_isPanning) + { + _isPanning = false; + _panningAnchorPos = std::nullopt; + if (const auto window = CoreWindow::GetForCurrentThread()) + { + window.PointerCursor(CoreCursor(CoreCursorType::IBeam, 1)); + } + } + _TryStopAutoScroll(ptr.PointerId()); args.Handled(true); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 4011c76fc22..56166d0c099 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -308,6 +308,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation SafeDispatcherTimer _autoScrollTimer; std::optional _lastAutoScrollUpdateTime; bool _pointerPressedInBounds{ false }; + bool _isPanning{ false }; + std::optional _panningAnchorPos; winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr }; winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellDarkAnimation{ nullptr }; diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index b92994c2d66..985435c1ebc 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -54,6 +54,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::IntenseStyle, IntenseTextStyle); DEFINE_ENUM_MAP(Microsoft::Terminal::Core::AdjustTextMode, AdjustIndistinguishableColors); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle); + DEFINE_ENUM_MAP(Microsoft::Terminal::Control::MiddleClickAction, MiddleClickAction); // Actions DEFINE_ENUM_MAP(Microsoft::Terminal::Settings::Model::ResizeDirection, ResizeDirection); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index e8586704506..36dad621d56 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -52,6 +52,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap IntenseTextStyle(); static winrt::Windows::Foundation::Collections::IMap AdjustIndistinguishableColors(); static winrt::Windows::Foundation::Collections::IMap PathTranslationStyle(); + static winrt::Windows::Foundation::Collections::IMap MiddleClickAction(); // Actions static winrt::Windows::Foundation::Collections::IMap ResizeDirection(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index 7f52aace494..a8d2a2944f5 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -34,6 +34,7 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap FontWeight { get; }; static Windows.Foundation.Collections.IMap IntenseTextStyle { get; }; static Windows.Foundation.Collections.IMap PathTranslationStyle { get; }; + static Windows.Foundation.Collections.IMap MiddleClickAction { get; }; // Actions static Windows.Foundation.Collections.IMap ResizeDirection { get; }; diff --git a/src/cascadia/TerminalSettingsModel/IInheritable.h b/src/cascadia/TerminalSettingsModel/IInheritable.h index aae47f61db5..f25e3e81bb0 100644 --- a/src/cascadia/TerminalSettingsModel/IInheritable.h +++ b/src/cascadia/TerminalSettingsModel/IInheritable.h @@ -136,7 +136,7 @@ private: \ return std::nullopt; \ } \ \ - auto _get##name##OverrideSourceImpl()->decltype(get_strong()) \ + auto _get##name##OverrideSourceImpl() -> decltype(get_strong()) \ { \ /*we have a value*/ \ if (_##name) \ @@ -159,7 +159,7 @@ private: \ } \ \ auto _get##name##OverrideSourceAndValueImpl() \ - ->std::pair \ + -> std::pair \ { \ /*we have a value*/ \ if (_##name) \ diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 96cd878b800..81af73d1fb3 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -80,38 +80,39 @@ Author(s): // * TerminalSettings.cpp: TerminalSettings::_ApplyProfileSettings // * IControlSettings.idl or ICoreSettings.idl // * ControlProperties.h -#define MTSM_PROFILE_SETTINGS(X) \ - X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ - X(bool, SnapOnInput, "snapOnInput", true) \ - X(bool, AltGrAliasing, "altGrAliasing", true) \ - X(hstring, AnswerbackMessage, "answerbackMessage") \ - X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ - X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ - X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ - X(hstring, StartingDirectory, "startingDirectory") \ - X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ - X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ - X(guid, ConnectionType, "connectionType") \ - X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ - X(hstring, TabTitle, "tabTitle") \ - X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ - X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ - X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ - X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ - X(bool, Elevate, "elevate", false) \ - X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ - X(bool, ShowMarks, "showMarksOnScrollbar", false) \ - X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ - X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ - X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ - X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ - X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ - X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ - X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ - X(bool, AllowOscNotifications, "compatibility.allowOSC777", false) \ - X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ - X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ - X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) +#define MTSM_PROFILE_SETTINGS(X) \ + X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \ + X(bool, SnapOnInput, "snapOnInput", true) \ + X(bool, AltGrAliasing, "altGrAliasing", true) \ + X(hstring, AnswerbackMessage, "answerbackMessage") \ + X(hstring, Commandline, "commandline", L"%SystemRoot%\\System32\\cmd.exe") \ + X(Microsoft::Terminal::Control::ScrollbarState, ScrollState, "scrollbarState", Microsoft::Terminal::Control::ScrollbarState::Visible) \ + X(Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, "antialiasingMode", Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ + X(hstring, StartingDirectory, "startingDirectory") \ + X(IMediaResource, Icon, "icon", implementation::MediaResource::FromString(L"\uE756")) \ + X(bool, SuppressApplicationTitle, "suppressApplicationTitle", false) \ + X(guid, ConnectionType, "connectionType") \ + X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic) \ + X(hstring, TabTitle, "tabTitle") \ + X(Model::BellStyle, BellStyle, "bellStyle", BellStyle::Audible) \ + X(IEnvironmentVariableMap, EnvironmentVariables, "environment", nullptr) \ + X(bool, RightClickContextMenu, "rightClickContextMenu", false) \ + X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ + X(bool, Elevate, "elevate", false) \ + X(bool, AutoMarkPrompts, "autoMarkPrompts", true) \ + X(bool, ShowMarks, "showMarksOnScrollbar", false) \ + X(bool, RepositionCursorWithMouse, "experimental.repositionCursorWithMouse", false) \ + X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \ + X(bool, RainbowSuggestions, "experimental.rainbowSuggestions", false) \ + X(bool, ForceVTInput, "compatibility.input.forceVT", false) \ + X(bool, AllowKittyKeyboardMode, "compatibility.kittyKeyboardMode", true) \ + X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \ + X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \ + X(bool, AllowOscNotifications, "compatibility.allowOSC777", false) \ + X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \ + X(hstring, DragDropDelimiter, "dragDropDelimiter", L" ") \ + X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None) \ + X(Microsoft::Terminal::Control::MiddleClickAction, MiddleClickAction, "experimental.middleClickAction", Microsoft::Terminal::Control::MiddleClickAction::Pan) // Intentionally omitted Profile settings: // * Name diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 3702003acdc..72937ccc055 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -97,5 +97,6 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle); INHERITABLE_PROFILE_SETTING(String, DragDropDelimiter); + INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.MiddleClickAction, MiddleClickAction); } } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 878b09f89ca..4d3b0aa95a9 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -874,6 +874,15 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::PathTranslationStyle) }; }; +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::MiddleClickAction) +{ + static constexpr std::array mappings = { + pair_type{ "pan", ValueType::Pan }, + pair_type{ "paste", ValueType::Paste }, + pair_type{ "none", ValueType::None }, + }; +}; + JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste) { JSON_MAPPINGS(3) = { diff --git a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp index 1b538b8505d..e5801502300 100644 --- a/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlInteractivityTests.cpp @@ -42,6 +42,7 @@ namespace ControlUnitTests TEST_METHOD(GetMouseEventsInTest); TEST_METHOD(AltBufferClampMouse); + TEST_METHOD(TestMiddleClick); TEST_CLASS_SETUP(ClassSetup) { @@ -1070,4 +1071,32 @@ namespace ControlUnitTests cursorPosition1.to_core_point()); VERIFY_ARE_EQUAL(0u, expectedOutput.size(), L"Validate we drained all the expected output"); } + + void ControlInteractivityTests::TestMiddleClick() + { + WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{}; + + auto [settings, conn] = _createSettingsAndConnection(); + auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn); + _standardInit(core, interactivity); + + const auto modifiers = ControlKeyStates(); + const auto midMouseDown{ Control::MouseButtonState::IsMiddleButtonDown }; + + bool pasteRequested = false; + interactivity->PasteFromClipboard([&](auto&&, auto&&) { + pasteRequested = true; + }); + + settings->MiddleClickAction(Control::MiddleClickAction::Paste); + + interactivity->PointerPressed(0, + midMouseDown, + WM_MBUTTONDOWN, + 0, + modifiers, + Core::Point{ 0, 0 }); + + VERIFY_IS_TRUE(pasteRequested); + } } diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index e54cb14a249..3297b4a9762 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -89,4 +89,5 @@ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, 0) \ X(bool, RightClickContextMenu, false) \ X(winrt::Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, winrt::Microsoft::Terminal::Control::PathTranslationStyle::None) \ - X(winrt::hstring, DragDropDelimiter, L" ") + X(winrt::hstring, DragDropDelimiter, L" ") \ + X(winrt::Microsoft::Terminal::Control::MiddleClickAction, MiddleClickAction, winrt::Microsoft::Terminal::Control::MiddleClickAction::Pan)