Skip to content

Commit

Permalink
Add support for alternate scroll mode in Terminal
Browse files Browse the repository at this point in the history
_⚠️ targets #12561 ⚠️_

"Alternate scroll mode" is a neat little mode where the app wants mouse wheel events to come through as arrow keypresses instead, when in the alternate buffer. Now that we've got support for the alt buffer in the Terminal, we can support this as well.

* [x] Closes #3321
* [x] I work here
* [ ] Tests would be nice

Tested manually with

```bash
printf "\e[?1007h" ; man ps
```
  • Loading branch information
zadjii-msft committed Feb 24, 2022
1 parent bf24cdd commit 97029f6
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 9 deletions.
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _terminal != nullptr && _terminal->IsTrackingMouseInput();
}
bool ControlCore::ShouldSendAlternateScroll(const unsigned int uiButton,
const int32_t delta) const
{
return _terminal != nullptr && _terminal->ShouldSendAlternateScroll(uiButton, delta);
}

Core::Point ControlCore::CursorPosition() const
{
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void CursorOn(const bool isCursorOn);

bool IsVtMouseModeEnabled() const;
bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const;
Core::Point CursorPosition() const;

bool HasSelection() const;
Expand Down
21 changes: 19 additions & 2 deletions src/cascadia/TerminalControl/ControlInteractivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
const til::point terminalPosition = _getTerminalPosition(til::point{ pixelPosition });

// Short-circuit isReadOnly check to avoid warning dialog
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
// Short-circuit isReadOnly check to avoid warning dialog.
//
// GH#3321: Alternate scroll mode is a special type of mouse input mode
// where the terminal sends arrow keys when the user mouse wheels, but
// the client app doesn't care for other mouse input. It's tracked
// seperately from _canSendVTMouseInput.
if (!_core->IsInReadOnlyMode() &&
(_canSendVTMouseInput(modifiers) || _shouldSendAlternateScroll(modifiers, delta)))
{
// Most mouse event handlers call
// _trySendMouseEvent(point);
Expand Down Expand Up @@ -571,6 +577,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core->IsVtMouseModeEnabled();
}

bool ControlInteractivity::_shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta)
{
// If the user is holding down Shift, suppress mouse events
// TODO GH#4875: disable/customize this functionality
if (modifiers.IsShiftPressed())
{
return false;
}
return _core->ShouldSendAlternateScroll(WM_MOUSEWHEEL, delta);
}

// Method Description:
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
// Arguments:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void _hyperlinkHandler(const std::wstring_view uri);
bool _canSendVTMouseInput(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
bool _shouldSendAlternateScroll(const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta);

void _sendPastedTextToConnection(std::wstring_view wstr);
til::point _getTerminalPosition(const til::point pixelPosition);
Expand Down
14 changes: 14 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,20 @@ bool Terminal::IsTrackingMouseInput() const noexcept
return _terminalInput->IsTrackingMouseInput();
}

// Routine Description:
// - Relays if we are in alternate scroll mode, a special type of mouse input
// mode where scrolling sends the arrow keypresses, but the app doesn't
// otherwise want mouse input.
// Parameters:
// - <none>
// Return value:
// - true, if we are tracking mouse input. False, otherwise
bool Terminal::ShouldSendAlternateScroll(const unsigned int uiButton,
const int32_t delta) const noexcept
{
return _terminalInput->ShouldSendAlternateScroll(uiButton, ::base::saturated_cast<short>(delta));
}

// Method Description:
// - Given a coord, get the URI at that location
// Arguments:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class Microsoft::Terminal::Core::Terminal final :

void TrySnapOnInput() override;
bool IsTrackingMouseInput() const noexcept;
bool ShouldSendAlternateScroll(const unsigned int uiButton, const int32_t delta) const noexcept;

std::wstring GetHyperlinkAtPosition(const COORD position);
uint16_t GetHyperlinkIdAtPosition(const COORD position);
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,10 @@ void Terminal::UseAlternateScreenBuffer()
// update all the hyperlinks on the screen
UpdatePatternsUnderLock();

// GH#3321: Make sure we let the TerminalInput know that we switched
// buffers. This might affect how we interpret certain mouse events.
_terminalInput->UseAlternateScreenBuffer();

// Update scrollbars
_NotifyScrollEvent();

Expand Down Expand Up @@ -661,6 +665,10 @@ void Terminal::UseMainScreenBuffer()
// update all the hyperlinks on the screen
UpdatePatternsUnderLock();

// GH#3321: Make sure we let the TerminalInput know that we switched
// buffers. This might affect how we interpret certain mouse events.
_terminalInput->UseMainScreenBuffer();

// Update scrollbars
_NotifyScrollEvent();

Expand Down
13 changes: 7 additions & 6 deletions src/terminal/input/mouseInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ bool TerminalInput::HandleMouse(const COORD position,
// 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);
return IsTrackingMouseInput() || ShouldSendAlternateScroll(button, delta);
}

// We're ready to send this event through, but first we need to clear the accumulated;
Expand All @@ -333,7 +333,7 @@ bool TerminalInput::HandleMouse(const COORD position,
}

bool success = false;
if (_ShouldSendAlternateScroll(button, delta))
if (ShouldSendAlternateScroll(button, delta))
{
success = _SendAlternateScroll(delta);
}
Expand Down Expand Up @@ -542,11 +542,12 @@ std::wstring TerminalInput::_GenerateSGRSequence(const COORD position,
// - delta: The scroll wheel delta of the input event
// Return value:
// True iff the alternate buffer is active and alternate scroll mode is enabled and the event is a mouse wheel event.
bool TerminalInput::_ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
bool TerminalInput::ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept
{
return _mouseInputState.inAlternateBuffer &&
_inputMode.test(Mode::AlternateScroll) &&
(button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0;
const bool inAltBuffer{ _mouseInputState.inAlternateBuffer };
const bool inAltScroll{ _inputMode.test(Mode::AlternateScroll) };
const bool wasMouseWheel{ (button == WM_MOUSEWHEEL || button == WM_MOUSEHWHEEL) && delta != 0 };
return inAltBuffer && inAltScroll && wasMouseWheel;
}

// Routine Description:
Expand Down
2 changes: 1 addition & 1 deletion src/terminal/input/terminalInput.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ namespace Microsoft::Console::VirtualTerminal
const MouseButtonState state);

bool IsTrackingMouseInput() const noexcept;
bool ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
#pragma endregion

#pragma region MouseInputState Management
Expand Down Expand Up @@ -127,7 +128,6 @@ namespace Microsoft::Console::VirtualTerminal
const short modifierKeyState,
const short delta);

bool _ShouldSendAlternateScroll(const unsigned int button, const short delta) const noexcept;
bool _SendAlternateScroll(const short delta) const noexcept;

static constexpr unsigned int s_GetPressedButton(const MouseButtonState state) noexcept;
Expand Down

1 comment on commit 97029f6

@github-actions
Copy link

@github-actions github-actions bot commented on 97029f6 Feb 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@check-spelling-bot Report

Unrecognized words, please review:

  • deduplicating
  • seperately
Previously acknowledged words that are now absent azurewebsites cxcy debolden deconstructed devicefamily guardxfg LLVM MSDL ned NOWAIT pgorepro pgort PGU Timeline timelines unintense WResult xfg
To accept these unrecognized words as correct (and remove the previously acknowledged and now absent words), run the following commands

... in a clone of the git@github.com:microsoft/terminal.git repository
on the dev/migrie/b/3321-altscroll-in-terminal branch:

update_files() {
perl -e '
my @expect_files=qw('".github/actions/spelling/expect/alphabet.txt
.github/actions/spelling/expect/expect.txt
.github/actions/spelling/expect/web.txt"');
@ARGV=@expect_files;
my @stale=qw('"$patch_remove"');
my $re=join "|", @stale;
my $suffix=".".time();
my $previous="";
sub maybe_unlink { unlink($_[0]) if $_[0]; }
while (<>) {
if ($ARGV ne $old_argv) { maybe_unlink($previous); $previous="$ARGV$suffix"; rename($ARGV, $previous); open(ARGV_OUT, ">$ARGV"); select(ARGV_OUT); $old_argv = $ARGV; }
next if /^(?:$re)(?:(?:\r|\n)*$| .*)/; print;
}; maybe_unlink($previous);'
perl -e '
my $new_expect_file=".github/actions/spelling/expect/97029f64619806af32bbb4c933fad1e9ea47c0fd.txt";
use File::Path qw(make_path);
use File::Basename qw(dirname);
make_path (dirname($new_expect_file));
open FILE, q{<}, $new_expect_file; chomp(my @words = <FILE>); close FILE;
my @add=qw('"$patch_add"');
my %items; @items{@words} = @words x (1); @items{@add} = @add x (1);
@words = sort {lc($a)."-".$a cmp lc($b)."-".$b} keys %items;
open FILE, q{>}, $new_expect_file; for my $word (@words) { print FILE "$word\n" if $word =~ /\w/; };
close FILE;
system("git", "add", $new_expect_file);
'
}

comment_json=$(mktemp)
curl -L -s -S \
  --header "Content-Type: application/json" \
  "https://api.github.com/repos/microsoft/terminal/comments/67399355" > "$comment_json"
comment_body=$(mktemp)
jq -r .body < "$comment_json" > $comment_body
rm $comment_json

patch_remove=$(perl -ne 'next unless s{^</summary>(.*)</details>$}{$1}; print' < "$comment_body")
  

patch_add=$(perl -e '$/=undef;
$_=<>;
s{<details>.*}{}s;
s{^#.*}{};
s{\n##.*}{};
s{(?:^|\n)\s*\*}{}g;
s{\s+}{ }g;
print' < "$comment_body")
  
update_files
rm $comment_body
git add -u
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

⚠️ The command is written for posix shells. You can copy the contents of each perl command excluding the outer ' marks and dropping any '"/"' quotation mark pairs into a file and then run perl file.pl from the root of the repository to run the code. Alternatively, you can manually insert the items...

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spelling/allow/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spelling/allow/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spelling/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spelling/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

🗜️ If you see a bunch of garbage

If it relates to a ...

well-formed pattern

See if there's a pattern that would match it.

If not, try writing one and adding it to a patterns/{file}.txt.

Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

Note that patterns can't match multiline strings.

binary-ish string

Please add a file path to the excludes.txt file instead of just accepting the garbage.

File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

Please sign in to comment.