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 
51 using namespace Konsole;
52 
Emulation()53 Emulation::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 
programUsesMouse() const81 bool Emulation::programUsesMouse() const
82 {
83     return _usesMouse;
84 }
85 
usesMouseChanged(bool usesMouse)86 void Emulation::usesMouseChanged(bool usesMouse)
87 {
88     _usesMouse = usesMouse;
89 }
90 
programBracketedPasteMode() const91 bool Emulation::programBracketedPasteMode() const
92 {
93     return _bracketedPasteMode;
94 }
95 
bracketedPasteModeChanged(bool bracketedPasteMode)96 void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode)
97 {
98     _bracketedPasteMode = bracketedPasteMode;
99 }
100 
createWindow()101 ScreenWindow* 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 
~Emulation()115 Emulation::~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 
setScreen(int n)129 void 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 
clearHistory()141 void Emulation::clearHistory()
142 {
143     _screen[0]->setScroll( _screen[0]->getScroll() , false );
144 }
setHistory(const HistoryType & t)145 void Emulation::setHistory(const HistoryType& t)
146 {
147   _screen[0]->setScroll(t);
148 
149   showBulk();
150 }
151 
history() const152 const HistoryType& Emulation::history() const
153 {
154   return _screen[0]->getScroll();
155 }
156 
setCodec(const QTextCodec * qtc)157 void 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 
setCodec(EmulationCodec codec)170 void 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 
setKeyBindings(const QString & name)178 void Emulation::setKeyBindings(const QString& name)
179 {
180   _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name);
181   if (!_keyTranslator)
182   {
183       _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator();
184   }
185 }
186 
keyBindings() const187 QString Emulation::keyBindings() const
188 {
189   return _keyTranslator->name();
190 }
191 
receiveChar(wchar_t c)192 void 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 
sendKeyEvent(QKeyEvent * ev)209 void 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 
sendString(const char *,int)221 void Emulation::sendString(const char*,int)
222 {
223     // default implementation does nothing
224 }
225 
sendMouseEvent(int,int,int,int)226 void 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.
233 TODO: Character composition from the old code.  See #96536
234 */
235 
receiveData(const char * text,int length)236 void 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 
writeToStream(TerminalCharacterDecoder * _decoder,int startLine,int endLine)316 void Emulation::writeToStream( TerminalCharacterDecoder* _decoder ,
317                                int startLine ,
318                                int endLine)
319 {
320   _currentScreen->writeLinesToStream(_decoder,startLine,endLine);
321 }
322 
writeToStream(TerminalCharacterDecoder * _decoder)323 void Emulation::writeToStream( TerminalCharacterDecoder* _decoder)
324 {
325   _currentScreen->writeLinesToStream(_decoder, 0, _currentScreen->getHistLines());
326 }
327 
lineCount() const328 int Emulation::lineCount() const
329 {
330     // sum number of lines currently on _screen plus number of lines in history
331     return _currentScreen->getLines() + _currentScreen->getHistLines();
332 }
333 
334 #define BULK_TIMEOUT1 10
335 #define BULK_TIMEOUT2 40
336 
showBulk()337 void Emulation::showBulk()
338 {
339     _bulkTimer1.stop();
340     _bulkTimer2.stop();
341 
342     emit outputChanged();
343 
344     _currentScreen->resetScrolledLines();
345     _currentScreen->resetDroppedLines();
346 }
347 
bufferedUpdate()348 void Emulation::bufferedUpdate()
349 {
350    _bulkTimer1.setSingleShot(true);
351    _bulkTimer1.start(BULK_TIMEOUT1);
352    if (!_bulkTimer2.isActive())
353    {
354       _bulkTimer2.setSingleShot(true);
355       _bulkTimer2.start(BULK_TIMEOUT2);
356    }
357 }
358 
eraseChar() const359 char Emulation::eraseChar() const
360 {
361   return '\b';
362 }
363 
setImageSize(int lines,int columns)364 void Emulation::setImageSize(int lines, int columns)
365 {
366   if ((lines < 1) || (columns < 1))
367     return;
368 
369   QSize screenSize[2] = { QSize(_screen[0]->getColumns(),
370                                 _screen[0]->getLines()),
371                           QSize(_screen[1]->getColumns(),
372                                 _screen[1]->getLines()) };
373   QSize newSize(columns,lines);
374 
375   if (newSize == screenSize[0] && newSize == screenSize[1])
376     return;
377 
378   _screen[0]->resizeImage(lines,columns);
379   _screen[1]->resizeImage(lines,columns);
380 
381   emit imageSizeChanged(lines,columns);
382 
383   bufferedUpdate();
384 }
385 
imageSize() const386 QSize Emulation::imageSize() const
387 {
388   return QSize(_currentScreen->getColumns(), _currentScreen->getLines());
389 }
390 
extendedCharHash(ushort * unicodePoints,ushort length) const391 ushort ExtendedCharTable::extendedCharHash(ushort* unicodePoints , ushort length) const
392 {
393     ushort hash = 0;
394     for ( ushort i = 0 ; i < length ; i++ )
395     {
396         hash = 31*hash + unicodePoints[i];
397     }
398     return hash;
399 }
extendedCharMatch(ushort hash,ushort * unicodePoints,ushort length) const400 bool ExtendedCharTable::extendedCharMatch(ushort hash , ushort* unicodePoints , ushort length) const
401 {
402     ushort* entry = extendedCharTable[hash];
403 
404     // compare given length with stored sequence length ( given as the first ushort in the
405     // stored buffer )
406     if ( entry == 0 || entry[0] != length )
407        return false;
408     // if the lengths match, each character must be checked.  the stored buffer starts at
409     // entry[1]
410     for ( int i = 0 ; i < length ; i++ )
411     {
412         if ( entry[i+1] != unicodePoints[i] )
413            return false;
414     }
415     return true;
416 }
createExtendedChar(ushort * unicodePoints,ushort length)417 ushort ExtendedCharTable::createExtendedChar(ushort* unicodePoints , ushort length)
418 {
419     // look for this sequence of points in the table
420     ushort hash = extendedCharHash(unicodePoints,length);
421 
422     // check existing entry for match
423     while ( extendedCharTable.contains(hash) )
424     {
425         if ( extendedCharMatch(hash,unicodePoints,length) )
426         {
427             // this sequence already has an entry in the table,
428             // return its hash
429             return hash;
430         }
431         else
432         {
433             // if hash is already used by another, different sequence of unicode character
434             // points then try next hash
435             hash++;
436         }
437     }
438 
439 
440      // add the new sequence to the table and
441      // return that index
442     ushort* buffer = new ushort[length+1];
443     buffer[0] = length;
444     for ( int i = 0 ; i < length ; i++ )
445        buffer[i+1] = unicodePoints[i];
446 
447     extendedCharTable.insert(hash,buffer);
448 
449     return hash;
450 }
451 
lookupExtendedChar(ushort hash,ushort & length) const452 ushort* ExtendedCharTable::lookupExtendedChar(ushort hash , ushort& length) const
453 {
454     // lookup index in table and if found, set the length
455     // argument and return a pointer to the character sequence
456 
457     ushort* buffer = extendedCharTable[hash];
458     if ( buffer )
459     {
460         length = buffer[0];
461         return buffer+1;
462     }
463     else
464     {
465         length = 0;
466         return 0;
467     }
468 }
469 
ExtendedCharTable()470 ExtendedCharTable::ExtendedCharTable()
471 {
472 }
~ExtendedCharTable()473 ExtendedCharTable::~ExtendedCharTable()
474 {
475     // free all allocated character buffers
476     QHashIterator<ushort,ushort*> iter(extendedCharTable);
477     while ( iter.hasNext() )
478     {
479         iter.next();
480         delete[] iter.value();
481     }
482 }
483 
484 // global instance
485 ExtendedCharTable ExtendedCharTable::instance;
486 
487 
488 //#include "Emulation.moc"
489 
490