1 /*
2     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
3     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
4     SPDX-FileCopyrightText: 1996 Matthias Ettrich <ettrich@kde.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 // Own
10 #include "Emulation.h"
11 
12 // Qt
13 #include <QKeyEvent>
14 
15 // Konsole
16 #include "Screen.h"
17 #include "ScreenWindow.h"
18 #include "keyboardtranslator/KeyboardTranslator.h"
19 #include "keyboardtranslator/KeyboardTranslatorManager.h"
20 
21 using namespace Konsole;
22 
Emulation()23 Emulation::Emulation()
24     : _windows(QList<ScreenWindow *>())
25     , _currentScreen(nullptr)
26     , _codec(nullptr)
27     , _keyTranslator(nullptr)
28     , _usesMouseTracking(false)
29     , _bracketedPasteMode(false)
30     , _bulkTimer1(QTimer(this))
31     , _bulkTimer2(QTimer(this))
32     , _imageSizeInitialized(false)
33     , _peekingPrimary(false)
34     , _activeScreenIndex(0)
35 {
36     // create screens with a default size
37     _screen[0] = new Screen(40, 80);
38     _screen[1] = new Screen(40, 80);
39     _currentScreen = _screen[0];
40 
41     QObject::connect(&_bulkTimer1, &QTimer::timeout, this, &Konsole::Emulation::showBulk);
42     QObject::connect(&_bulkTimer2, &QTimer::timeout, this, &Konsole::Emulation::showBulk);
43 
44     // listen for mouse status changes
45     connect(this, &Konsole::Emulation::programRequestsMouseTracking, this, &Konsole::Emulation::setUsesMouseTracking);
46     connect(this, &Konsole::Emulation::programBracketedPasteModeChanged, this, &Konsole::Emulation::bracketedPasteModeChanged);
47 }
48 
programUsesMouseTracking() const49 bool Emulation::programUsesMouseTracking() const
50 {
51     return _usesMouseTracking;
52 }
53 
setUsesMouseTracking(bool usesMouseTracking)54 void Emulation::setUsesMouseTracking(bool usesMouseTracking)
55 {
56     _usesMouseTracking = usesMouseTracking;
57 }
58 
programBracketedPasteMode() const59 bool Emulation::programBracketedPasteMode() const
60 {
61     return _bracketedPasteMode;
62 }
63 
bracketedPasteModeChanged(bool bracketedPasteMode)64 void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode)
65 {
66     _bracketedPasteMode = bracketedPasteMode;
67 }
68 
createWindow()69 ScreenWindow *Emulation::createWindow()
70 {
71     auto window = new ScreenWindow(_currentScreen);
72     _windows << window;
73 
74     connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::bufferedUpdate);
75     connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::checkSelectedText);
76 
77     connect(this, &Konsole::Emulation::outputChanged, window, &Konsole::ScreenWindow::notifyOutputChanged);
78 
79     return window;
80 }
81 
setCurrentTerminalDisplay(TerminalDisplay * display)82 void Emulation::setCurrentTerminalDisplay(TerminalDisplay *display)
83 {
84     _screen[0]->setCurrentTerminalDisplay(display);
85     _screen[1]->setCurrentTerminalDisplay(display);
86 }
87 
checkScreenInUse()88 void Emulation::checkScreenInUse()
89 {
90     Q_EMIT primaryScreenInUse(_currentScreen == _screen[0]);
91 }
92 
checkSelectedText()93 void Emulation::checkSelectedText()
94 {
95     QString text = _currentScreen->selectedText(Screen::PreserveLineBreaks);
96     Q_EMIT selectionChanged(text);
97 }
98 
~Emulation()99 Emulation::~Emulation()
100 {
101     for (ScreenWindow *window : qAsConst(_windows)) {
102         delete window;
103     }
104 
105     delete _screen[0];
106     delete _screen[1];
107 }
108 
setPeekPrimary(const bool doPeek)109 void Emulation::setPeekPrimary(const bool doPeek)
110 {
111     if (doPeek == _peekingPrimary) {
112         return;
113     }
114     _peekingPrimary = doPeek;
115     setScreenInternal(doPeek ? 0 : _activeScreenIndex);
116     Q_EMIT outputChanged();
117 }
118 
setScreen(int index)119 void Emulation::setScreen(int index)
120 {
121     _activeScreenIndex = index;
122     _peekingPrimary = false;
123     setScreenInternal(_activeScreenIndex);
124 }
125 
setScreenInternal(int index)126 void Emulation::setScreenInternal(int index)
127 {
128     Screen *oldScreen = _currentScreen;
129     _currentScreen = _screen[index & 1];
130     if (_currentScreen != oldScreen) {
131         // tell all windows onto this emulation to switch to the newly active screen
132         for (ScreenWindow *window : qAsConst(_windows)) {
133             window->setScreen(_currentScreen);
134         }
135 
136         checkScreenInUse();
137         checkSelectedText();
138     }
139 }
140 
clearHistory()141 void Emulation::clearHistory()
142 {
143     _screen[0]->setScroll(_screen[0]->getScroll(), false);
144 }
145 
setHistory(const HistoryType & history)146 void Emulation::setHistory(const HistoryType &history)
147 {
148     _screen[0]->setScroll(history);
149 
150     showBulk();
151 }
152 
history() const153 const HistoryType &Emulation::history() const
154 {
155     return _screen[0]->getScroll();
156 }
157 
setCodec(const QTextCodec * codec)158 void Emulation::setCodec(const QTextCodec *codec)
159 {
160     if (codec != nullptr) {
161         _codec = codec;
162 
163         _decoder.reset(_codec->makeDecoder());
164 
165         Q_EMIT useUtf8Request(utf8());
166     } else {
167         setCodec(LocaleCodec);
168     }
169 }
170 
setCodec(EmulationCodec codec)171 void Emulation::setCodec(EmulationCodec codec)
172 {
173     if (codec == Utf8Codec) {
174         setCodec(QTextCodec::codecForName("utf8"));
175     } else if (codec == LocaleCodec) {
176         setCodec(QTextCodec::codecForLocale());
177     }
178 }
179 
setKeyBindings(const QString & name)180 void Emulation::setKeyBindings(const QString &name)
181 {
182     _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
183     if (_keyTranslator == nullptr) {
184         _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
185     }
186 }
187 
keyBindings() const188 QString Emulation::keyBindings() const
189 {
190     return _keyTranslator->name();
191 }
192 
193 // process application unicode input to terminal
194 // this is a trivial scanner
receiveChar(uint c)195 void Emulation::receiveChar(uint c)
196 {
197     c &= 0xff;
198     switch (c) {
199     case '\b':
200         _currentScreen->backspace();
201         break;
202     case '\t':
203         _currentScreen->tab();
204         break;
205     case '\n':
206         _currentScreen->newLine();
207         break;
208     case '\r':
209         _currentScreen->toStartOfLine();
210         break;
211     case 0x07:
212         Q_EMIT bell();
213         break;
214     default:
215         _currentScreen->displayCharacter(c);
216         break;
217     }
218 }
219 
sendKeyEvent(QKeyEvent * ev)220 void Emulation::sendKeyEvent(QKeyEvent *ev)
221 {
222     if (!ev->text().isEmpty()) {
223         // A block of text
224         // Note that the text is proper unicode.
225         // We should do a conversion here
226         Q_EMIT sendData(ev->text().toLocal8Bit());
227     }
228 }
229 
receiveData(const char * text,int length)230 void Emulation::receiveData(const char *text, int length)
231 {
232     Q_ASSERT(_decoder);
233 
234     bufferedUpdate();
235 
236     // send characters to terminal emulator
237     for (const uint i : _decoder->toUnicode(text, length).toUcs4()) {
238         receiveChar(i);
239     }
240 
241     // look for z-modem indicator
242     //-- someone who understands more about z-modems that I do may be able to move
243     // this check into the above for loop?
244     for (int i = 0; i < length - 4; i++) {
245         if (text[i] == '\030') {
246             if (qstrncmp(text + i + 1, "B00", 3) == 0) {
247                 Q_EMIT zmodemDownloadDetected();
248             } else if (qstrncmp(text + i + 1, "B01", 3) == 0) {
249                 Q_EMIT zmodemUploadDetected();
250             }
251         }
252     }
253 }
254 
writeToStream(TerminalCharacterDecoder * decoder,int startLine,int endLine)255 void Emulation::writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine)
256 {
257     _currentScreen->writeLinesToStream(decoder, startLine, endLine);
258 }
259 
lineCount() const260 int Emulation::lineCount() const
261 {
262     // sum number of lines currently on _screen plus number of lines in history
263     return _currentScreen->getLines() + _currentScreen->getHistLines();
264 }
265 
showBulk()266 void Emulation::showBulk()
267 {
268     _bulkTimer1.stop();
269     _bulkTimer2.stop();
270 
271     Q_EMIT outputChanged();
272 
273     _currentScreen->resetScrolledLines();
274     _currentScreen->resetDroppedLines();
275 }
276 
bufferedUpdate()277 void Emulation::bufferedUpdate()
278 {
279     static const int BULK_TIMEOUT1 = 10;
280     static const int BULK_TIMEOUT2 = 40;
281 
282     _bulkTimer1.setSingleShot(true);
283     _bulkTimer1.start(BULK_TIMEOUT1);
284     if (!_bulkTimer2.isActive()) {
285         _bulkTimer2.setSingleShot(true);
286         _bulkTimer2.start(BULK_TIMEOUT2);
287     }
288 }
289 
eraseChar() const290 char Emulation::eraseChar() const
291 {
292     return '\b';
293 }
294 
setImageSize(int lines,int columns)295 void Emulation::setImageSize(int lines, int columns)
296 {
297     if ((lines < 1) || (columns < 1)) {
298         return;
299     }
300 
301     QSize screenSize[2] = {QSize(_screen[0]->getColumns(), _screen[0]->getLines()), //
302                            QSize(_screen[1]->getColumns(), _screen[1]->getLines())};
303     QSize newSize(columns, lines);
304 
305     if (newSize == screenSize[0] && newSize == screenSize[1]) {
306         // If this method is called for the first time, always emit
307         // SIGNAL(imageSizeChange()), even if the new size is the same as the
308         // current size.  See #176902
309         if (!_imageSizeInitialized) {
310             Q_EMIT imageSizeChanged(lines, columns);
311         }
312     } else {
313         _screen[0]->resizeImage(lines, columns);
314         _screen[1]->resizeImage(lines, columns);
315 
316         Q_EMIT imageSizeChanged(lines, columns);
317 
318         bufferedUpdate();
319     }
320 
321     if (!_imageSizeInitialized) {
322         _imageSizeInitialized = true;
323 
324         Q_EMIT imageSizeInitialized();
325     }
326 }
327 
imageSize() const328 QSize Emulation::imageSize() const
329 {
330     return {_currentScreen->getColumns(), _currentScreen->getLines()};
331 }
332