Skip to content

Commit fedf115

Browse files
Merge pull request #1894 from contour-terminal/fix/modifier-keys-no-scroll
Fix modifier-only keys scrolling viewport to bottom
2 parents 55676f6 + 97859ab commit fedf115

File tree

5 files changed

+113
-29
lines changed

5 files changed

+113
-29
lines changed

metainfo.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<release version="0.6.3" urgency="medium" type="development">
108108
<description>
109109
<ul>
110+
<li>Fixes modifier-only key presses (Shift, Ctrl, Alt, etc.) incorrectly scrolling the viewport to the bottom when Kitty keyboard protocol ReportAllKeysAsEscapeCodes is enabled</li>
110111
<li>Fixes keyboard input being swallowed in programs like less and bat due to empty-string terminfo capabilities (#1861)</li>
111112
<li>Fixes ESC key not being sent to the application when CancelSelection input mapping has no mode restriction (#1839)</li>
112113
<li>Fixes selective erase (DECSED/DECSEL) incorrectly erasing the cursor line instead of the target line when erasing lines without protected characters (#28)</li>

src/vtbackend/InputGenerator.cpp

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -526,34 +526,6 @@ constexpr pair<unsigned, char> mapKey(Key key) noexcept
526526
crispy::unreachable();
527527
}
528528

529-
constexpr bool isModifierKey(Key key) noexcept
530-
{
531-
// clang-format off
532-
switch (key)
533-
{
534-
case Key::LeftShift:
535-
case Key::LeftControl:
536-
case Key::LeftAlt:
537-
case Key::LeftSuper:
538-
case Key::LeftHyper:
539-
case Key::LeftMeta:
540-
case Key::RightShift:
541-
case Key::RightControl:
542-
case Key::RightAlt:
543-
case Key::RightSuper:
544-
case Key::RightHyper:
545-
case Key::RightMeta:
546-
case Key::IsoLevel3Shift:
547-
case Key::IsoLevel5Shift:
548-
case Key::CapsLock:
549-
case Key::NumLock:
550-
return true;
551-
default:
552-
return false;
553-
}
554-
// clang-format on
555-
}
556-
557529
bool ExtendedKeyboardInputGenerator::generateKey(Key key, Modifiers modifiers, KeyboardEventType eventType)
558530
{
559531
if (!enabled(eventType))

src/vtbackend/InputGenerator.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,38 @@ enum class Key : uint8_t
184184
// NOLINTEND(readability-identifier-naming)
185185
};
186186

187+
/// Checks whether the given key is a modifier-only key (e.g. Shift, Control, Alt, etc.).
188+
///
189+
/// @param key The key to check.
190+
/// @return true if the key is a modifier-only key, false otherwise.
191+
constexpr bool isModifierKey(Key key) noexcept
192+
{
193+
// clang-format off
194+
switch (key)
195+
{
196+
case Key::LeftShift:
197+
case Key::LeftControl:
198+
case Key::LeftAlt:
199+
case Key::LeftSuper:
200+
case Key::LeftHyper:
201+
case Key::LeftMeta:
202+
case Key::RightShift:
203+
case Key::RightControl:
204+
case Key::RightAlt:
205+
case Key::RightSuper:
206+
case Key::RightHyper:
207+
case Key::RightMeta:
208+
case Key::IsoLevel3Shift:
209+
case Key::IsoLevel5Shift:
210+
case Key::CapsLock:
211+
case Key::NumLock:
212+
return true;
213+
default:
214+
return false;
215+
}
216+
// clang-format on
217+
}
218+
187219
std::string to_string(Key key);
188220

189221
enum class KeyMode : uint8_t

src/vtbackend/Terminal.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,8 @@ Handled Terminal::sendKeyEvent(Key key, Modifiers modifiers, KeyboardEventType e
824824
if (success)
825825
{
826826
flushInput();
827-
_viewport.scrollToBottom();
827+
if (!isModifierKey(key))
828+
_viewport.scrollToBottom();
828829
}
829830
return Handled { success };
830831
}

src/vtbackend/Terminal_test.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,84 @@ TEST_CASE("Terminal.BlinkingCursor", "[terminal]")
8888
}
8989
}
9090

91+
TEST_CASE("Terminal.ModifierKeysDoNotScrollViewport", "[terminal]")
92+
{
93+
// Set up a terminal with history capacity to allow scrollback
94+
auto mc = MockTerm { PageSize { LineCount(4), ColumnCount(6) }, LineCount(10) };
95+
auto& terminal = mc.terminal;
96+
97+
// Enable ReportAllKeysAsEscapeCodes (kitty keyboard protocol)
98+
terminal.keyboardProtocol().enter(vtbackend::KeyboardEventFlag::ReportAllKeysAsEscapeCodes);
99+
100+
// Fill terminal and generate scrollback history
101+
mc.writeToScreen("line1\r\n"
102+
"line2\r\n"
103+
"line3\r\n"
104+
"line4\r\n"
105+
"line5\r\n"
106+
"line6\r\n");
107+
108+
// Scroll up so viewport is not at bottom
109+
terminal.viewport().scrollUp(LineCount(2));
110+
REQUIRE(terminal.viewport().scrolled());
111+
auto const scrollOffsetBeforeKey = terminal.viewport().scrollOffset();
112+
113+
SECTION("modifier-only press does not scroll")
114+
{
115+
mc.resetReplyData();
116+
terminal.sendKeyEvent(vtbackend::Key::LeftShift,
117+
vtbackend::Modifiers { vtbackend::Modifier::Shift },
118+
vtbackend::KeyboardEventType::Press,
119+
std::chrono::steady_clock::now());
120+
121+
// Viewport should remain scrolled
122+
CHECK(terminal.viewport().scrolled());
123+
CHECK(terminal.viewport().scrollOffset() == scrollOffsetBeforeKey);
124+
// Escape sequence should still have been sent to the application
125+
CHECK(!mc.replyData().empty());
126+
}
127+
128+
SECTION("non-modifier press does scroll")
129+
{
130+
mc.resetReplyData();
131+
terminal.sendKeyEvent(vtbackend::Key::Enter,
132+
vtbackend::Modifiers { vtbackend::Modifier::None },
133+
vtbackend::KeyboardEventType::Press,
134+
std::chrono::steady_clock::now());
135+
136+
// Viewport should scroll to bottom
137+
CHECK(!terminal.viewport().scrolled());
138+
}
139+
140+
SECTION("various modifier keys")
141+
{
142+
auto const modifierKeys = std::vector<vtbackend::Key> {
143+
vtbackend::Key::LeftShift, vtbackend::Key::RightShift, vtbackend::Key::LeftControl,
144+
vtbackend::Key::RightControl, vtbackend::Key::LeftAlt, vtbackend::Key::RightAlt,
145+
vtbackend::Key::LeftSuper, vtbackend::Key::RightSuper, vtbackend::Key::LeftHyper,
146+
vtbackend::Key::RightHyper, vtbackend::Key::LeftMeta, vtbackend::Key::RightMeta,
147+
vtbackend::Key::IsoLevel3Shift, vtbackend::Key::IsoLevel5Shift, vtbackend::Key::CapsLock,
148+
vtbackend::Key::NumLock,
149+
};
150+
151+
for (auto const modKey: modifierKeys)
152+
{
153+
INFO("Testing modifier key: " << std::format("{}", modKey));
154+
155+
// Reset viewport to scrolled position
156+
terminal.viewport().scrollUp(LineCount(2));
157+
REQUIRE(terminal.viewport().scrolled());
158+
159+
terminal.sendKeyEvent(modKey,
160+
vtbackend::Modifiers { vtbackend::Modifier::None },
161+
vtbackend::KeyboardEventType::Press,
162+
std::chrono::steady_clock::now());
163+
164+
CHECK(terminal.viewport().scrolled());
165+
}
166+
}
167+
}
168+
91169
TEST_CASE("Terminal.DECCARA", "[terminal]")
92170
{
93171
auto mock = MockTerm { ColumnCount(5), LineCount(5) };

0 commit comments

Comments
 (0)