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