1/*
2 Copyright 2007-2008 Robert Knight <robertknight@gmail.com>
3 Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
4 Copyright 1996 by Matthias Ettrich <ettrich@kde.org>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20*/
21
22// Own
23#include "Emulation.h"
24
25// System
26#include <stdio.h>
27#include <stdlib.h>
28#include <unistd.h>
29#include <string>
30
31// Qt
32#include <QApplication>
33#include <QClipboard>
34#include <QHash>
35#include <QKeyEvent>
36#include <QRegExp>
37#include <QTextStream>
38#include <QThread>
39
40#include <QTime>
41
42// KDE
43//#include <kdebug.h>
44
45// Konsole
46#include "KeyboardTranslator.h"
47#include "Screen.h"
48#include "TerminalCharacterDecoder.h"
49#include "ScreenWindow.h"
50
51using namespace Konsole;
52
53Emulation::Emulation() :
54 _currentScreen(0),
55 _codec(0),
56 _decoder(0),
57 _keyTranslator(0),
58 _usesMouse(false),
59 _bracketedPasteMode(false)
60{
61 // create screens with a default size
62 _screen[0] = new Screen(40,80);
63 _screen[1] = new Screen(40,80);
64 _currentScreen = _screen[0];
65
66 QObject::connect(&_bulkTimer1, SIGNAL(timeout()), this, SLOT(showBulk()) );
67 QObject::connect(&_bulkTimer2, SIGNAL(timeout()), this, SLOT(showBulk()) );
68
69 // listen for mouse status changes
70 connect(this , SIGNAL(programUsesMouseChanged(bool)) ,
71 SLOT(usesMouseChanged(bool)));
72 connect(this , SIGNAL(programBracketedPasteModeChanged(bool)) ,
73 SLOT(bracketedPasteModeChanged(bool)));
74
75 connect(this, &Emulation::cursorChanged, [this] (KeyboardCursorShape cursorShape, bool blinkingCursorEnabled) {
76 emit titleChanged( 50, QString(QLatin1String("CursorShape=%1;BlinkingCursorEnabled=%2"))
77 .arg(static_cast<int>(cursorShape)).arg(blinkingCursorEnabled) );
78 });
79}
80
81bool Emulation::programUsesMouse() const
82{
83 return _usesMouse;
84}
85
86void Emulation::usesMouseChanged(bool usesMouse)
87{
88 _usesMouse = usesMouse;
89}
90
91bool Emulation::programBracketedPasteMode() const
92{
93 return _bracketedPasteMode;
94}
95
96void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode)
97{
98 _bracketedPasteMode = bracketedPasteMode;
99}
100
101ScreenWindow* Emulation::createWindow()
102{
103 ScreenWindow* window = new ScreenWindow();
104 window->setScreen(_currentScreen);
105 _windows << window;
106
107 connect(window , SIGNAL(selectionChanged()),
108 this , SLOT(bufferedUpdate()));
109
110 connect(this , SIGNAL(outputChanged()),
111 window , SLOT(notifyOutputChanged()) );
112 return window;
113}
114
115Emulation::~Emulation()
116{
117 QListIterator<ScreenWindow*> windowIter(_windows);
118
119 while (windowIter.hasNext())
120 {
121 delete windowIter.next();
122 }
123
124 delete _screen[0];
125 delete _screen[1];
126 delete _decoder;
127}
128
129void Emulation::setScreen(int n)
130{
131 Screen *old = _currentScreen;
132 _currentScreen = _screen[n & 1];
133 if (_currentScreen != old)
134 {
135 // tell all windows onto this emulation to switch to the newly active screen
136 for(ScreenWindow* window : const_cast<const QList<ScreenWindow*>&>(_windows))
137 window->setScreen(_currentScreen);
138 }
139}
140
141void Emulation::clearHistory()
142{
143 _screen[0]->setScroll( _screen[0]->getScroll() , false );
144}
145void Emulation::setHistory(const HistoryType& t)
146{
147 _screen[0]->setScroll(t);
148
149 showBulk();
150}
151
152const HistoryType& Emulation::history() const
153{
154 return _screen[0]->getScroll();
155}
156
157void Emulation::setCodec(const QTextCodec * qtc)
158{
159 if (qtc)
160 _codec = qtc;
161 else
162 setCodec(LocaleCodec);
163
164 delete _decoder;
165 _decoder = _codec->makeDecoder();
166
167 emit useUtf8Request(utf8());
168}
169
170void Emulation::setCodec(EmulationCodec codec)
171{
172 if ( codec == Utf8Codec )
173 setCodec( QTextCodec::codecForName("utf8") );
174 else if ( codec == LocaleCodec )
175 setCodec( QTextCodec::codecForLocale() );
176}
177
178void Emulation::setKeyBindings(const QString& name)
179{
180 _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
181 if (!_keyTranslator)
182 {
183 _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
184 }
185}
186
187QString Emulation::keyBindings() const
188{
189 return _keyTranslator->name();
190}
191
192void Emulation::receiveChar(wchar_t c)
193// process application unicode input to terminal
194// this is a trivial scanner
195{
196 c &= 0xff;
197 switch (c)
198 {
199 case '\b' : _currentScreen->backspace(); break;
200 case '\t' : _currentScreen->tab(); break;
201 case '\n' : _currentScreen->newLine(); break;
202 case '\r' : _currentScreen->toStartOfLine(); break;
203 case 0x07 : emit stateSet(NOTIFYBELL);
204 break;
205 default : _currentScreen->displayCharacter(c); break;
206 };
207}
208
209void Emulation::sendKeyEvent( QKeyEvent* ev )
210{
211 emit stateSet(NOTIFYNORMAL);
212
213 if (!ev->text().isEmpty())
214 { // A block of text
215 // Note that the text is proper unicode.
216 // We should do a conversion here
217 emit sendData(ev->text().toUtf8().constData(),ev->text().length());
218 }
219}
220
221void Emulation::sendString(const char*,int)
222{
223 // default implementation does nothing
224}
225
226void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/)
227{
228 // default implementation does nothing
229}
230
231/*
232 We are doing code conversion from locale to unicode first.
233TODO: Character composition from the old code. See #96536
234*/
235
236void Emulation::receiveData(const char* text, int length)
237{
238 emit stateSet(NOTIFYACTIVITY);
239
240 bufferedUpdate();
241
242 /* XXX: the following code involves encoding & decoding of "UTF-16
243 * surrogate pairs", which does not work with characters higher than
244 * U+10FFFF
245 * https://unicodebook.readthedocs.io/unicode_encodings.html#surrogates
246 */
247 QString utf16Text = _decoder->toUnicode(text,length);
248 std::wstring unicodeText = utf16Text.toStdWString();
249
250 //send characters to terminal emulator
251 for (size_t i=0;i<unicodeText.length();i++)
252 receiveChar(unicodeText[i]);
253
254 //look for z-modem indicator
255 //-- someone who understands more about z-modems that I do may be able to move
256 //this check into the above for loop?
257 for (int i=0;i<length;i++)
258 {
259 if (text[i] == '\030')
260 {
261 if ((length-i-1 > 3) && (strncmp(text+i+1, "B00", 3) == 0))
262 emit zmodemDetected();
263 }
264 }
265}
266
267//OLDER VERSION
268//This version of onRcvBlock was commented out because
269// a) It decoded incoming characters one-by-one, which is slow in the current version of Qt (4.2 tech preview)
270// b) It messed up decoding of non-ASCII characters, with the result that (for example) chinese characters
271// were not printed properly.
272//
273//There is something about stopping the _decoder if "we get a control code halfway a multi-byte sequence" (see below)
274//which hasn't been ported into the newer function (above). Hopefully someone who understands this better
275//can find an alternative way of handling the check.
276
277
278/*void Emulation::onRcvBlock(const char *s, int len)
279{
280 emit notifySessionState(NOTIFYACTIVITY);
281
282 bufferedUpdate();
283 for (int i = 0; i < len; i++)
284 {
285
286 QString result = _decoder->toUnicode(&s[i],1);
287 int reslen = result.length();
288
289 // If we get a control code halfway a multi-byte sequence
290 // we flush the _decoder and continue with the control code.
291 if ((s[i] < 32) && (s[i] > 0))
292 {
293 // Flush _decoder
294 while(!result.length())
295 result = _decoder->toUnicode(&s[i],1);
296 reslen = 1;
297 result.resize(reslen);
298 result[0] = QChar(s[i]);
299 }
300
301 for (int j = 0; j < reslen; j++)
302 {
303 if (result[j].characterategory() == QChar::Mark_NonSpacing)
304 _currentScreen->compose(result.mid(j,1));
305 else
306 onRcvChar(result[j].unicode());
307 }
308 if (s[i] == '\030')
309 {
310 if ((len-i-1 > 3) && (strncmp(s+i+1, "B00", 3) == 0))
311 emit zmodemDetected();
312 }
313 }
314}*/
315
316void Emulation::writeToStream( TerminalCharacterDecoder* _decoder ,
317 int startLine ,
318 int endLine)
319{
320 _currentScreen->writeLinesToStream(_decoder,startLine,endLine);
321}
322
323int Emulation::lineCount() const
324{
325 // sum number of lines currently on _screen plus number of lines in history
326 return _currentScreen->getLines() + _currentScreen->getHistLines();
327}
328
329#define BULK_TIMEOUT1 10
330#define BULK_TIMEOUT2 40
331
332void Emulation::showBulk()
333{
334 _bulkTimer1.stop();
335 _bulkTimer2.stop();
336
337 emit outputChanged();
338
339 _currentScreen->resetScrolledLines();
340 _currentScreen->resetDroppedLines();
341}
342
343void Emulation::bufferedUpdate()
344{
345 _bulkTimer1.setSingleShot(true);
346 _bulkTimer1.start(BULK_TIMEOUT1);
347 if (!_bulkTimer2.isActive())
348 {
349 _bulkTimer2.setSingleShot(true);
350 _bulkTimer2.start(BULK_TIMEOUT2);
351 }
352}
353
354char Emulation::eraseChar() const
355{
356 return '\b';
357}
358
359void Emulation::setImageSize(int lines, int columns)
360{
361 if ((lines < 1) || (columns < 1))
362 return;
363
364 QSize screenSize[2] = { QSize(_screen[0]->getColumns(),
365 _screen[0]->getLines()),
366 QSize(_screen[1]->getColumns(),
367 _screen[1]->getLines()) };
368 QSize newSize(columns,lines);
369
370 if (newSize == screenSize[0] && newSize == screenSize[1])
371 return;
372
373 _screen[0]->resizeImage(lines,columns);
374 _screen[1]->resizeImage(lines,columns);
375
376 emit imageSizeChanged(lines,columns);
377
378 bufferedUpdate();
379}
380
381QSize Emulation::imageSize() const
382{
383 return QSize(_currentScreen->getColumns(), _currentScreen->getLines());
384}
385
386ushort ExtendedCharTable::extendedCharHash(ushort* unicodePoints , ushort length) const
387{
388 ushort hash = 0;
389 for ( ushort i = 0 ; i < length ; i++ )
390 {
391 hash = 31*hash + unicodePoints[i];
392 }
393 return hash;
394}
395bool ExtendedCharTable::extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const
396{
397 ushort* entry = extendedCharTable[hash];
398
399 // compare given length with stored sequence length ( given as the first ushort in the
400 // stored buffer )
401 if ( entry == 0 || entry[0] != length )
402 return false;
403 // if the lengths match, each character must be checked. the stored buffer starts at
404 // entry[1]
405 for ( int i = 0 ; i < length ; i++ )
406 {
407 if ( entry[i+1] != unicodePoints[i] )
408 return false;
409 }
410 return true;
411}
412ushort ExtendedCharTable::createExtendedChar(ushort* unicodePoints , ushort length)
413{
414 // look for this sequence of points in the table
415 ushort hash = extendedCharHash(unicodePoints,length);
416
417 // check existing entry for match
418 while ( extendedCharTable.contains(hash) )
419 {
420 if ( extendedCharMatch(hash,unicodePoints,length) )
421 {
422 // this sequence already has an entry in the table,
423 // return its hash
424 return hash;
425 }
426 else
427 {
428 // if hash is already used by another, different sequence of unicode character
429 // points then try next hash
430 hash++;
431 }
432 }
433
434
435 // add the new sequence to the table and
436 // return that index
437 ushort* buffer = new ushort[length+1];
438 buffer[0] = length;
439 for ( int i = 0 ; i < length ; i++ )
440 buffer[i+1] = unicodePoints[i];
441
442 extendedCharTable.insert(hash,buffer);
443
444 return hash;
445}
446
447ushort* ExtendedCharTable::lookupExtendedChar(ushort hash , ushort& length) const
448{
449 // lookup index in table and if found, set the length
450 // argument and return a pointer to the character sequence
451
452 ushort* buffer = extendedCharTable[hash];
453 if ( buffer )
454 {
455 length = buffer[0];
456 return buffer+1;
457 }
458 else
459 {
460 length = 0;
461 return 0;
462 }
463}
464
465ExtendedCharTable::ExtendedCharTable()
466{
467}
468ExtendedCharTable::~ExtendedCharTable()
469{
470 // free all allocated character buffers
471 QHashIterator<ushort,ushort*> iter(extendedCharTable);
472 while ( iter.hasNext() )
473 {
474 iter.next();
475 delete[] iter.value();
476 }
477}
478
479// global instance
480ExtendedCharTable ExtendedCharTable::instance;
481
482
483//#include "Emulation.moc"
484
485