File indexing completed on 2024-05-19 05:28:15

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0004     SPDX-FileCopyrightText: 1996 Matthias Ettrich <ettrich@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 
0008     This program is distributed in the hope that it will be useful,
0009     but WITHOUT ANY WARRANTY; without even the implied warranty of
0010     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0011     GNU General Public License for more details.
0012 
0013     You should have received a copy of the GNU General Public License
0014     along with this program; if not, write to the Free Software
0015     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0016     02110-1301  USA.
0017 */
0018 
0019 // Own
0020 #include "Emulation.h"
0021 
0022 // System
0023 #include <cstdio>
0024 #include <cstdlib>
0025 #include <string>
0026 #include <unistd.h>
0027 
0028 // Qt
0029 #include <QApplication>
0030 #include <QClipboard>
0031 #include <QHash>
0032 #include <QKeyEvent>
0033 #include <QRegExp>
0034 #include <QTextStream>
0035 #include <QThread>
0036 
0037 #include <QTime>
0038 
0039 // KDE
0040 // #include <kdebug.h>
0041 
0042 // Konsole
0043 #include "KeyboardTranslator.h"
0044 #include "Screen.h"
0045 #include "ScreenWindow.h"
0046 #include "TerminalCharacterDecoder.h"
0047 
0048 using namespace Konsole;
0049 
0050 Emulation::Emulation()
0051     : _currentScreen(nullptr)
0052     , _codec(nullptr)
0053     , _decoder(nullptr)
0054     , _keyTranslator(nullptr)
0055     , _usesMouse(false)
0056     , _bracketedPasteMode(false)
0057 {
0058     // create screens with a default size
0059     _primaryScreen = std::make_unique<Screen>(40, 80);
0060     _alternateScreen = std::make_unique<Screen>(40, 80);
0061     _currentScreen = _primaryScreen.get();
0062 
0063     QObject::connect(&_bulkTimer1, &QTimer::timeout, this, &Emulation::showBulk);
0064     QObject::connect(&_bulkTimer2, &QTimer::timeout, this, &Emulation::showBulk);
0065 
0066     // listen for mouse status changes
0067     connect(this, &Emulation::programUsesMouseChanged, &Emulation::usesMouseChanged);
0068     connect(this, &Emulation::programBracketedPasteModeChanged, &Emulation::bracketedPasteModeChanged);
0069 
0070     connect(this, &Emulation::cursorChanged, [this](KeyboardCursorShape cursorShape, bool blinkingCursorEnabled) {
0071         Q_EMIT titleChanged(50,
0072                             QString(QLatin1String("CursorShape=%1;BlinkingCursorEnabled=%2")).arg(static_cast<int>(cursorShape)).arg(blinkingCursorEnabled));
0073     });
0074 }
0075 
0076 Emulation::~Emulation() = default;
0077 
0078 bool Emulation::programUsesMouse() const
0079 {
0080     return _usesMouse;
0081 }
0082 
0083 void Emulation::usesMouseChanged(bool usesMouse)
0084 {
0085     _usesMouse = usesMouse;
0086 }
0087 
0088 bool Emulation::programBracketedPasteMode() const
0089 {
0090     return _bracketedPasteMode;
0091 }
0092 
0093 void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode)
0094 {
0095     _bracketedPasteMode = bracketedPasteMode;
0096 }
0097 
0098 ScreenWindow *Emulation::createWindow()
0099 {
0100     auto window = std::make_unique<ScreenWindow>();
0101     window->setScreen(_currentScreen);
0102 
0103     connect(window.get(), &ScreenWindow::selectionChanged, this, &Emulation::bufferedUpdate);
0104 
0105     connect(this, &Emulation::outputChanged, window.get(), &ScreenWindow::notifyOutputChanged);
0106 
0107     connect(this, &Emulation::handleCommandFromKeyboard, window.get(), &ScreenWindow::handleCommandFromKeyboard);
0108     connect(this, &Emulation::outputFromKeypressEvent, window.get(), &ScreenWindow::scrollToEnd);
0109 
0110     _windows.push_back(std::move(window));
0111 
0112     return _windows.back().get();
0113 }
0114 
0115 void Emulation::setScreen(int n)
0116 {
0117     Screen *old = _currentScreen;
0118     Screen *screens[2] = {_primaryScreen.get(), _alternateScreen.get()};
0119     _currentScreen = screens[n & 1];
0120     if (_currentScreen != old) {
0121         // tell all windows onto this emulation to switch to the newly active screen
0122         for (const auto &window : std::as_const(_windows))
0123             window->setScreen(_currentScreen);
0124     }
0125 }
0126 
0127 void Emulation::clearHistory()
0128 {
0129     _primaryScreen->setScroll(_primaryScreen->getScroll(), false);
0130 }
0131 void Emulation::setHistory(const HistoryType &t)
0132 {
0133     _primaryScreen->setScroll(t);
0134 
0135     showBulk();
0136 }
0137 
0138 const HistoryType &Emulation::history() const
0139 {
0140     return _primaryScreen->getScroll();
0141 }
0142 
0143 void Emulation::setCodec(const QTextCodec *qtc)
0144 {
0145     if (qtc)
0146         _codec = qtc;
0147     else
0148         setCodec(LocaleCodec);
0149 
0150     _decoder.reset();
0151     _decoder = std::unique_ptr<QTextDecoder>(_codec->makeDecoder());
0152 
0153     Q_EMIT useUtf8Request(utf8());
0154 }
0155 
0156 void Emulation::setCodec(EmulationCodec codec)
0157 {
0158     if (codec == Utf8Codec)
0159         setCodec(QTextCodec::codecForName("utf8"));
0160     else if (codec == LocaleCodec)
0161         setCodec(QTextCodec::codecForLocale());
0162 }
0163 
0164 void Emulation::setKeyBindings(const QString &name)
0165 {
0166     _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
0167     if (!_keyTranslator) {
0168         _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
0169     }
0170 }
0171 
0172 QString Emulation::keyBindings() const
0173 {
0174     return _keyTranslator->name();
0175 }
0176 
0177 void Emulation::receiveChar(QChar c)
0178 // process application unicode input to terminal
0179 // this is a trivial scanner
0180 {
0181     c.unicode() &= 0xff;
0182     switch (c.unicode()) {
0183     case '\b':
0184         _currentScreen->backspace();
0185         break;
0186     case '\t':
0187         _currentScreen->tab();
0188         break;
0189     case '\n':
0190         _currentScreen->newLine();
0191         break;
0192     case '\r':
0193         _currentScreen->toStartOfLine();
0194         break;
0195     case 0x07:
0196         Q_EMIT stateSet(NOTIFYBELL);
0197         break;
0198     default:
0199         _currentScreen->displayCharacter(c);
0200         break;
0201     };
0202 }
0203 
0204 void Emulation::sendKeyEvent(QKeyEvent *ev, bool)
0205 {
0206     Q_EMIT stateSet(NOTIFYNORMAL);
0207 
0208     if (!ev->text().isEmpty()) { // A block of text
0209         // Note that the text is proper unicode.
0210         // We should do a conversion here
0211         Q_EMIT sendData(ev->text().toUtf8().constData(), ev->text().length());
0212     }
0213 }
0214 
0215 void Emulation::sendString(const char *, int)
0216 {
0217     // default implementation does nothing
0218 }
0219 
0220 void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/)
0221 {
0222     // default implementation does nothing
0223 }
0224 
0225 /*
0226    We are doing code conversion from locale to unicode first.
0227 TODO: Character composition from the old code.  See #96536
0228 */
0229 
0230 void Emulation::receiveData(const char *text, int length)
0231 {
0232     Q_EMIT stateSet(NOTIFYACTIVITY);
0233 
0234     bufferedUpdate();
0235 
0236     /* XXX: the following code involves encoding & decoding of "UTF-16
0237      * surrogate pairs", which does not work with characters higher than
0238      * U+10FFFF
0239      * https://unicodebook.readthedocs.io/unicode_encodings.html#surrogates
0240      */
0241     QString utf16Text = _decoder->toUnicode(text, length);
0242 
0243     // send characters to terminal emulator
0244     for (auto c : utf16Text) {
0245         receiveChar(c);
0246     }
0247 
0248     // look for z-modem indicator
0249     //-- someone who understands more about z-modems that I do may be able to move
0250     // this check into the above for loop?
0251     for (int i = 0; i < length; i++) {
0252         if (text[i] == '\030') {
0253             if ((length - i - 1 > 3) && (strncmp(text + i + 1, "B00", 3) == 0))
0254                 Q_EMIT zmodemDetected();
0255         }
0256     }
0257 }
0258 
0259 // OLDER VERSION
0260 // This version of onRcvBlock was commented out because
0261 //     a)  It decoded incoming characters one-by-one, which is slow in the current version of Qt (4.2 tech preview)
0262 //     b)  It messed up decoding of non-ASCII characters, with the result that (for example) chinese characters
0263 //         were not printed properly.
0264 //
0265 // There is something about stopping the _decoder if "we get a control code halfway a multi-byte sequence" (see below)
0266 // which hasn't been ported into the newer function (above).  Hopefully someone who understands this better
0267 // can find an alternative way of handling the check.
0268 
0269 /*void Emulation::onRcvBlock(const char *s, int len)
0270 {
0271   Q_EMIT notifySessionState(NOTIFYACTIVITY);
0272 
0273   bufferedUpdate();
0274   for (int i = 0; i < len; i++)
0275   {
0276 
0277     QString result = _decoder->toUnicode(&s[i],1);
0278     int reslen = result.length();
0279 
0280     // If we get a control code halfway a multi-byte sequence
0281     // we flush the _decoder and continue with the control code.
0282     if ((s[i] < 32) && (s[i] > 0))
0283     {
0284        // Flush _decoder
0285        while(!result.length())
0286           result = _decoder->toUnicode(&s[i],1);
0287        reslen = 1;
0288        result.resize(reslen);
0289        result[0] = QChar(s[i]);
0290     }
0291 
0292     for (int j = 0; j < reslen; j++)
0293     {
0294       if (result[j].characterategory() == QChar::Mark_NonSpacing)
0295          _currentScreen->compose(result.mid(j,1));
0296       else
0297          onRcvChar(result[j].unicode());
0298     }
0299     if (s[i] == '\030')
0300     {
0301       if ((len-i-1 > 3) && (strncmp(s+i+1, "B00", 3) == 0))
0302           Q_EMIT zmodemDetected();
0303     }
0304   }
0305 }*/
0306 
0307 void Emulation::writeToStream(TerminalCharacterDecoder *_decoder, int startLine, int endLine)
0308 {
0309     _currentScreen->writeLinesToStream(_decoder, startLine, endLine);
0310 }
0311 
0312 void Emulation::writeToStream(TerminalCharacterDecoder *_decoder)
0313 {
0314     _currentScreen->writeLinesToStream(_decoder, 0, _currentScreen->getHistLines());
0315 }
0316 
0317 int Emulation::lineCount() const
0318 {
0319     // sum number of lines currently on _screen plus number of lines in history
0320     return _currentScreen->getLines() + _currentScreen->getHistLines();
0321 }
0322 
0323 #define BULK_TIMEOUT1 10
0324 #define BULK_TIMEOUT2 40
0325 
0326 void Emulation::showBulk()
0327 {
0328     _bulkTimer1.stop();
0329     _bulkTimer2.stop();
0330 
0331     Q_EMIT outputChanged();
0332 
0333     _currentScreen->resetScrolledLines();
0334     _currentScreen->resetDroppedLines();
0335 }
0336 
0337 void Emulation::bufferedUpdate()
0338 {
0339     _bulkTimer1.setSingleShot(true);
0340     _bulkTimer1.start(BULK_TIMEOUT1);
0341     if (!_bulkTimer2.isActive()) {
0342         _bulkTimer2.setSingleShot(true);
0343         _bulkTimer2.start(BULK_TIMEOUT2);
0344     }
0345 }
0346 
0347 char Emulation::eraseChar() const
0348 {
0349     return '\b';
0350 }
0351 
0352 void Emulation::setImageSize(int lines, int columns)
0353 {
0354     if ((lines < 1) || (columns < 1))
0355         return;
0356 
0357     QSize screenSize[2] = {QSize(_primaryScreen->getColumns(), _primaryScreen->getLines()),
0358                            QSize(_alternateScreen->getColumns(), _alternateScreen->getLines())};
0359     QSize newSize(columns, lines);
0360 
0361     if (newSize == screenSize[0] && newSize == screenSize[1])
0362         return;
0363 
0364     _primaryScreen->resizeImage(lines, columns);
0365     _alternateScreen->resizeImage(lines, columns);
0366 
0367     Q_EMIT imageSizeChanged(lines, columns);
0368 
0369     bufferedUpdate();
0370 }
0371 
0372 QSize Emulation::imageSize() const
0373 {
0374     return {_currentScreen->getColumns(), _currentScreen->getLines()};
0375 }
0376 
0377 ushort ExtendedCharTable::extendedCharHash(ushort *unicodePoints, ushort length) const
0378 {
0379     ushort hash = 0;
0380     for (ushort i = 0; i < length; i++) {
0381         hash = 31 * hash + unicodePoints[i];
0382     }
0383     return hash;
0384 }
0385 bool ExtendedCharTable::extendedCharMatch(ushort hash, ushort *unicodePoints, ushort length) const
0386 {
0387     std::span entry = extendedCharTable.at(hash);
0388 
0389     // compare given length with stored sequence length ( given as the first ushort in the
0390     // stored buffer )
0391     if (entry.empty() || entry[0] != length)
0392         return false;
0393     // if the lengths match, each character must be checked.  the stored buffer starts at
0394     // entry[1]
0395     for (int i = 0; i < length; i++) {
0396         if (entry[i + 1] != unicodePoints[i])
0397             return false;
0398     }
0399     return true;
0400 }
0401 ushort ExtendedCharTable::createExtendedChar(ushort *unicodePoints, ushort length)
0402 {
0403     // look for this sequence of points in the table
0404     ushort hash = extendedCharHash(unicodePoints, length);
0405 
0406     // check existing entry for match
0407     while (extendedCharTable.contains(hash)) {
0408         if (extendedCharMatch(hash, unicodePoints, length)) {
0409             // this sequence already has an entry in the table,
0410             // return its hash
0411             return hash;
0412         } else {
0413             // if hash is already used by another, different sequence of unicode character
0414             // points then try next hash
0415             hash++;
0416         }
0417     }
0418 
0419     // add the new sequence to the table and
0420     // return that index
0421     std::vector<ushort> buffer(length + 1);
0422     buffer[0] = length;
0423     for (int i = 0; i < length; i++)
0424         buffer[i + 1] = unicodePoints[i];
0425 
0426     extendedCharTable.insert({hash, std::move(buffer)});
0427 
0428     return hash;
0429 }
0430 
0431 std::span<const ushort> ExtendedCharTable::lookupExtendedChar(ushort hash, ushort &length) const
0432 {
0433     // lookup index in table and if found, set the length
0434     // argument and return a pointer to the character sequence
0435 
0436     std::span buffer = extendedCharTable.at(hash);
0437     if (!buffer.empty()) {
0438         length = buffer[0];
0439         return buffer.subspan(1);
0440     } else {
0441         length = 0;
0442         return std::span<ushort>();
0443     }
0444 }
0445 
0446 ExtendedCharTable::ExtendedCharTable() = default;
0447 ExtendedCharTable::~ExtendedCharTable() = default;
0448 
0449 // global instance
0450 ExtendedCharTable ExtendedCharTable::instance;
0451 
0452 // #include "Emulation.moc"