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 | |
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 | |
81 | bool Emulation::programUsesMouse() const |
82 | { |
83 | return _usesMouse; |
84 | } |
85 | |
86 | void Emulation::usesMouseChanged(bool usesMouse) |
87 | { |
88 | _usesMouse = usesMouse; |
89 | } |
90 | |
91 | bool Emulation::programBracketedPasteMode() const |
92 | { |
93 | return _bracketedPasteMode; |
94 | } |
95 | |
96 | void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode) |
97 | { |
98 | _bracketedPasteMode = bracketedPasteMode; |
99 | } |
100 | |
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 | |
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 | |
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 | |
141 | void Emulation::clearHistory() |
142 | { |
143 | _screen[0]->setScroll( _screen[0]->getScroll() , false ); |
144 | } |
145 | void Emulation::setHistory(const HistoryType& t) |
146 | { |
147 | _screen[0]->setScroll(t); |
148 | |
149 | showBulk(); |
150 | } |
151 | |
152 | const HistoryType& Emulation::history() const |
153 | { |
154 | return _screen[0]->getScroll(); |
155 | } |
156 | |
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 | |
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 | |
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 | |
187 | QString Emulation::keyBindings() const |
188 | { |
189 | return _keyTranslator->name(); |
190 | } |
191 | |
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 | |
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 | |
221 | void Emulation::sendString(const char*,int) |
222 | { |
223 | // default implementation does nothing |
224 | } |
225 | |
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 | |
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 | |
316 | void Emulation::writeToStream( TerminalCharacterDecoder* _decoder , |
317 | int startLine , |
318 | int endLine) |
319 | { |
320 | _currentScreen->writeLinesToStream(_decoder,startLine,endLine); |
321 | } |
322 | |
323 | int 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 | |
332 | void Emulation::showBulk() |
333 | { |
334 | _bulkTimer1.stop(); |
335 | _bulkTimer2.stop(); |
336 | |
337 | emit outputChanged(); |
338 | |
339 | _currentScreen->resetScrolledLines(); |
340 | _currentScreen->resetDroppedLines(); |
341 | } |
342 | |
343 | void 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 | |
354 | char Emulation::eraseChar() const |
355 | { |
356 | return '\b'; |
357 | } |
358 | |
359 | void 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 | |
381 | QSize Emulation::imageSize() const |
382 | { |
383 | return QSize(_currentScreen->getColumns(), _currentScreen->getLines()); |
384 | } |
385 | |
386 | ushort 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 | } |
395 | bool 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 | } |
412 | ushort 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 | |
447 | ushort* 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 | |
465 | ExtendedCharTable::ExtendedCharTable() |
466 | { |
467 | } |
468 | ExtendedCharTable::~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 |
480 | ExtendedCharTable ExtendedCharTable::instance; |
481 | |
482 | |
483 | //#include "Emulation.moc" |
484 | |
485 | |