2026-04-06 00:20:51 -05:00

378 lines
11 KiB
C++

#include "Emulation.h"
#include <cstdio>
#include <cstdlib>
#include <string>
#include <QApplication>
#include <QClipboard>
#include <QHash>
#include <QKeyEvent>
#include <QTextStream>
#include <QThread>
#include <QTime>
#include "util/KeyboardTranslator.h"
#include "Screen.h"
#include "ScreenWindow.h"
#include "util/TerminalCharacterDecoder.h"
#include "TerminalDisplay.h"
Emulation::Emulation()
: _currentScreen(nullptr)
, _keyTranslator(nullptr)
, _enableHandleCtrlC(false)
, _usesMouse(false)
, _bracketedPasteMode(false)
, _fromUtf8(QStringEncoder::Utf16) {
_screen[0] = new Screen(40, 80);
_screen[1] = new Screen(40, 80);
_currentScreen = _screen[0];
connect(&_bulkTimer1, &QTimer::timeout, this, &Emulation::showBulk);
connect(&_bulkTimer2, &QTimer::timeout, this, &Emulation::showBulk);
connect(this, &Emulation::programUsesMouseChanged, this, &Emulation::usesMouseChanged);
connect(this, &Emulation::programBracketedPasteModeChanged, this, &Emulation::bracketedPasteModeChanged);
connect(this, &Emulation::cursorChanged, this, [this](KeyboardCursorShape cursorShape, bool blinkingCursorEnabled) {
Q_EMIT profileChangeCommandReceived( QString(QLatin1String("CursorShape=%1;BlinkingCursorEnabled=%2")).arg(static_cast<int>(cursorShape)).arg(blinkingCursorEnabled));
});
}
bool Emulation::programUsesMouse() const {
return _usesMouse;
}
void Emulation::usesMouseChanged(bool usesMouse) {
_usesMouse = usesMouse;
}
bool Emulation::programBracketedPasteMode() const {
return _bracketedPasteMode;
}
void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode) {
_bracketedPasteMode = bracketedPasteMode;
}
ScreenWindow *Emulation::createWindow() {
ScreenWindow *window = new ScreenWindow();
window->setScreen(_currentScreen);
_windows << window;
ExtendedCharTable::instance.windows << window;
connect(window, &ScreenWindow::selectionChanged, this, &Emulation::bufferedUpdate);
connect(this, &Emulation::outputChanged, window, &ScreenWindow::notifyOutputChanged);
connect(this, &Emulation::handleCommandFromKeyboard, window, &ScreenWindow::handleCommandFromKeyboard);
connect(this, &Emulation::handleCtrlC, window, &ScreenWindow::handleCtrlC);
connect(this, &Emulation::outputFromKeypressEvent, window, &ScreenWindow::scrollToEnd);
return window;
}
void Emulation::checkScreenInUse() {
Q_EMIT primaryScreenInUse(_currentScreen == _screen[0]);
}
Emulation::~Emulation() {
QListIterator<ScreenWindow *> windowIter(_windows);
while (windowIter.hasNext()) {
auto win = windowIter.next();
ExtendedCharTable::instance.windows.remove(win);
delete win;
}
delete _screen[0];
delete _screen[1];
}
void Emulation::setScreen(int n) {
Screen *old = _currentScreen;
_currentScreen = _screen[n & 1];
if (_currentScreen != old) {
for (ScreenWindow *window : std::as_const(_windows))
window->setScreen(_currentScreen);
checkScreenInUse();
}
}
void Emulation::clearHistory() {
_screen[0]->setScroll(_screen[0]->getScroll(), false);
}
void Emulation::setHistory(const HistoryType &t) {
_screen[0]->setScroll(t);
showBulk();
}
const HistoryType &Emulation::history() const {
return _screen[0]->getScroll();
}
void Emulation::setCodec(QStringEncoder qtc) {
if (qtc.isValid())
_fromUtf16 = std::move(qtc);
else
setCodec(LocaleCodec);
_toUtf16 = QStringDecoder{utf8() ? QStringConverter::Encoding::Utf8
: QStringConverter::Encoding::System};
Q_EMIT useUtf8Request(utf8());
}
bool Emulation::utf8() const {
const auto enc = QStringConverter::encodingForName(_fromUtf16.name());
return enc && enc.value() == QStringConverter::Encoding::Utf8;
}
void Emulation::setCodec(EmulationCodec codec) {
setCodec(QStringEncoder{codec == Utf8Codec
? QStringConverter::Encoding::Utf8
: QStringConverter::Encoding::System});
}
void Emulation::setKeyBindings(const QString &name) {
_keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
if (!_keyTranslator) {
_keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
}
}
QString Emulation::keyBindings() const {
return _keyTranslator->name();
}
void Emulation::receiveChar(wchar_t c) {
c &= 0xff;
switch (c) {
case '\b':
_currentScreen->backspace();
break;
case '\t':
_currentScreen->tab();
break;
case '\n':
_currentScreen->newLine();
break;
case '\r':
_currentScreen->toStartOfLine();
break;
case 0x07:
Q_EMIT stateSet(NOTIFYBELL);
break;
default:
_currentScreen->displayCharacter(c);
break;
};
}
void Emulation::sendKeyEvent(QKeyEvent *ev, bool) {
Q_EMIT stateSet(NOTIFYNORMAL);
if (!ev->text().isEmpty()) {
Q_EMIT sendData(ev->text().toUtf8().constData(), ev->text().length());
}
}
void Emulation::sendString(const char *, int) {}
void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) {}
void Emulation::receiveData(const char *text, int length) {
Q_EMIT stateSet(NOTIFYACTIVITY);
bufferedUpdate();
QString utf16Text = _toUtf16(QByteArray::fromRawData(text, length));
std::wstring unicodeText = utf16Text.toStdWString();
for (wchar_t i : unicodeText)
receiveChar(i);
for (int i = 0; i < length; i++) {
if (text[i] == '\030') {
if ((length - i - 1 > 3) && (strncmp(text + i + 1, "B00", 3) == 0)) {
Q_EMIT zmodemSendDetected();
}
if ((length - i - 1 > 5) && (strncmp(text + i + 1, "B0100", 5) == 0)) {
Q_EMIT zmodemRecvDetected();
}
}
}
}
void Emulation::dupDisplayCharacter(wchar_t cc) {
if (cc == L'\n') {
dupCache.append(L'\n');
PlainTextDecoder decoder;
QString lineText;
QTextStream stream(&lineText);
decoder.begin(&stream);
Character *data = new Character[dupCache.size()];
for (int j = 0; j < dupCache.size(); j++) {
data[j] = Character(dupCache.at(j));
}
decoder.decodeLine(data, dupCache.size(), 0);
decoder.end();
delete[] data;
Q_EMIT dupDisplayOutput(lineText.toUtf8().constData(),
lineText.toUtf8().length());
dupCache.clear();
} else {
dupCache.append(cc);
}
}
void Emulation::writeToStream(TerminalCharacterDecoder *_decoder, int startLine,
int endLine) {
_currentScreen->writeLinesToStream(_decoder, startLine, endLine);
}
int Emulation::lineCount() const {
return _currentScreen->getLines() + _currentScreen->getHistLines();
}
void Emulation::showBulk() {
_bulkTimer1.stop();
_bulkTimer2.stop();
Q_EMIT outputChanged();
_currentScreen->resetScrolledLines();
_currentScreen->resetDroppedLines();
}
void Emulation::bufferedUpdate() {
static const int BULK_TIMEOUT1 = 10;
static const int BULK_TIMEOUT2 = 40;
_bulkTimer1.setSingleShot(true);
_bulkTimer1.start(BULK_TIMEOUT1);
if (!_bulkTimer2.isActive()) {
_bulkTimer2.setSingleShot(true);
_bulkTimer2.start(BULK_TIMEOUT2);
}
}
char Emulation::eraseChar() const {
return '\b';
}
void Emulation::setImageSize(int lines, int columns) {
if ((lines < 1) || (columns < 1))
return;
QSize screenSize[2] = {
QSize(_screen[0]->getColumns(), _screen[0]->getLines()),
QSize(_screen[1]->getColumns(), _screen[1]->getLines())};
QSize newSize(columns, lines);
if (newSize == screenSize[0] && newSize == screenSize[1])
return;
_screen[0]->resizeImage(lines, columns);
_screen[1]->resizeImage(lines, columns);
Q_EMIT imageSizeChanged(lines, columns);
bufferedUpdate();
}
QSize Emulation::imageSize() const {
return {_currentScreen->getColumns(), _currentScreen->getLines()};
}
uint ExtendedCharTable::extendedCharHash(uint* unicodePoints , ushort length) const {
uint hash = 0;
for (ushort i = 0; i < length; i++) {
hash = 31 * hash + unicodePoints[i];
}
return hash;
}
bool ExtendedCharTable::extendedCharMatch(uint hash , uint* unicodePoints , ushort length) const {
uint* entry = extendedCharTable[hash];
if (entry == nullptr || entry[0] != length)
return false;
for (int i = 0; i < length; i++) {
if (entry[i + 1] != unicodePoints[i])
return false;
}
return true;
}
uint ExtendedCharTable::createExtendedChar(uint* unicodePoints , ushort length) {
uint hash = extendedCharHash(unicodePoints,length);
const uint initialHash = hash;
bool triedCleaningSolution = false;
while (extendedCharTable.contains(hash) && hash != 0) {
if (extendedCharMatch(hash, unicodePoints, length)) {
return hash;
} else {
hash++;
if (hash == initialHash) {
if (!triedCleaningSolution) {
triedCleaningSolution = true;
QSet<uint> usedExtendedChars;
for (const auto &w : std::as_const(windows)) {
if (w->screen()) {
usedExtendedChars += w->screen()->usedExtendedChars();
}
}
QHash<uint,uint*>::iterator it = extendedCharTable.begin();
QHash<uint,uint*>::iterator itEnd = extendedCharTable.end();
while (it != itEnd) {
if (usedExtendedChars.contains(it.key())) {
++it;
} else {
it = extendedCharTable.erase(it);
}
}
} else {
return 0;
}
}
}
}
uint* buffer = new uint[length+1];
buffer[0] = length;
for (int i = 0; i < length; i++)
buffer[i + 1] = unicodePoints[i];
extendedCharTable.insert(hash, buffer);
return hash;
}
uint* ExtendedCharTable::lookupExtendedChar(uint hash , ushort& length) const {
uint* buffer = extendedCharTable[hash];
if (buffer) {
length = buffer[0];
return buffer + 1;
} else {
length = 0;
return nullptr;
}
}
ExtendedCharTable::ExtendedCharTable() {
}
ExtendedCharTable::~ExtendedCharTable() {
QHashIterator<uint,uint*> iter(extendedCharTable);
while (iter.hasNext()) {
iter.next();
delete[] iter.value();
}
}
ExtendedCharTable ExtendedCharTable::instance;