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 )