Expand Up
@@ -7,6 +7,7 @@
#include < internal/constmap.h>
#include < internal/constarr.h>
#include < internal/codepage.h>
#include < internal/win32con.h>
#include < internal/getenv.h>
#include < internal/base64.h>
#include < internal/utf8.h>
Expand Down
Expand Up
@@ -176,6 +177,78 @@ static bool keyFromLetter(uint letter, uint mod, KeyDownEvent &keyDown) noexcept
return true ;
}
void GetChBuf::reject () noexcept
{
while (size)
unget ();
}
// getNum, getInt: INVARIANT: the last non-digit read key (or -1)
// can be accessed with 'last()' and can also be ungetted.
bool GetChBuf::getNum (uint &result) noexcept
{
uint num = 0 , digits = 0 ;
int k;
while ((k = get (true )) != -1 && ' 0' <= k && k <= ' 9' )
{
num = 10 * num + (k - ' 0' );
++digits;
}
if (digits)
return (result = num), true ;
return false ;
}
bool GetChBuf::getInt (int &result) noexcept
{
int num = 0 , digits = 0 , sign = 1 ;
int k = get (true );
if (k == ' -' )
{
sign = -1 ;
k = get (true );
}
while (k != -1 && ' 0' <= k && k <= ' 9' )
{
num = 10 * num + (k - ' 0' );
++digits;
k = get (true );
}
if (digits)
return (result = sign*num), true ;
return false ;
}
bool GetChBuf::readStr (TStringView str) noexcept
{
size_t origSize = size;
size_t i = 0 ;
while (i < str.size () && get () == str[i])
++i;
if (i == str.size ())
return true ;
while (origSize < size)
unget ();
return false ;
}
bool CSIData::readFrom (GetChBuf &buf) noexcept
// Pre: "\x1B[" has just been read.
{
length = 0 ;
for (uint i = 0 ; i < maxLength; ++i)
{
if (!buf.getNum (_val[i]))
_val[i] = UINT_MAX;
int k = buf.last ();
if (k == -1 ) return false ;
if ((terminator = (uint ) k) != ' ;' )
return (length = i + 1 ), true ;
}
return false ;
}
// The default mouse experience with Ncurses is not always good. To work around
// some issues, we request and parse mouse events manually.
Expand Down
Expand Up
@@ -209,6 +282,7 @@ void TermIO::keyModsOn(StdioCtl &io) noexcept
" \x1B [?2004h" // Enable bracketed paste.
" \x1B [>4;1m" // Enable modifyOtherKeys (XTerm).
" \x1B [>1u" // Disambiguate escape codes (Kitty).
" \x1B [?9001h" // Enable win32-input-mode (Conpty).
far2lEnableSeq
;
io.write (seq.data (), seq.size ());
Expand All
@@ -235,6 +309,7 @@ void TermIO::keyModsOff(StdioCtl &io, EventSource &source, InputState &state) no
{
TStringView seq = far2lPingSeq
far2lDisableSeq
" \x1B [?9001l" // Disable win32-input-mode (Conpty).
" \x1B [<u" // Restore previous keyboard mode (Kitty).
" \x1B [>4m" // Reset modifyOtherKeys (XTerm).
" \x1B [?2004l" // Disable bracketed paste.
Expand Down
Expand Up
@@ -307,10 +382,15 @@ ParseResult TermIO::parseEscapeSeq(GetChBuf &buf, TEvent &ev, InputState &state)
CSIData csi;
if (csi.readFrom (buf))
{
if (csi.terminator () == ' u' )
return parseFixTermKey (csi, ev);
else
return parseCSIKey (csi, ev, state);
switch (csi.terminator )
{
case ' u' :
return parseFixTermKey (csi, ev);
case ' _' :
return parseWin32InputModeKeyOrEscapeSeq (csi, buf.in , ev, state);
default :
return parseCSIKey (csi, ev, state);
}
}
break ;
}
Expand Down
Expand Up
@@ -458,10 +538,10 @@ ParseResult TermIO::parseCSIKey(const CSIData &csi, TEvent &ev, InputState &stat
// https://invisible-island.net/xterm/xterm-function-keys.html
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
{
uint terminator = csi.terminator () ;
uint terminator = csi.terminator ;
if (csi.length == 1 && terminator == ' ~' )
{
switch (csi.val [ 0 ] )
switch (csi.getValue ( 0 ) )
{
case 1 : ev.keyDown = {{kbHome}}; break ;
case 2 : ev.keyDown = {{kbIns}}; break ;
Expand Down
Expand Up
@@ -497,23 +577,23 @@ ParseResult TermIO::parseCSIKey(const CSIData &csi, TEvent &ev, InputState &stat
default : return Rejected;
}
}
else if (csi.length == 1 && csi.val [ 0 ] == 1 )
else if (csi.length == 1 && csi.getValue ( 0 ) == 1 )
{
if (!keyFromLetter (terminator, XTermModDefault, ev.keyDown ))
return Rejected;
}
else if (csi.length == 2 )
{
uint mod = csi.val [ 1 ] ;
if (csi.val [ 0 ] == 1 )
uint mod = csi.getValue ( 1 ) ;
if (csi.getValue ( 0 ) == 1 )
{
if (!keyFromLetter (terminator, mod, ev.keyDown ))
return Rejected;
}
else if (terminator == ' ~' )
{
ushort keyCode = 0 ;
switch (csi.val [ 0 ] )
switch (csi.getValue ( 0 ) )
{
case 2 : keyCode = kbIns; break ;
case 3 : keyCode = kbDel; break ;
Expand All
@@ -534,16 +614,16 @@ ParseResult TermIO::parseCSIKey(const CSIData &csi, TEvent &ev, InputState &stat
case 29 : keyCode = kbNoKey; break ; // Menu key (XTerm).
default : return Rejected;
}
ev.keyDown = keyWithXTermMods (keyCode, csi.val [ 1 ] );
ev.keyDown = keyWithXTermMods (keyCode, csi.getValue ( 1 ) );
}
else
return Rejected;
}
else if (csi.length == 3 && csi.val [ 0 ] == 27 && terminator == ' ~' )
else if (csi.length == 3 && csi.getValue ( 0 ) == 27 && terminator == ' ~' )
{
// XTerm's "modifyOtherKeys" mode.
uint key = csi.val [ 2 ] ;
uint mod = csi.val [ 1 ] ;
uint key = csi.getValue ( 2 ) ;
uint mod = csi.getValue ( 1 ) ;
if (!keyFromCodepoint (key, mod, ev.keyDown ))
return Ignored;
}
Expand Down
Expand Up
@@ -571,11 +651,11 @@ ParseResult TermIO::parseFixTermKey(const CSIData &csi, TEvent &ev) noexcept
// http://www.leonerd.org.uk/hacks/fixterms/
{
if (csi.length < 1 || csi.terminator () != ' u' )
if (csi.length < 1 || csi.terminator != ' u' )
return Rejected;
uint key = csi.val [ 0 ] ;
uint mods = (csi.length > 1 ) ? max (csi.val [ 1 ] , 1 ) : 1 ;
uint key = csi.getValue ( 0 ) ;
uint mods = (csi.length > 1 ) ? max (csi.getValue ( 1 ) , 1 ) : 1 ;
if (keyFromCodepoint (key, mods, ev.keyDown ))
{
ev.what = evKeyDown;
Expand Down
Expand Up
@@ -630,6 +710,83 @@ ParseResult TermIO::parseOSC(GetChBuf &buf, InputState &state) noexcept
return Ignored;
}
static ParseResult parseWin32InputModeKey (const CSIData &csi, TEvent &ev, InputState &state) noexcept
// https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
{
if (csi.terminator != ' _' )
return Rejected;
KEY_EVENT_RECORD kev;
kev.wVirtualKeyCode = (ushort ) csi.getValue (0 , 0 );
kev.wVirtualScanCode = (ushort ) csi.getValue (1 , 0 );
kev.uChar .UnicodeChar = (ushort ) csi.getValue (2 , 0 );
kev.bKeyDown = (ushort ) csi.getValue (3 , 0 );
kev.dwControlKeyState = (ushort ) csi.getValue (4 , 0 );
kev.wRepeatCount = (ushort ) csi.getValue (5 , 1 );
if (kev.bKeyDown && getWin32Key (kev, ev, state))
{
TermIO::normalizeKey (ev.keyDown );
return Accepted;
}
return Ignored;
}
// Due to issue https://github.com/microsoft/terminal/issues/15083, Conpty will
// emit ANSI escape sequences wrapped in win32-input-mode events. This class
// allows handling these sequences properly.
class Win32InputModeUnwrapper : public InputGetter
{
InputGetter ∈
InputState &state;
public:
Win32InputModeUnwrapper (InputGetter &aIn, InputState &aState) noexcept :
in (aIn), state(aState)
{
}
int get () noexcept override
{
GetChBuf buf (in);
CSIData csi;
TEvent ev {};
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 ];
buf.reject ();
return -1 ;
}
void unget (int ) 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.
}
};
ParseResult TermIO::parseWin32InputModeKeyOrEscapeSeq (const CSIData &csi, InputGetter &in, TEvent &ev, InputState &state) noexcept
{
ParseResult res = parseWin32InputModeKey (csi, ev, state);
if (res == Accepted && ev.keyDown == 0x001B )
{
// We received the initiator of an escape sequence wrapped in
// win32-input-mode events.
Win32InputModeUnwrapper unwrapper (in, state);
GetChBuf buf (unwrapper);
res = parseEscapeSeq (buf, ev, state);
// Avoid propagating 'Rejected' because we have used a secondary GetChBuf.
if (res != Accepted)
res = Ignored;
}
return res;
}
static bool setOsc52Clipboard (StdioCtl &io, TStringView text, InputState &state) noexcept
{
TStringView prefix = " \x1B ]52;;" ;
Expand Down