File indexing completed on 2025-03-16 08:11:24
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"