Skip to content

Commit

Permalink
Fix handling escape sequences encoded in win32-input-mode events
Browse files Browse the repository at this point in the history
  • Loading branch information
magiblot committed Sep 28, 2023
1 parent 5188218 commit ae1a685
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 69 deletions.
21 changes: 17 additions & 4 deletions source/platform/terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,11 @@ class Win32InputModeUnwrapper : public InputGetter
InputGetter ∈
InputState &state;

enum { maxSize = 31 };

ushort ungetSize {0};
short ungetBuffer[maxSize];

public:

Win32InputModeUnwrapper(InputGetter &aIn, InputState &aState) noexcept :
Expand All @@ -750,23 +755,31 @@ class Win32InputModeUnwrapper : public InputGetter

int get() noexcept override
{
if (ungetSize > 0)
return ungetBuffer[--ungetSize];

GetChBuf buf(in);
CSIData csi;
TEvent ev {};
// If we get a win32-input-mode event with no scan code and
// a single-byte character, take just that character.
if ( buf.get() == '\x1B' && buf.get() == '['
&& csi.readFrom(buf) && csi.terminator == '_'
&& parseWin32InputModeKey(csi, ev, state) == Accepted
&& ev.keyDown.charScan.scanCode == 0
&& ev.keyDown.textLength == 1 )
return ev.keyDown.text[0];
return (uchar) ev.keyDown.text[0];
buf.reject();
return -1;
}

void unget(int) noexcept override
void unget(int key) noexcept override
{
// Do nothing. It is desirable not to reject win32-input-mode events,
// as that would just spill escape sequences into the input queue.
// We could reconstruct the original win32-input-mode event and call
// 'in.unget()', but there is no need for that. However, we still need
// to be able to temporarily store characters returned by 'get()'.
if (ungetSize < maxSize)
ungetBuffer[ungetSize++] = (short) key;
}
};

Expand Down
85 changes: 60 additions & 25 deletions test/platform/terminal.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <test.h>
#include "terminal.test.h"
#include <vector>

static bool operator==(const KeyDownEvent &a, const KeyDownEvent &b)
{
Expand Down Expand Up @@ -53,40 +54,74 @@ TEST(TermIO, ShouldNormalizeKeys)

TEST(TermIO, ShouldReadWin32InputModeKeys)
{
static const TestCase<TStringView, ParseResultEvent> testCases[] =
static const TestCase<TStringView, std::vector<TEvent>> testCases[] =
{
{"[65;30;65;1;16;1_", {Accepted, keyDownEv(0x1e41, kbShift, "A")}},
{"[65;30;65;1;16_", {Accepted, keyDownEv(0x1e41, kbShift, "A")}},
{"[16;42;0;0;0;1_", {Ignored}},
{"[65;30;97;1;0;1_", {Accepted, keyDownEv(0x1e61, 0x0000, "a")}},
{"[65;30;97;1_", {Accepted, keyDownEv(0x1e61, 0x0000, "a")}},
{"[112;59;0;1;8;1_", {Accepted, keyDownEv(kbCtrlF1, kbLeftCtrl, "")}},
{"[112;59;;1;8_", {Accepted, keyDownEv(kbCtrlF1, kbLeftCtrl, "")}},
{"[112;59;0;0;8;1_", {Ignored}},
{"\x1B[65;30;65;1;16;1_", {keyDownEv(0x1e41, kbShift, "A")}},
{"\x1B[65;30;65;1;16_", {keyDownEv(0x1e41, kbShift, "A")}},
{"\x1B[16;42;0;0;0;1_", {}},
{"\x1B[65;30;97;1;0;1_", {keyDownEv(0x1e61, 0x0000, "a")}},
{"\x1B[65;30;97;1_", {keyDownEv(0x1e61, 0x0000, "a")}},
{"\x1B[112;59;0;1;8;1_", {keyDownEv(kbCtrlF1, kbLeftCtrl, "")}},
{"\x1B[112;59;;1;8_", {keyDownEv(kbCtrlF1, kbLeftCtrl, "")}},
{"\x1B[112;59;0;0;8;1_", {}},
// https://github.com/microsoft/terminal/issues/15083
// SGR mouse event
{"[0;0;27;1;0;1_"
"\x1B[0;0;91;1;0;1_"
"\x1B[0;0;60;1;0;1_"
"\x1B[0;0;48;1;0;1_"
"\x1B[0;0;59;1;0;1_"
"\x1B[0;0;53;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;59;1;0;1_"
"\x1B[0;0;49;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;77;1;0;1_", {Accepted, mouseEv({51, 11}, 0x0000, 0x0000, mbLeftButton, 0x0000)}},
{ // SGR mouse event
"\x1B[0;0;27;1;0;1_"
"\x1B[0;0;91;1;0;1_"
"\x1B[0;0;60;1;0;1_"
"\x1B[0;0;48;1;0;1_"
"\x1B[0;0;59;1;0;1_"
"\x1B[0;0;53;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;59;1;0;1_"
"\x1B[0;0;49;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;77;1;0;1_",
{mouseEv({51, 11}, 0x0000, 0x0000, mbLeftButton, 0x0000)},
},
{ // Paste event
"\x1B[17;29;0;1;8;1_" // Ctrl (press)
"\x1B[16;42;0;1;24;1_" // Shift (press)
"\x1B[0;0;27;1;0;1_" // \x1B[200~ (bracketed paste begin)
"\x1B[0;0;91;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;48;1;0;1_"
"\x1B[0;0;48;1;0;1_"
"\x1B[0;0;126;1;0;1_"
"\x1B[65;30;97;1;0;1_" // 'a' (press)
"\x1B[65;30;97;0;0;1_" // 'a' (release)
"\x1B[0;0;27;1;0;1_" // \x1B[201~ (bracketed paste end)
"\x1B[0;0;91;1;0;1_"
"\x1B[0;0;50;1;0;1_"
"\x1B[0;0;48;1;0;1_"
"\x1B[0;0;49;1;0;1_"
"\x1B[0;0;126;1;0;1_"
"\x1B[86;47;22;0;24;1_" // Ctrl+Shift+V (release)
"\x1B[17;29;0;0;16;1_" // Shift (release)
"\x1B[16;42;0;0;0;1_", // Ctrl (release)
{keyDownEv(0x1e61, kbPaste, "a")},
},
};

for (auto &testCase : testCases)
{
StrInputGetter in(testCase.input);
GetChBuf buf(in);
ParseResultEvent actual {};
std::vector<TEvent> actual {};
InputState state {};
actual.parseResult = TermIO::parseEscapeSeq(buf, actual.ev, state);
while (true)
{
GetChBuf buf(in);
TEvent ev {};
ParseResult result = TermIO::parseEvent(buf, ev, state);
if (state.bracketedPaste && ev.what == evKeyDown)
ev.keyDown.controlKeyState |= kbPaste;

if (result == Accepted)
actual.push_back(ev);
else if (result == Rejected)
break;
}
expectResultMatches(actual, testCase);
EXPECT_EQ(in.bytesLeft(), 0);
}
}

Expand Down
91 changes: 51 additions & 40 deletions test/platform/terminal.test.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,54 @@

#include <internal/terminal.h>

inline bool operator==(const TEvent &a, const TEvent &b)
{
if (a.what != b.what)
return false;
if (a.what == evNothing)
return true;
if (a.what == evKeyDown)
return
a.keyDown.keyCode == b.keyDown.keyCode &&
a.keyDown.controlKeyState == b.keyDown.controlKeyState &&
a.keyDown.getText() == b.keyDown.getText();
if (a.what == evMouse)
return
a.mouse.where == b.mouse.where &&
a.mouse.eventFlags == b.mouse.eventFlags &&
a.mouse.controlKeyState == b.mouse.controlKeyState &&
a.mouse.buttons == b.mouse.buttons &&
a.mouse.wheel == b.mouse.wheel;
abort();
}

inline std::ostream &operator<<(std::ostream &os, const TEvent &ev)
{
os << "{";
if (ev.what == evKeyDown)
{
os << "{";
printKeyCode(os, ev.keyDown.keyCode);
os << "}, {";
printControlKeyState(os, ev.keyDown.controlKeyState);
os << "}, '" << ev.keyDown.getText() << "'";
}
else if (ev.what == evMouse)
{
os << "(" << ev.mouse.where.x << "," << ev.mouse.where.y << ")";
os << ", ";
printMouseEventFlags(os, ev.mouse.eventFlags);
os << ", ";
printControlKeyState(os, ev.mouse.controlKeyState);
os << ", ";
printMouseButtonState(os, ev.mouse.buttons);
os << ", ";
printMouseWheelState(os, ev.mouse.wheel);
}
os << "}";
return os;
}

namespace tvision
{

Expand Down Expand Up @@ -41,29 +89,13 @@ struct ParseResultEvent
TEvent ev;
};

static bool operator==(const ParseResultEvent &a, const ParseResultEvent &b)
inline bool operator==(const ParseResultEvent &a, const ParseResultEvent &b)
{
if (a.parseResult != b.parseResult)
return false;
if (a.parseResult == Ignored)
return true;
if (a.ev.what != b.ev.what)
return false;
if (a.ev.what == evNothing)
return true;
if (a.ev.what == evKeyDown)
return
a.ev.keyDown.keyCode == b.ev.keyDown.keyCode &&
a.ev.keyDown.controlKeyState == b.ev.keyDown.controlKeyState &&
a.ev.keyDown.getText() == b.ev.keyDown.getText();
if (a.ev.what == evMouse)
return
a.ev.mouse.where == b.ev.mouse.where &&
a.ev.mouse.eventFlags == b.ev.mouse.eventFlags &&
a.ev.mouse.controlKeyState == b.ev.mouse.controlKeyState &&
a.ev.mouse.buttons == b.ev.mouse.buttons &&
a.ev.mouse.wheel == b.ev.mouse.wheel;
abort();
return a.ev == b.ev;
}

inline std::ostream &operator<<(std::ostream &os, const ParseResultEvent &p)
Expand All @@ -77,28 +109,7 @@ inline std::ostream &operator<<(std::ostream &os, const ParseResultEvent &p)
{
os << "Accepted, {";
printEventCode(os, p.ev.what);
os << ", {";
if (p.ev.what == evKeyDown)
{
os << "{";
printKeyCode(os, p.ev.keyDown.keyCode);
os << "}, {";
printControlKeyState(os, p.ev.keyDown.controlKeyState);
os << "}, '" << p.ev.keyDown.getText() << "'";
}
else if (p.ev.what == evMouse)
{
os << "(" << p.ev.mouse.where.x << "," << p.ev.mouse.where.y << ")";
os << ", ";
printMouseEventFlags(os, p.ev.mouse.eventFlags);
os << ", ";
printControlKeyState(os, p.ev.mouse.controlKeyState);
os << ", ";
printMouseButtonState(os, p.ev.mouse.buttons);
os << ", ";
printMouseWheelState(os, p.ev.mouse.wheel);
}
os << "}}";
os << ", " << p.ev << "}";
}
}
os << "}";
Expand Down

0 comments on commit ae1a685

Please sign in to comment.