/* -*- C++ -*- * This file is part of Zutty. * Copyright (C) 2020 Tom Szilagyi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * See the file LICENSE for the full license. */ #include "log.h" #include "pty.h" #include #include // for the debug/step facility: #include #include #include #include namespace zutty { #ifdef DEBUG inline void Vterm::debugKey () { switch (debugStep) { case 0: debugStep = 1; break; case 1: debugStep = 10; break; case 10: debugStep = 100; break; case 100: debugStep = 0; break; } debugCnt = debugStep; logT << "*** DEBUG step=" << debugStep << std::endl; } inline void Vterm::debugBreak () { if (!debugStep || --debugCnt > 0) return; debugCnt = debugStep; logT << "*** DEBUG STOP (step=" << debugStep << "), " << readPos + 1 - lastStopPos << " bytes since last:\n " << dumpBuffer (inputBuf + lastStopPos, inputBuf + readPos + 1); lastStopPos = readPos + 1; logT << "Issue 'kill -CONT " << getpid () << "' or 'fg' to continue." << std::endl; redraw (); using namespace std::chrono_literals; std::this_thread::sleep_for (100ms); kill (getpid (), SIGSTOP); } #define DEBUG_BREAK debugBreak() #define TRACE_FUN \ do { \ logT << __FUNCTION__ << " ["; \ for (size_t k = 0; k < nInputOps; ++k) { \ if (k) { \ vlog << ";"; \ } \ vlog << inputOps [k]; \ } \ vlog << "] \t" \ << "p(" << posY << "," << posX << ") " \ << "d(" << nRows << "," << nCols << ") " \ << "mgn[" << marginTop << "," << marginBottom << ") " \ << "hmgn:" << horizMarginMode \ << " [" << hMargin << "," << nColsEff << ")" \ << std::endl; \ } while (0) #else #define DEBUG_BREAK #define TRACE_FUN #endif // DEBUG inline void Vterm::unhandledInput (unsigned char ch) { logE << "Unhandled input char '" << ch << "' (" << (int)ch << ") in state " << strInputState (inputState) << ". Escape sequence so far: " << dumpBuffer (inputBuf + lastEscBegin, inputBuf + readPos + 1); setState (InputState::Normal); } inline void Vterm::traceNormalInput () { #ifdef DEBUG if (lastNormalBegin < readPos) { auto dumpbufs = dumpBuffer (inputBuf + lastNormalBegin, inputBuf + readPos); if (dumpbufs.length ()) { logT << "Inserted: " << dumpbufs; } } lastNormalBegin = readPos + 1; #endif // DEBUG } inline void Vterm::redraw () { onRefresh (* cf); cf->resetDamage (); } inline const MouseTrackingState& Vterm::getMouseTrackingState () const { return mouseTrk; } inline void Vterm::setHasFocus (bool hasFocus_) { hasFocus = hasFocus_; showCursor (); redraw (); } inline void Vterm::pageUp () { if (altScrollMode && altScreenBufferMode) { inputOps [0] = 1; nInputOps = 1; for (int k = 0; k < (marginBottom - marginTop) / 2; ++k) writePty (VtKey::Up); } else { cf->pageUp (nRows / 2); redraw (); } } inline void Vterm::pageDown () { if (altScrollMode && altScreenBufferMode) { inputOps [0] = 1; nInputOps = 1; for (int k = 0; k < (marginBottom - marginTop) / 2; ++k) writePty (VtKey::Down); } else { cf->pageDown (nRows / 2); redraw (); } } inline void Vterm::mouseWheelUp () { if (altScrollMode && altScreenBufferMode) { inputOps [0] = 1; nInputOps = 1; writePty (VtKey::Up); } else { cf->pageUp (5); redraw (); } } inline void Vterm::mouseWheelDown () { if (altScrollMode && altScreenBufferMode) { inputOps [0] = 1; nInputOps = 1; writePty (VtKey::Down); } else { cf->pageDown (5); redraw (); } } inline void Vterm::resetTerminal () { resetScreen (); resetAttrs (); switchColMode (ColMode::C80); cf->dropScrollbackHistory (); cf->resetMargins (marginTop, marginBottom); clearScreen (); switchScreenBufferMode (false); altScrollMode = opts.altScrollMode; altSendsEscape = opts.altSendsEscape; modifyOtherKeys = opts.modifyOtherKeys; horizMarginMode = false; hMargin = 0; nColsEff = nCols; setState (InputState::Normal); if (haveOscHandler) { // reset title argBuf.clear (); argBuf.push_back ('0'); argBuf.push_back (';'); for (const char* p = opts.title; *p != '\0'; ++p) argBuf.push_back (*p); handle_OSC (); } } inline void Vterm::resetScreen () { showCursorMode = true; cursorStyle = CharVdev::Cursor::Style::filled_block; autoWrapMode = true; autoNewlineMode = false; keyboardLocked = false; insertMode = false; bkspSendsDel = true; localEcho = false; bracketedPasteMode = false; compatLevel = CompatibilityLevel::VT400; cursorKeyMode = CursorKeyMode::ANSI; keypadMode = KeypadMode::Normal; originMode = OriginMode::Absolute; charsetState = CharsetState {}; savedCursor_SCO.isSet = false; savedCursor_DEC->isSet = false; mouseTrk = MouseTrackingState {}; tabStops.clear (); cf->getSelection ().clear (); } inline void Vterm::resetAttrs () { reverseVideo = false; fg = &attrs.fg; bg = &attrs.bg; inputOps [0] = 0; nInputOps = 1; csi_SGR (); } inline void Vterm::clearScreen () { posX = 0; posY = 0; lastCol = false; fillScreen (' '); } inline void Vterm::fillScreen (uint16_t ch) { cf->fillCells (ch, attrs); } inline void Vterm::switchColMode (ColMode colMode_) { if (colMode == colMode_) return; resetScreen (); clearScreen (); if (colMode_ == ColMode::C80) { logT << "DECCOLM: Selected 80 columns per line" << std::endl; } else { logT << "DECCOLM: Selected 132 columns per line" << std::endl; } colMode = colMode_; } inline void Vterm::switchScreenBufferMode (bool altScreenBufferMode_) { if (altScreenBufferMode == altScreenBufferMode_) return; if (altScreenBufferMode_) { frame_alt = Frame (winPx, winPy, nCols, nRows, marginTop, marginBottom); cf = &frame_alt; cf->expose (); savedCursor_DEC = &savedCursor_DEC_alt; altScreenBufferMode = true; } else { frame_pri.resize (winPx, winPy, nCols, nRows, marginTop, marginBottom); cf = &frame_pri; cf->expose (); frame_alt.freeCells (); savedCursor_DEC_alt.isSet = false; savedCursor_DEC = &savedCursor_DEC_pri; altScreenBufferMode = false; cf->getSelection ().clear (); } } inline void Vterm::setState (InputState newState) { if (newState == inputState) return; if (newState == InputState::Normal) { DEBUG_BREAK; nInputOps = 0; inputOps [0] = 0; lastNormalBegin = readPos + 1; } else if (inputState == InputState::Normal) { traceNormalInput (); } inputState = newState; } inline bool Vterm::readPty () { static bool first = true; ssize_t n = read (ptyFd, inputBuf, sizeof (inputBuf)); if (n < 0) return true; else if (n == 0) return !first; if (first) { // Mitigate the race condition between shell process startup // and first window size configuration happening in parallel: // the signal could get delivered before the shell is ready // for it, and thus get lost. pty_resize (ptyFd, nCols, nRows); first = false; } logT << "pty read: " << dumpBuffer (inputBuf, inputBuf + n); processInput (inputBuf, n); return false; } inline void Vterm::normalizeCursorPos () { if (nColsEff < posX + 1) posX = nColsEff - 1; if (nRows < posY + 1) posY = nRows - 1; lastCol = false; } inline bool Vterm::isCursorInsideMargins () { return posX >= hMargin && posX < nColsEff && posY >= marginTop && posY < marginBottom; } inline void Vterm::eraseRow (uint16_t pY) { cf->eraseInRow (pY, hMargin, nColsEff - hMargin, attrs); } inline void Vterm::eraseRows (uint16_t startY, uint16_t count) { for (uint16_t pY = startY; pY < startY + count; ++pY) eraseRow (pY); } inline void Vterm::copyRow (uint16_t dstY, uint16_t srcY) { cf->copyRow (dstY, srcY, hMargin, nColsEff - hMargin); } // insert blank rows at and below startY, within the scrolling area inline void Vterm::insertRows (uint16_t startY, uint16_t count) { for (uint16_t pY = marginBottom - count - 1; pY >= startY; --pY) { copyRow (pY + count, pY); if (!pY) break; } for (uint16_t pY = startY; pY < startY + count; ++pY) eraseRow (pY); } // delete rows at and below startY, within the scrolling area inline void Vterm::deleteRows (uint16_t startY, uint16_t count) { for (uint16_t pY = startY; pY < marginBottom - count; ++pY) copyRow (pY, pY + count); for (uint16_t pY = marginBottom - count; pY < marginBottom; ++pY) eraseRow (pY); } // insert blank cols at and to the right of startX, within the scrolling area inline void Vterm::insertCols (uint16_t startX, uint16_t count) { for (uint16_t r = marginTop; r < marginBottom; ++r) { cf->moveInRow (r, startX + count, startX, nColsEff - startX - count); cf->eraseInRow (r, startX, count, attrs); } } // delete cols at and to the right of startX, within the scrolling area inline void Vterm::deleteCols (uint16_t startX, uint16_t count) { for (uint16_t r = marginTop; r < marginBottom; ++r) { cf->moveInRow (r, startX, startX + count, nColsEff - startX - count); cf->eraseInRow (r, nColsEff - count, count, attrs); } } inline void Vterm::inputGraphicChar (unsigned char ch) { if ((ch & 0x80) == 0) { utf8dec.checkPrematureEOS (); // GL range Charset cs; if (charsetState.ss) { cs = charsetState.g [charsetState.ss]; charsetState.ss = 0; } else cs = charsetState.g [charsetState.gl]; if (cs == Charset::UTF8) utf8dec.onUnicode (ch < 127 ? ch : 0); else if (ch >= 32 && (cs == Charset::IsoLatin1 || ch < 127)) utf8dec.onUnicode (charCodes [(uint8_t)cs] [ch - 32]); } else { // GR range Charset cs = charsetState.g [charsetState.gr]; if (cs == Charset::UTF8) utf8dec.pushByte (ch); else if (ch >= 160 && (cs == Charset::IsoLatin1 || ch < 255)) utf8dec.onUnicode (charCodes [(uint8_t)cs] [ch - 160]); } } inline void Vterm::placeGraphicChar () { auto pt = utf8dec.getUnicode (); auto w = wcwidth (pt); if (!w) // zero-width code return; if (pt > 0xffff) { // Code outside the Unicode BMP -- not supported. // Would get truncated anyway; this is safer and more deterministic. pt = Missing_Glyph_Marker; } if (w < 0) { // Render codes with no visual representation as a Unicode RC w = 1; pt = Unicode_Replacement_Character; } if (autoWrapMode && lastCol) { cf->getCell (posY, posX).wrap = 1; inp_CR (); inp_LF (); } if (insertMode) { nInputOps = 1; inputOps [0] = 1; csi_ICH (); } auto& c = cf->getCell (posY, posX); c = attrs; c.uc_pt = pt; if (w == 2 && posX < nColsEff - 1) { c.dwidth = 1; cf->getCell (posY, ++posX).dwidth_cont = 1; } if (posX == nColsEff - 1) lastCol = true; else ++posX; } inline void Vterm::inp_LF () { TRACE_FUN; if (esc_IND ()) cf->eraseInRow (posY, posX, nColsEff - posX, attrs); } inline void Vterm::inp_CR () { TRACE_FUN; if (originMode == OriginMode::Absolute && posX < hMargin) posX = 0; else posX = hMargin; lastCol = false; } inline void Vterm::jumpToNextTabStop () { if (tabStops.empty ()) { // Hard default of 8 chars limited to right margin auto margin = isCursorInsideMargins () ? hMargin : 0; do { posX = ((posX / 8) + 1) * 8; } while (posX < margin); posX = std::min ((int)posX, nColsEff - 1); } else { // Next tabstop column set, or the right margin auto ts = std::lower_bound (tabStops.begin (), tabStops.end (), posX); posX = (ts == tabStops.end ()) ? nCols - 1 : * ts; } lastCol = false; } inline void Vterm::inp_HT () { TRACE_FUN; if (posX < nColsEff - 1) jumpToNextTabStop (); } inline void Vterm::showCursor () { TRACE_FUN; if (showCursorMode && inputState == InputState::Normal) { cf->setCursorPos (posY, posX); using CS = CharVdev::Cursor::Style; CS cs = cursorStyle; if (!hasFocus && cursorStyle == CS::filled_block) cs = CS::hollow_block; cf->setCursorStyle (cs); } } inline void Vterm::hideCursor () { TRACE_FUN; using CS = CharVdev::Cursor::Style; cf->setCursorStyle (CS::hidden); } inline void Vterm::esc_DCS (unsigned char fin) { TRACE_FUN; #ifdef DEBUG logT << "Designate Character Set: destination '" << scsDst <<"', charset '"; if (scsMod) { vlog << scsMod; } vlog << fin << "'" << std::endl; #endif uint8_t ix = 0; bool cs96 = false; switch (scsDst) { case '(': ix = 0; break; case ')': ix = 1; break; case '*': ix = 2; break; case '+': ix = 3; break; case '-': ix = 1; cs96 = true; break; case '.': ix = 2; cs96 = true; break; case '/': ix = 3; cs96 = true; break; } Charset cs = Charset::UTF8; switch (fin) { case 'A': cs = cs96 ? Charset::IsoLatin1 : Charset::IsoUK; break; case 'B': cs = Charset::UTF8; break; case '0': cs = Charset::DecSpec; break; case '5': if (scsMod == '%') cs = Charset::DecSuppl; break; case '<': cs = Charset::DecUserPref; break; case '>': cs = Charset::DecTechn; break; } charsetState.g [ix] = cs; setState (InputState::Normal); } inline bool Vterm::esc_IND () { TRACE_FUN; bool scrolled = false; if (posY == marginBottom - 1) { nInputOps = 1; inputOps [0] = 1; csi_SU (); scrolled = true; } else if (posY < nRows - 1) { ++posY; lastCol = false; } setState (InputState::Normal); return scrolled; } inline void Vterm::esc_RI () { TRACE_FUN; if (posY == marginTop) { nInputOps = 1; inputOps [0] = 1; csi_SD (); } else if (posY > 0) { --posY; lastCol = false; } setState (InputState::Normal); } inline void Vterm::csi_ecma48_SL () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1u; arg = std::min (arg, nColsEff - hMargin); deleteCols (hMargin, arg); setState (InputState::Normal); } inline void Vterm::csi_ecma48_SR () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1u; arg = std::min (arg, nColsEff - hMargin); insertCols (hMargin, arg); setState (InputState::Normal); } inline void Vterm::csi_DECSCUSR () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1u; using CS = CharVdev::Cursor::Style; switch (arg) { case 3: case 4: cursorStyle = CS::underline; break; case 5: case 6: cursorStyle = CS::bar; break; default: cursorStyle = CS::filled_block; break; } setState (InputState::Normal); } inline void Vterm::csi_DECIC () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1u; if (isCursorInsideMargins ()) { arg = std::min (arg, nColsEff - posX); insertCols (posX, arg); } setState (InputState::Normal); } inline void Vterm::csi_DECDC () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1u; if (isCursorInsideMargins ()) { arg = std::min (arg, nColsEff - posX); deleteCols (posX, arg); } setState (InputState::Normal); } inline void Vterm::esc_FI () { TRACE_FUN; nInputOps = 1; inputOps [0] = 1; if (posX < nColsEff - 1) csi_CUF (); else csi_ecma48_SL (); setState (InputState::Normal); } inline void Vterm::esc_BI () { TRACE_FUN; nInputOps = 1; inputOps [0] = 1; if (posX > hMargin) csi_CUB (); else csi_ecma48_SR (); setState (InputState::Normal); } inline void Vterm::esc_NEL () { TRACE_FUN; esc_IND (); inp_CR (); setState (InputState::Normal); } inline void Vterm::esc_HTS () { TRACE_FUN; tabStops.push_back (posX); std::sort (tabStops.begin (), tabStops.end ()); setState (InputState::Normal); } inline void Vterm::csi_SCOSC_SLRM () { if (horizMarginMode) csi_SLRM (); else csi_SCOSC (); } inline void Vterm::csi_SCOSC () { TRACE_FUN; savedCursor_SCO.posX = posX; savedCursor_SCO.posY = posY; savedCursor_SCO.isSet = true; setState (InputState::Normal); } inline void Vterm::csi_SCORC () { TRACE_FUN; if (!savedCursor_SCO.isSet) { logI << "Asked to restore cursor (SCORC) but it has not been saved." << std::endl; } else { posX = savedCursor_SCO.posX; posY = savedCursor_SCO.posY; normalizeCursorPos (); savedCursor_SCO.isSet = false; } setState (InputState::Normal); } inline void Vterm::esc_DECSC () { TRACE_FUN; savedCursor_DEC->posX = posX; savedCursor_DEC->posY = posY; savedCursor_DEC->lastCol = lastCol; savedCursor_DEC->attrs = attrs; savedCursor_DEC->originMode = originMode; savedCursor_DEC->charsetState = charsetState; savedCursor_DEC->isSet = true; setState (InputState::Normal); } inline void Vterm::esc_DECRC () { TRACE_FUN; if (!savedCursor_DEC->isSet) { logI << "Asked to restore cursor (DECRC) but it has not been saved." << std::endl; } else { posX = savedCursor_DEC->posX; posY = savedCursor_DEC->posY; normalizeCursorPos (); lastCol = savedCursor_DEC->lastCol; attrs = savedCursor_DEC->attrs; originMode = savedCursor_DEC->originMode; charsetState = savedCursor_DEC->charsetState; savedCursor_DEC->isSet = false; } setState (InputState::Normal); } inline void Vterm::csi_CUU () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; if (posY >= marginTop) arg = std::min ((int)arg, posY - marginTop); else arg = std::min (arg, posY); posY -= arg; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_CUD () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1; if (posY < marginBottom) arg = std::min (arg, marginBottom - posY - 1); else arg = std::min (arg, nRows - posY - 1); posY += arg; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_CUF () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1; arg = std::min (arg, nColsEff - posX - 1); posX += arg; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_CUB () { TRACE_FUN; int arg = inputOps [0] ? inputOps [0] : 1; if (posX >= hMargin) arg = std::min (arg, posX - hMargin); else arg = std::min (arg, (int)posX); if (posX == nColsEff) arg = std::min (arg + 1, (int)posX); posX -= arg; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_CNL () { TRACE_FUN; csi_CUD (); inp_CR (); setState (InputState::Normal); } inline void Vterm::csi_CPL () { TRACE_FUN; csi_CUU (); inp_CR (); setState (InputState::Normal); } inline void Vterm::csi_CHA () { TRACE_FUN; uint16_t col = inputOps [0] ? inputOps [0] : 1; col = std::max ((uint16_t)1, std::min (col, nCols)); posX = col - 1; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_HPA () { TRACE_FUN; csi_CHA (); setState (InputState::Normal); } inline void Vterm::csi_HPR () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; inputOps [0] = posX + arg + 1; csi_CHA (); setState (InputState::Normal); } inline void Vterm::csi_VPA () { TRACE_FUN; uint16_t row = inputOps [0] ? inputOps [0] : 1; row = std::max ((uint16_t)1, std::min (row, nRows)); posY = row - 1; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_VPR () { TRACE_FUN; uint16_t row = inputOps [0] ? inputOps [0] : 1; row += (posY + 1); row = std::max ((uint16_t)1, std::min (row, nRows)); posY = row - 1; lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_CUP () { TRACE_FUN; uint16_t row = inputOps [0] ? inputOps [0] : 1; uint16_t col = (nInputOps > 1 && inputOps [1]) ? inputOps [1] : 1; switch (originMode) { case OriginMode::Absolute: row = std::max ((uint16_t)1, std::min (row, nRows)) - 1; break; case OriginMode::ScrollingRegion: row = std::max ((uint16_t)1, std::min (row, marginBottom)) - 1; row += marginTop; break; } col = std::max ((uint16_t)1, std::min (col, nCols)) - 1; posX = col; posY = row; lastCol = false; setState (InputState::Normal); logT << "Cursor positioned to (" << posY << "," << posX << ")" << std::endl; } inline void Vterm::csi_SU () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; if (horizMarginMode) { arg = std::min ((int)arg, marginBottom - marginTop); deleteRows (marginTop, arg); } else { cf->scrollUp (arg); eraseRows (marginBottom - arg, arg); lastCol = false; } setState (InputState::Normal); } inline void Vterm::csi_SD () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; if (horizMarginMode) { arg = std::min ((int)arg, marginBottom - marginTop); insertRows (marginTop, arg); } else { cf->scrollDown (arg); eraseRows (marginTop, arg); lastCol = false; } setState (InputState::Normal); } inline void Vterm::csi_CHT () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; if (arg == 1) inp_HT (); else for (int k = 0; k < arg; ++k) jumpToNextTabStop (); setState (InputState::Normal); } inline void Vterm::csi_CBT () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; for (uint16_t k = 0; k < arg; ++k) { if (tabStops.empty ()) { if (posX > 0 && posX % 8 == 0) posX -= 8; else posX = (posX / 8) * 8; } else { // Set posX to previous tab stop auto ts = std::lower_bound (tabStops.begin (), tabStops.end (), posX); if (ts != tabStops.begin ()) posX = * (--ts); else posX = 0; } lastCol = false; } setState (InputState::Normal); } inline void Vterm::csi_REP () { TRACE_FUN; uint16_t arg = inputOps [0] ? inputOps [0] : 1; for (int k = 0; k < arg; ++k) placeGraphicChar (); utf8dec.setUnicode (' '); setState (InputState::Normal); } inline void Vterm::csi_ED () { TRACE_FUN; normalizeCursorPos (); switch (inputOps [0]) { case 0: // clear from cursor to end of screen cf->eraseInRow (posY, posX, nCols - posX, attrs); for (uint16_t pY = posY + 1; pY < nRows; ++pY) eraseRow (pY); break; case 1: // clear from beginning of screen to cursor for (uint16_t pY = 0; pY < posY; ++pY) eraseRow (pY); cf->eraseInRow (posY, 0, posX + 1, attrs); break; case 3: // clear entire screen including scrollback buffer (xterm) cf->dropScrollbackHistory (); // fallthrough case 2: // clear entire screen for (uint16_t pY = 0; pY < nRows; ++pY) eraseRow (pY); break; default: logI << "Erase in Display with illegal param: " << inputOps [0] << std::endl; break; } setState (InputState::Normal); } inline void Vterm::csi_EL () { TRACE_FUN; normalizeCursorPos (); switch (inputOps [0]) { case 0: // clear from cursor to end of line cf->eraseInRow (posY, posX, nCols - posX, attrs); break; case 1: // clear from cursor to beginning of line cf->eraseInRow (posY, 0, posX + 1, attrs); break; case 2: // clear entire line cf->eraseInRow (posY, 0, nCols, attrs); break; default: logI << "Erase in Line with illegal param: " << inputOps [0] << std::endl; break; } setState (InputState::Normal); } inline void Vterm::csi_IL () { TRACE_FUN; if (isCursorInsideMargins ()) { int arg = inputOps [0] ? inputOps [0] : 1; arg = std::min (arg, marginBottom - posY); insertRows (posY, arg); inp_CR (); } setState (InputState::Normal); } inline void Vterm::csi_DL () { TRACE_FUN; if (isCursorInsideMargins ()) { int arg = inputOps [0] ? inputOps [0] : 1; arg = std::min (arg, marginBottom - posY); deleteRows (posY, arg); inp_CR (); } setState (InputState::Normal); } inline void Vterm::csi_ICH () { TRACE_FUN; if (isCursorInsideMargins ()) { uint32_t arg = inputOps [0] ? inputOps [0] : 1; uint32_t len = nColsEff - posX; arg = std::min (arg, len); len -= arg; if (len > 0 && cf->getCell (posY, posX + arg + len - 1).wrap) { // maintain wrap bit invariance at EOL cf->getCell (posY, posX + arg + len - 1).wrap = 0; cf->getCell (posY, posX + len - 1).wrap = 1; } cf->moveInRow (posY, posX + arg, posX, len); cf->eraseInRow (posY, posX, arg, attrs); } lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_DCH () { TRACE_FUN; if (isCursorInsideMargins ()) { uint32_t arg = inputOps [0] ? inputOps [0] : 1; uint32_t len = nColsEff - posX; arg = std::min (arg, len); len -= arg; cf->moveInRow (posY, posX, posX + arg, len); cf->eraseInRow (posY, posX + len, arg, attrs); } lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_ECH () { TRACE_FUN; uint32_t arg = inputOps [0] ? inputOps [0] : 1; uint32_t len = nColsEff - posX; arg = std::min (arg, len); cf->eraseInRow (posY, posX, arg, attrs); lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_STBM () { TRACE_FUN; if (nInputOps == 1 && inputOps [0] == 0) { if (marginTop != 0 || marginBottom != nRows) { cf->resetMargins (marginTop, marginBottom); } } else if (nInputOps == 2) { uint16_t newMarginTop = inputOps [0] > 0 ? inputOps [0] - 1 : 0; uint16_t newMarginBottom = inputOps [1]; if (newMarginBottom < newMarginTop + 2 || nRows < newMarginBottom) { logI << "Illegal arguments to SetTopBottomMargins: top=" << inputOps [0] << ", bottom=" << inputOps [1] << std::endl; } else if (newMarginTop != marginTop || newMarginBottom != marginBottom) { marginTop = newMarginTop; marginBottom = newMarginBottom; if (marginTop == 0 && marginBottom == nRows) cf->resetMargins (marginTop, marginBottom); else cf->setMargins (marginTop, marginBottom); } } if (originMode == OriginMode::Absolute) { posX = 0; posY = 0; } else { posX = hMargin; posY = marginTop; } lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_SLRM () { TRACE_FUN; if (nInputOps == 1 && inputOps [0] == 0) { hMargin = 0; nColsEff = nCols; } else if (nInputOps == 2) { uint16_t newMarginLeft = inputOps [0] > 0 ? inputOps [0] - 1 : 0; uint16_t newMarginRight = inputOps [1]; if (newMarginRight < newMarginLeft + 2 || nCols < newMarginRight) { logI << "Illegal arguments to SetLeftRightMargins: left=" << inputOps [0] << ", right=" << inputOps [1] << std::endl; } else if (newMarginLeft != hMargin || newMarginRight != nColsEff) { hMargin = newMarginLeft; nColsEff = newMarginRight; } } if (originMode == OriginMode::Absolute) { posX = 0; posY = 0; } else { posX = hMargin; posY = marginTop; } lastCol = false; setState (InputState::Normal); } inline void Vterm::csi_TBC () { TRACE_FUN; switch (inputOps [0]) { case 0: { auto it = std::find (tabStops.begin (), tabStops.end (), posX); if (it != tabStops.end ()) tabStops.erase (it); } break; case 3: tabStops.clear (); break; default: break; } setState (InputState::Normal); } inline void Vterm::csi_SM () { TRACE_FUN; for (size_t k = 0; k < nInputOps; ++k) { const auto& arg = inputOps [k]; switch (arg) { case 2: keyboardLocked = true; break; case 4: insertMode = true; break; case 12: localEcho = false; break; case 20: autoNewlineMode = true; break; default: logW << "Ignored bogus set mode " << arg << std::endl; break; } } setState (InputState::Normal); } inline void Vterm::csi_RM () { TRACE_FUN; for (size_t k = 0; k < nInputOps; ++k) { const auto& arg = inputOps [k]; switch (arg) { case 2: keyboardLocked = false; break; case 4: insertMode = false; break; case 12: localEcho = true; break; case 20: autoNewlineMode = false; break; default: logW << "Ignored bogus reset mode " << arg << std::endl; break; } } setState (InputState::Normal); } inline void Vterm::csi_privSM () { TRACE_FUN; for (size_t k = 0; k < nInputOps; ++k) { const auto& arg = inputOps [k]; switch (arg) { case 1: cursorKeyMode = CursorKeyMode::Application; break; case 2: charsetState = CharsetState {}; compatLevel = CompatibilityLevel::VT400; break; case 3: switchColMode (ColMode::C132); break; case 4: logT << "DECSCLM: Set smooth scroll" << std::endl; break; case 5: logU << "DECSCNM: Set inverse video mode" << std::endl; break; case 6: originMode = OriginMode::ScrollingRegion; break; case 7: autoWrapMode = true; break; case 8: logU << "DECARM: Set auto-repeat mode" << std::endl; break; case 9: mouseTrk.mode = MouseTrackingMode::X10_Compat; break; case 12: logU << "Start blinking cursor" << std::endl; break; case 25: showCursorMode = true; break; case 47: switchScreenBufferMode (true); break; case 67: bkspSendsDel = false; break; case 69: horizMarginMode = true; hMargin = 0; nColsEff = nCols; break; case 1000: mouseTrk.mode = MouseTrackingMode::VT200; break; case 1001: logU << "Set VT200 Highlight Mouse mode" << std::endl; break; case 1002: mouseTrk.mode = MouseTrackingMode::VT200_ButtonEvent; break; case 1003: mouseTrk.mode = MouseTrackingMode::VT200_AnyEvent; break; case 1004: mouseTrk.focusEventMode = true; break; case 1005: mouseTrk.enc = MouseTrackingEnc::UTF8; break; case 1006: mouseTrk.enc = MouseTrackingEnc::SGR; break; case 1007: altScrollMode = true; break; case 1015: mouseTrk.enc = MouseTrackingEnc::URXVT; break; case 1036: case 1039: altSendsEscape = true; break; case 1047: switchScreenBufferMode (true); break; case 1048: esc_DECSC (); break; case 1049: esc_DECSC (); switchScreenBufferMode (true); break; case 2004: bracketedPasteMode = true; break; default: logU << "set priv mode " << arg << std::endl; break; } } setState (InputState::Normal); } inline void Vterm::csi_privRM () { TRACE_FUN; for (size_t k = 0; k < nInputOps; ++k) { const auto& arg = inputOps [k]; switch (arg) { case 1: cursorKeyMode = CursorKeyMode::ANSI; break; case 2: charsetState = CharsetState {}; compatLevel = CompatibilityLevel::VT52; break; case 3: switchColMode (ColMode::C80); break; case 4: logT << "DECSCLM: Set jump scroll" << std::endl; break; case 5: logU << "DECSCNM: Set normal video mode" << std::endl; break; case 6: originMode = OriginMode::Absolute; break; case 7: autoWrapMode = false; break; case 8: logU << "DECARM: Reset auto-repeat mode" << std::endl; break; case 9: case 1000: case 1002: case 1003: mouseTrk.mode = MouseTrackingMode::Disabled; break; case 12: logU << "Stop blinking cursor" << std::endl; break; case 25: showCursorMode = false; break; case 47: switchScreenBufferMode (false); break; case 67: bkspSendsDel = true; break; case 69: horizMarginMode = false; hMargin = 0; nColsEff = nCols; break; case 1001: logU << "Reset VT200 Highlight Mouse mode" << std::endl; break; case 1004: mouseTrk.focusEventMode = false; break; case 1005: case 1006: case 1015: mouseTrk.enc = MouseTrackingEnc::Default; break; case 1007: altScrollMode = false; break; case 1036: case 1039: altSendsEscape = false; break; case 1047: switchScreenBufferMode (false); break; case 1048: esc_DECRC (); break; case 1049: switchScreenBufferMode (false); esc_DECRC (); break; case 2004: bracketedPasteMode = false; break; default: logU << "reset priv mode " << arg << std::endl; break; } } setState (InputState::Normal); } inline void Vterm::setFgFromPalIx () { if (fgPalIx < 0) *fg = rgb_fg; else if (opts.boldColors && attrs.bold && fgPalIx >= 0 && fgPalIx <= 7) *fg = palette256 [fgPalIx + 8]; else *fg = palette256 [fgPalIx]; } inline void Vterm::setBgFromPalIx () { if (bgPalIx < 0) *bg = rgb_bg; else *bg = palette256 [bgPalIx]; } inline void Vterm::csi_SGR () { TRACE_FUN; for (size_t k = 0; k < nInputOps; ++k) { const auto& attr = inputOps [k]; switch (attr) { case 0: attrs.uc_pt = ' '; attrs.bold = 0; attrs.italic = 0; attrs.underline = 0; attrs.inverse = 0; reverseVideo = false; fg = &attrs.fg; bg = &attrs.bg; fgPalIx = defaultFgPalIx; setFgFromPalIx (); bgPalIx = defaultBgPalIx; setBgFromPalIx (); break; case 1: attrs.bold = 1; setFgFromPalIx (); break; case 2: attrs.bold = 0; setFgFromPalIx (); break; case 3: attrs.italic = 1; break; case 4: attrs.underline = 1; break; case 5: /* blink on */ break; case 7: if (!reverseVideo) { fg = &attrs.bg; bg = &attrs.fg; reverseVideo = true; setFgFromPalIx (); setBgFromPalIx (); } break; case 8: logU << "attr.: concealed" << std::endl; break; case 10: attrs.bold = 0; attrs.italic = 0; setFgFromPalIx (); break; case 11: attrs.bold = 1; attrs.italic = 0; setFgFromPalIx (); break; case 12: attrs.bold = 0; attrs.italic = 1; setFgFromPalIx (); break; case 13: attrs.bold = 1; attrs.italic = 1; setFgFromPalIx (); break; case 14: case 15: case 16: case 17: case 18: case 19: attrs.bold = 0; attrs.italic = 0; setFgFromPalIx (); break; case 22: attrs.bold = 0; setFgFromPalIx (); break; case 23: attrs.italic = 0; break; case 25: /* blink off */ break; case 24: attrs.underline = 0; break; case 27: if (reverseVideo) { fg = &attrs.fg; bg = &attrs.bg; reverseVideo = false; setFgFromPalIx (); setBgFromPalIx (); } break; case 28: logU << "attr.: revealed (conceal off)" << std::endl; break; // standard foregrounds (bright if bold) case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: fgPalIx = attr - 30; setFgFromPalIx (); break; case 38: if (k >= nInputOps - 1) break; switch (inputOps [++k]) { case 5: if (k >= nInputOps - 1) break; fgPalIx = inputOps [++k]; setFgFromPalIx (); break; case 2: if (k >= nInputOps - 3) break; rgb_fg.red = inputOps [++k]; rgb_fg.green = inputOps [++k]; rgb_fg.blue = inputOps [++k]; fgPalIx = -1; setFgFromPalIx (); break; default: break; } break; case 39: fgPalIx = defaultFgPalIx; setFgFromPalIx (); break; // standard backgrounds case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: bgPalIx = attr - 40; setBgFromPalIx (); break; case 48: if (k >= nInputOps - 1) break; switch (inputOps [++k]) { case 5: if (k >= nInputOps - 1) break; bgPalIx = inputOps [++k]; setBgFromPalIx (); break; case 2: if (k >= nInputOps - 3) break; rgb_bg.red = inputOps [++k]; rgb_bg.green = inputOps [++k]; rgb_bg.blue = inputOps [++k]; bgPalIx = -1; setBgFromPalIx (); break; default: break; } break; case 49: bgPalIx = defaultBgPalIx; setBgFromPalIx (); break; // bright colored foregrounds case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97: fgPalIx = attr - 82; setFgFromPalIx (); break; // bright colored backgrounds case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107: bgPalIx = attr - 92; setBgFromPalIx (); break; default: logU << "attribute: " << attr << std::endl; break; } } rgb_fg = opts.fg; rgb_bg = opts.bg; setState (InputState::Normal); } /* 64 - VT420 family * 1 - 132 columns * 9 - National Replacement Character-sets * 15 - DEC technical set * 21 - horizontal scrolling * 22 - color */ #define DEVICE_ID "64;1;9;15;21;22c" inline void Vterm::csi_priDA () { TRACE_FUN; writePty ("\e[?" DEVICE_ID); setState (InputState::Normal); } inline void Vterm::csi_secDA () { TRACE_FUN; writePty ("\e[>64;0;0c"); // VT520 setState (InputState::Normal); } inline void Vterm::csi_DSR () { TRACE_FUN; switch (inputOps [0]) { case 5: writePty ("\e[0n"); // device OK break; case 6: { std::ostringstream oss; if (originMode == OriginMode::Absolute) oss << "\e[" << (posY + 1) << ";" << (posX + 1) << "R"; else oss << "\e[" << (posY - marginTop + 1) << ";" << (posX + 1) << "R"; writePty (oss.str ().c_str ()); } break; default: break; } setState (InputState::Normal); } inline void Vterm::esch_DECALN () { TRACE_FUN; // Save current attrs CharVdev::Cell origAttrs = attrs; Color* origFg = &attrs.fg; Color* origBg = &attrs.bg; resetAttrs (); fillScreen ('E'); // Restore attrs fg = origFg; bg = origBg; attrs = origAttrs; setState (InputState::Normal); } inline void Vterm::esc_RIS () { TRACE_FUN; resetTerminal (); setState (InputState::Normal); } inline void Vterm::csi_DECSTR () { TRACE_FUN; resetScreen (); resetAttrs (); setState (InputState::Normal); } inline void Vterm::handle_DCS () { TRACE_FUN; auto arg = std::string ((char*)argBuf.data (), argBuf.size ()); if (arg.substr (0, 2) == "$q") { dcs_DECRQSS (arg); } else { logU << "DCS: '" << arg << "'" << std::endl; } setState (InputState::Normal); } inline void Vterm::dcs_DECRQSS (const std::string& arg) { TRACE_FUN; if (arg == "$q\"p") { // DECSCL if (compatLevel == CompatibilityLevel::VT400) writePty ("\eP1$r" DEVICE_ID "\e\\"); } else { writePty ("\eP0$r\e\\"); } } inline void Vterm::handle_OSC () { TRACE_FUN; auto osc = std::string ((char*)argBuf.data (), argBuf.size ()); std::size_t p = osc.find_first_of(";"); std::string arg; if (p != std::string::npos) arg = osc.substr (p + 1); std::stringstream iss (osc); int cmd; iss >> cmd; if (iss.fail () || cmd < 0 || cmd > 120) { logT << "OSC: malformed command string '" << osc << "'" << std::endl; } else { switch (cmd) { case 4: osc_PaletteQuery (cmd, arg); break; case 10: case 11: case 12: case 17: case 19: osc_DynamicColorQuery (cmd, arg); break; // Other cases handed over to external OSC handler: default: onOsc (cmd, arg); break; } } setState (InputState::Normal); } inline void Vterm::osc_PaletteQuery (int cmd, const std::string& arg) { std::size_t p = arg.find_first_of(";"); if (p == std::string::npos) return; // malformed argument, missing ';' std::string pc = arg.substr (0, p); std::string ps = arg.substr (p + 1); if (ps == "?") { std::stringstream iss (pc); int paletteIdx; iss >> paletteIdx; if (iss.fail () || paletteIdx < 0 || paletteIdx > 255) return; Color c = palette256 [paletteIdx]; std::ostringstream oss; oss << "\e]" << cmd << ";" << paletteIdx << ";" << c << "\e\\"; writePty (oss.str ().data (), oss.str ().size ()); } } inline void Vterm::osc_DynamicColorQuery (int cmd, const std::string& arg) { if (arg == "?") { Color c; switch (cmd) { case 10: case 17: c = *fg; break; case 11: case 19: c = *bg; break; case 12: c = cf->getCursor ().color; break; } std::ostringstream oss; oss << "\e]" << cmd << ";" << c << "\e\\"; writePty (oss.str ().data (), oss.str ().size ()); } } inline void Vterm::csiq_DECSCL () { TRACE_FUN; if (nInputOps > 0) { switch (inputOps [0]) { case 61: compatLevel = CompatibilityLevel::VT100; break; case 62: compatLevel = CompatibilityLevel::VT400; break; case 63: compatLevel = CompatibilityLevel::VT400; break; case 64: compatLevel = CompatibilityLevel::VT400; break; case 65: compatLevel = CompatibilityLevel::VT400; break; default: logU << "DECSCL: compatibility mode " << inputOps [0] << std::endl; break; } } if (nInputOps > 1) { switch (inputOps [1]) { case 0: logT << "DECSCL: 8-bit controls" << std::endl; break; case 1: logT << "DECSCL: 7-bit controls" << std::endl; break; case 2: logT << "DECSCL: 8-bit controls" << std::endl; break; default: logU << "DECSCL: C1 control transmission mode: " << inputOps [1] << std::endl; break; } } setState (InputState::Normal); } inline void Vterm::csi_XTWINOPS () { TRACE_FUN; // ignore setState (InputState::Normal); } inline void Vterm::csi_XTMODKEYS () { TRACE_FUN; switch (nInputOps) { case 0: // Reset all options to initial values modifyOtherKeys = opts.modifyOtherKeys; break; case 1: inputOps [1] = 0; // fall through case 2: switch (inputOps [0]) { case 0: if (inputOps [1] != 0) { logU << "XTMODKEYS: modifyKeyboard = " << inputOps [1] << std::endl; } break; case 1: if (inputOps [1] != 2) { logU << "XTMODKEYS: modifyCursorKeys = " << inputOps [1] << std::endl; } break; case 2: if (inputOps [1] != 2) { logU << "XTMODKEYS: modifyFunctionKeys = " << inputOps [1] << std::endl; } break; case 4: if (inputOps [1] <= 2) { modifyOtherKeys = inputOps [1]; logT << "XTMODKEYS: modifyOtherKeys set to " << (int)modifyOtherKeys << std::endl; } else { logI << "XTMODKEYS: illegal argument for modifyOtherKeys: " << inputOps [1] << std::endl; } break; } } setState (InputState::Normal); } inline void Vterm::csi_XTQMODKEYS () { TRACE_FUN; std::ostringstream oss; switch (nInputOps) { case 0: inputOps [0] = 1; nInputOps = 1; // fall through case 1: switch (inputOps [0]) { case 0: case 1: case 2: oss << "\e[>" << inputOps [0] << "m"; writePty (oss.str ().data (), oss.str ().size ()); break; case 4: oss << "\e[>4;" << (int)modifyOtherKeys << "m"; writePty (oss.str ().data (), oss.str ().size ()); break; default: logI << "XTQMODKEYS: illegal argument " << inputOps [0] << std::endl; break; } break; } setState (InputState::Normal); } } // namespace zutty