TVision
view release on metacpan or search on metacpan
tvision.git/source/platform/termio.cpp view on Meta::CPAN
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.
void TermIO::mouseOn(ConsoleCtl &con) noexcept
{
TStringView seq = "\x1B[?1001s" // Save old highlight mouse reporting.
"\x1B[?1000h" // Enable mouse reporting.
"\x1B[?1002h" // Enable mouse drag reporting.
"\x1B[?1006h" // Enable SGR extended mouse reporting.
;
con.write(seq.data(), seq.size());
}
void TermIO::mouseOff(ConsoleCtl &con) noexcept
{
TStringView seq = "\x1B[?1006l" // Disable SGR extended mouse reporting.
"\x1B[?1002l" // Disable mouse drag reporting.
"\x1B[?1000l" // Disable mouse reporting.
"\x1B[?1001r" // Restore old highlight mouse reporting.
;
con.write(seq.data(), seq.size());
}
void TermIO::keyModsOn(ConsoleCtl &con) noexcept
{
char buf[256];
strcpy(buf,
"\x1B[?1036s" // Save metaSendsEscape (XTerm).
"\x1B[?1036h" // Enable metaSendsEscape (XTerm).
"\x1B[?2004s" // Save bracketed paste.
"\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 // Enable far2l terminal extensions.
);
if (char *term = getenv("TERM"))
{
// Check for full OSC 52 clipboard support.
if (strstr(term, "alacritty") || strstr(term, "foot"))
strcat(buf,
// Request clipboard contents to see if they are readable. It is
// not safe to print this blindly so only do it for TERMs which
// we know should work.
"\x1B]52;;?\x07"
);
else
strcat(buf,
// Check for the 'kitty-query-clipboard_control' capability (XTGETTCAP).
"\x1BP+q6b697474792d71756572792d636c6970626f6172645f636f6e74726f6c\x1B\\"
// Check for 'allowWindowOps' (XTQALLOWED).
"\x1B]60\x1B\\"
);
}
strcat(buf,
// Some terminals do not recognize the sequences above and will display
// them on screen. Clear the screen to prevent this.
"\x1B[2J"
);
con.write(buf, strlen(buf));
}
void TermIO::keyModsOff(ConsoleCtl &con) noexcept
{
TStringView seq = 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.
"\x1B[?2004r" // Restore bracketed paste.
"\x1B[?1036r" // Restore metaSendsEscape (XTerm).
;
con.write(seq.data(), seq.size());
}
void TermIO::normalizeKey(KeyDownEvent &keyDown) noexcept
{
TKey tKey(keyDown);
ushort newMods = tKey.mods & (kbShift | kbLeftCtrl | kbLeftAlt);
if (newMods != 0)
{
// Modifier precedece: Shift < Ctrl < Alt.
int largestMod = (newMods & kbLeftAlt) ? 2
: (newMods & kbLeftCtrl) ? 1
: 0;
if (ushort keyCode = moddedKeyCodes[tKey.code][largestMod])
{
keyDown.keyCode = keyCode;
if (keyDown.charScan.charCode < ' ')
keyDown.textLength = 0;
}
}
// TKey does not distinguish left/right modifiers, so preserve those
// when available.
ushort origMods = keyDown.controlKeyState;
keyDown.controlKeyState =
((origMods | newMods) & ~(kbCtrlShift | kbAltShift))
| ((origMods & kbCtrlShift ? origMods : newMods) & kbCtrlShift)
| ((origMods & kbAltShift ? origMods : newMods) & kbAltShift)
;
}
ParseResult TermIO::parseEvent(GetChBuf &buf, TEvent &ev, InputState &state) noexcept
{
if (buf.get() == '\x1B')
return parseEscapeSeq(buf, ev, state);
return Rejected;
tvision.git/source/platform/termio.cpp view on Meta::CPAN
case 23: keyCode = kbF11; break;
case 24: keyCode = kbF12; break;
case 29: keyCode = kbNoKey; break; // Menu key (XTerm).
default: return Rejected;
}
ev.keyDown = keyWithXTermMods(keyCode, csi.getValue(1));
}
else
return Rejected;
}
else if (csi.length == 3 && csi.getValue(0) == 27 && terminator == '~')
{
// XTerm's "modifyOtherKeys" mode.
uint key = csi.getValue(2);
uint mod = csi.getValue(1);
if (!keyFromCodepoint(key, mod, ev.keyDown))
return Ignored;
}
else
return Rejected;
ev.what = evKeyDown;
return Accepted;
}
ParseResult TermIO::parseSS3Key(GetChBuf &buf, TEvent &ev) noexcept
// https://invisible-island.net/xterm/xterm-function-keys.html
// Pre: "\x1BO" has just been read.
// Konsole, IntelliJ.
{
uint mod;
if (!buf.getNum(mod)) return Rejected;
uint key = (uint) buf.last();
if (!keyFromLetter(key, mod, ev.keyDown)) return Rejected;
ev.what = evKeyDown;
return Accepted;
}
ParseResult TermIO::parseFixTermKey(const CSIData &csi, TEvent &ev) noexcept
// https://sw.kovidgoyal.net/kitty/keyboard-protocol.html
// http://www.leonerd.org.uk/hacks/fixterms/
{
if (csi.length < 1 || csi.terminator != 'u')
return Rejected;
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;
return Accepted;
}
return Ignored;
}
ParseResult TermIO::parseDCS(GetChBuf &buf, InputState &state) noexcept
// Pre: '\x1BP' has just been read.
{
if (char *s = readUntilBelOrSt(buf))
{
// We only get a DCS in response to our request for kitty capabilities.
if (strstr(s, "726561642d636c6970626f617264")) // 'read-clipboard'
state.hasFullOsc52 = true;
free(s);
}
return Ignored;
}
ParseResult TermIO::parseOSC(GetChBuf &buf, InputState &state) noexcept
// Pre: '\x1B]' has just been read.
{
if (char *s = readUntilBelOrSt(buf))
{
TStringView sv(s);
if (sv.size() > 3 && sv.substr(0, 3) == "52;") // OSC 52
{
if (char *begin = (char *) memchr(&sv[3], ';', sv.size() - 3))
{
if (!state.hasFullOsc52)
// We got a response to our initial request.
state.hasFullOsc52 = true;
else if (state.putPaste)
{
TStringView encoded = sv.substr(begin + 1 - &sv[0]);
if (char *pDecoded = (char *) malloc((encoded.size() * 3)/4 + 3))
{
TStringView decoded = decodeBase64(encoded, pDecoded);
state.putPaste(decoded);
free(pDecoded);
}
}
}
}
else if (sv.size() > 3 && sv.substr(0, 3) == "60;") // OSC 60
if (strstr(&sv[3], "allowWindowOps"))
state.hasFullOsc52 = true;
free(s);
}
return Ignored;
}
ParseResult TermIO::parseCPR(const CSIData &csi, InputState &state) noexcept
// Pre: csi.terminator == 'R'.
// We receive a Cursor Position Report as response to the Device Status Report
// request we make in 'consumeUnprocessedInput()'.
{
if (csi.length != 2)
return Rejected;
state.gotDsrResponse = true;
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
{
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);
tvision.git/source/platform/termio.cpp view on Meta::CPAN
{
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 (uchar) ev.keyDown.text[0];
buf.reject();
return -1;
}
void unget(int key) noexcept override
{
// 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;
}
};
ParseResult TermIO::parseWin32InputModeKeyOrEscapeSeq(const CSIData &csi, InputGetter &in, TEvent &ev, InputState &state) noexcept
// Pre: csi.terminator == '_'.
{
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(ConsoleCtl &con, TStringView text, InputState &state) noexcept
{
TStringView prefix = "\x1B]52;;";
TStringView suffix = "\x07";
if (char *buf = (char *) malloc(prefix.size() + suffix.size() + (text.size() * 4)/3 + 4))
{
memcpy(buf, prefix.data(), prefix.size());
TStringView b64 = encodeBase64(text, buf + prefix.size());
memcpy(buf + prefix.size() + b64.size(), suffix.data(), suffix.size());
con.write(buf, prefix.size() + b64.size() + suffix.size());
free(buf);
}
// Return false when there is no full OSC 52 support, even though we always
// make the request. This way, we can still use the internal clipboard.
return state.hasFullOsc52;
}
static bool requestOsc52Clipboard(ConsoleCtl &con, InputState &state) noexcept
{
if (state.hasFullOsc52)
{
TStringView seq = "\x1B]52;;?\x07";
con.write(seq.data(), seq.size());
return true;
}
return false;
}
bool TermIO::setClipboardText(ConsoleCtl &con, TStringView text, InputState &state) noexcept
{
return setFar2lClipboard(con, text, state)
|| setOsc52Clipboard(con, text, state);
}
bool TermIO::requestClipboardText(ConsoleCtl &con, void (&accept)(TStringView), InputState &state) noexcept
{
state.putPaste = &accept;
return requestFar2lClipboard(con, state)
|| requestOsc52Clipboard(con, state);
}
char *TermIO::readUntilBelOrSt(GetChBuf &buf) noexcept
// Returns a malloc-allocated and null-terminated string, or null.
{
size_t capacity = 1024;
size_t len = 0;
if (char *s = (char *) malloc(capacity))
{
int prev = '\0';
int c;
while (c = buf.getUnbuffered(), c != -1)
{
if (c == '\x07') // BEL
break;
if (c == '\\' && prev == '\x1B') // ST
{
len -= (len > 0);
break;
}
if (capacity == len + 1)
{
if (void *tmp = realloc(s, capacity *= 2))
s = (char *) tmp;
else
s = (free(s), nullptr);
}
if (s)
s[len++] = (char) c;
prev = c;
}
if (s)
s[len] = '\0';
return s;
}
( run in 1.650 second using v1.01-cache-2.11-cpan-2398b32b56e )