1/*
2 This file is part of Konsole
3
4 Copyright (C) 2006-2007 by Robert Knight <robertknight@gmail.com>
5 Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
6
7 Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 02110-1301 USA.
23*/
24
25// Own
26#include "Session.h"
27
28// Standard
29#include <stdlib.h>
30
31// Qt
32#include <QApplication>
33#include <QByteRef>
34#include <QDir>
35#include <QFile>
36#include <QRegExp>
37#include <QStringList>
38#include <QFile>
39#include <QtDebug>
40
41#include "Pty.h"
42//#include "kptyprocess.h"
43#include "TerminalDisplay.h"
44#include "ShellCommand.h"
45#include "Vt102Emulation.h"
46
47using namespace Konsole;
48
49int Session::lastSessionId = 0;
50
51Session::Session(QObject* parent) :
52 QObject(parent),
53 _shellProcess(0)
54 , _emulation(0)
55 , _monitorActivity(false)
56 , _monitorSilence(false)
57 , _notifiedActivity(false)
58 , _autoClose(true)
59 , _wantedClose(false)
60 , _silenceSeconds(10)
61 , _isTitleChanged(false)
62 , _addToUtmp(false) // disabled by default because of a bug encountered on certain systems
63 // which caused Konsole to hang when closing a tab and then opening a new
64 // one. A 'QProcess destroyed while still running' warning was being
65 // printed to the terminal. Likely a problem in KPty::logout()
66 // or KPty::login() which uses a QProcess to start /usr/bin/utempter
67 , _flowControl(true)
68 , _fullScripting(false)
69 , _sessionId(0)
70// , _zmodemBusy(false)
71// , _zmodemProc(0)
72// , _zmodemProgress(0)
73 , _hasDarkBackground(false)
74{
75 //prepare DBus communication
76// new SessionAdaptor(this);
77 _sessionId = ++lastSessionId;
78// QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this);
79
80 //create teletype for I/O with shell process
81 _shellProcess = new Pty();
82 ptySlaveFd = _shellProcess->pty()->slaveFd();
83
84 //create emulation backend
85 _emulation = new Vt102Emulation();
86
87 connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ),
88 this, SLOT( setUserTitle( int, const QString & ) ) );
89 connect( _emulation, SIGNAL( stateSet(int) ),
90 this, SLOT( activityStateSet(int) ) );
91// connect( _emulation, SIGNAL( zmodemDetected() ), this ,
92// SLOT( fireZModemDetected() ) );
93 connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ),
94 this, SIGNAL( changeTabTextColorRequest( int ) ) );
95 connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString &)),
96 this, SIGNAL( profileChangeCommandReceived(const QString &)) );
97
98 connect(_emulation, SIGNAL(imageResizeRequest(QSize)),
99 this, SLOT(onEmulationSizeChange(QSize)));
100 connect(_emulation, SIGNAL(imageSizeChanged(int, int)),
101 this, SLOT(onViewSizeChange(int, int)));
102 connect(_emulation, &Vt102Emulation::cursorChanged,
103 this, &Session::cursorChanged);
104
105 //connect teletype to emulation backend
106 _shellProcess->setUtf8Mode(_emulation->utf8());
107
108 connect( _shellProcess,SIGNAL(receivedData(const char *,int)),this,
109 SLOT(onReceiveBlock(const char *,int)) );
110 connect( _emulation,SIGNAL(sendData(const char *,int)),_shellProcess,
111 SLOT(sendData(const char *,int)) );
112 connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) );
113 connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) );
114
115 connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) );
116 // not in kprocess anymore connect( _shellProcess,SIGNAL(done(int)), this, SLOT(done(int)) );
117
118 //setup timer for monitoring session activity
119 _monitorTimer = new QTimer(this);
120 _monitorTimer->setSingleShot(true);
121 connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone()));
122}
123
124WId Session::windowId() const
125{
126 // On Qt5, requesting window IDs breaks QQuickWidget and the likes,
127 // for example, see the following bug reports:
128 // https://bugreports.qt.io/browse/QTBUG-40765
129 // https://codereview.qt-project.org/#/c/94880/
130 return 0;
131}
132
133void Session::setDarkBackground(bool darkBackground)
134{
135 _hasDarkBackground = darkBackground;
136}
137bool Session::hasDarkBackground() const
138{
139 return _hasDarkBackground;
140}
141bool Session::isRunning() const
142{
143 return _shellProcess->state() == QProcess::Running;
144}
145
146void Session::setCodec(QTextCodec * codec)
147{
148 emulation()->setCodec(codec);
149}
150
151void Session::setProgram(const QString & program)
152{
153 _program = ShellCommand::expand(program);
154}
155void Session::setInitialWorkingDirectory(const QString & dir)
156{
157 _initialWorkingDir = ShellCommand::expand(dir);
158}
159void Session::setArguments(const QStringList & arguments)
160{
161 _arguments = ShellCommand::expand(arguments);
162}
163
164QList<TerminalDisplay *> Session::views() const
165{
166 return _views;
167}
168
169void Session::addView(TerminalDisplay * widget)
170{
171 Q_ASSERT( !_views.contains(widget) );
172
173 _views.append(widget);
174
175 if ( _emulation != 0 ) {
176 // connect emulation - view signals and slots
177 connect( widget , SIGNAL(keyPressedSignal(QKeyEvent *)) , _emulation ,
178 SLOT(sendKeyEvent(QKeyEvent *)) );
179 connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation ,
180 SLOT(sendMouseEvent(int,int,int,int)) );
181 connect( widget , SIGNAL(sendStringToEmu(const char *)) , _emulation ,
182 SLOT(sendString(const char *)) );
183
184 // allow emulation to notify view when the foreground process
185 // indicates whether or not it is interested in mouse signals
186 connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget ,
187 SLOT(setUsesMouse(bool)) );
188
189 widget->setUsesMouse( _emulation->programUsesMouse() );
190
191 connect( _emulation , SIGNAL(programBracketedPasteModeChanged(bool)) ,
192 widget , SLOT(setBracketedPasteMode(bool)) );
193
194 widget->setBracketedPasteMode(_emulation->programBracketedPasteMode());
195
196 widget->setScreenWindow(_emulation->createWindow());
197 }
198
199 //connect view signals and slots
200 QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this,
201 SLOT(onViewSizeChange(int,int)));
202
203 QObject::connect( widget ,SIGNAL(destroyed(QObject *)) , this ,
204 SLOT(viewDestroyed(QObject *)) );
205//slot for close
206 QObject::connect(this, SIGNAL(finished()), widget, SLOT(close()));
207
208}
209
210void Session::viewDestroyed(QObject * view)
211{
212 TerminalDisplay * display = (TerminalDisplay *)view;
213
214 Q_ASSERT( _views.contains(display) );
215
216 removeView(display);
217}
218
219void Session::removeView(TerminalDisplay * widget)
220{
221 _views.removeAll(widget);
222
223 disconnect(widget,0,this,0);
224
225 if ( _emulation != 0 ) {
226 // disconnect
227 // - key presses signals from widget
228 // - mouse activity signals from widget
229 // - string sending signals from widget
230 //
231 // ... and any other signals connected in addView()
232 disconnect( widget, 0, _emulation, 0);
233
234 // disconnect state change signals emitted by emulation
235 disconnect( _emulation , 0 , widget , 0);
236 }
237
238 // close the session automatically when the last view is removed
239 if ( _views.count() == 0 ) {
240 close();
241 }
242}
243
244void Session::run()
245{
246 // Upon a KPty error, there is no description on what that error was...
247 // Check to see if the given program is executable.
248
249 /* ok iam not exactly sure where _program comes from - however it was set to /bin/bash on my system
250 * Thats bad for BSD as its /usr/local/bin/bash there - its also bad for arch as its /usr/bin/bash there too!
251 * So i added a check to see if /bin/bash exists - if no then we use $SHELL - if that does not exist either, we fall back to /bin/sh
252 * As far as i know /bin/sh exists on every unix system.. You could also just put some ifdef __FREEBSD__ here but i think these 2 filechecks are worth
253 * their computing time on any system - especially with the problem on arch linux beeing there too.
254 */
255 QString exec = QString::fromLocal8Bit(QFile::encodeName(_program));
256 // if 'exec' is not specified, fall back to default shell. if that
257 // is not set then fall back to /bin/sh
258
259 // here we expect full path. If there is no fullpath let's expect it's
260 // a custom shell (eg. python, etc.) available in the PATH.
261 if (exec.startsWith(QLatin1Char('/')) || exec.isEmpty())
262 {
263 const QString defaultShell{QLatin1String("/bin/sh")};
264
265 QFile excheck(exec);
266 if ( exec.isEmpty() || !excheck.exists() ) {
267 exec = QString::fromLocal8Bit(qgetenv("SHELL"));
268 }
269 excheck.setFileName(exec);
270
271 if ( exec.isEmpty() || !excheck.exists() ) {
272 qWarning() << "Neither default shell nor $SHELL is set to a correct path. Fallback to" << defaultShell;
273 exec = defaultShell;
274 }
275 }
276
277 // _arguments sometimes contain ("") so isEmpty()
278 // or count() does not work as expected...
279 QString argsTmp(_arguments.join(QLatin1Char(' ')).trimmed());
280 QStringList arguments;
281 arguments << exec;
282 if (argsTmp.length())
283 arguments << _arguments;
284
285 QString cwd = QDir::currentPath();
286 if (!_initialWorkingDir.isEmpty()) {
287 _shellProcess->setWorkingDirectory(_initialWorkingDir);
288 } else {
289 _shellProcess->setWorkingDirectory(cwd);
290 }
291
292 _shellProcess->setFlowControlEnabled(_flowControl);
293 _shellProcess->setErase(_emulation->eraseChar());
294
295 // this is not strictly accurate use of the COLORFGBG variable. This does not
296 // tell the terminal exactly which colors are being used, but instead approximates
297 // the color scheme as "black on white" or "white on black" depending on whether
298 // the background color is deemed dark or not
299 QString backgroundColorHint = _hasDarkBackground ? QLatin1String("COLORFGBG=15;0") : QLatin1String("COLORFGBG=0;15");
300
301 /* if we do all the checking if this shell exists then we use it ;)
302 * Dont know about the arguments though.. maybe youll need some more checking im not sure
303 * However this works on Arch and FreeBSD now.
304 */
305 int result = _shellProcess->start(exec,
306 arguments,
307 _environment << backgroundColorHint,
308 windowId(),
309 _addToUtmp);
310
311 if (result < 0) {
312 qDebug() << "CRASHED! result: " << result;
313 return;
314 }
315
316 _shellProcess->setWriteable(false); // We are reachable via kwrited.
317 emit started();
318}
319
320void Session::runEmptyPTY()
321{
322 _shellProcess->setFlowControlEnabled(_flowControl);
323 _shellProcess->setErase(_emulation->eraseChar());
324 _shellProcess->setWriteable(false);
325
326 // disconnet send data from emulator to internal terminal process
327 disconnect( _emulation,SIGNAL(sendData(const char *,int)),
328 _shellProcess, SLOT(sendData(const char *,int)) );
329
330 _shellProcess->setEmptyPTYProperties();
331 emit started();
332}
333
334void Session::setUserTitle( int what, const QString & caption )
335{
336 //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle )
337 bool modified = false;
338
339 // (btw: what=0 changes _userTitle and icon, what=1 only icon, what=2 only _nameTitle
340 if ((what == 0) || (what == 2)) {
341 _isTitleChanged = true;
342 if ( _userTitle != caption ) {
343 _userTitle = caption;
344 modified = true;
345 }
346 }
347
348 if ((what == 0) || (what == 1)) {
349 _isTitleChanged = true;
350 if ( _iconText != caption ) {
351 _iconText = caption;
352 modified = true;
353 }
354 }
355
356 if (what == 11) {
357 QString colorString = caption.section(QLatin1Char(';'),0,0);
358 //qDebug() << __FILE__ << __LINE__ << ": setting background colour to " << colorString;
359 QColor backColor = QColor(colorString);
360 if (backColor.isValid()) { // change color via \033]11;Color\007
361 if (backColor != _modifiedBackground) {
362 _modifiedBackground = backColor;
363
364 // bail out here until the code to connect the terminal display
365 // to the changeBackgroundColor() signal has been written
366 // and tested - just so we don't forget to do this.
367 Q_ASSERT( 0 );
368
369 emit changeBackgroundColorRequest(backColor);
370 }
371 }
372 }
373
374 if (what == 30) {
375 _isTitleChanged = true;
376 if ( _nameTitle != caption ) {
377 setTitle(Session::NameRole,caption);
378 return;
379 }
380 }
381
382 if (what == 31) {
383 QString cwd=caption;
384 cwd=cwd.replace( QRegExp(QLatin1String("^~")), QDir::homePath() );
385 emit openUrlRequest(cwd);
386 }
387
388 // change icon via \033]32;Icon\007
389 if (what == 32) {
390 _isTitleChanged = true;
391 if ( _iconName != caption ) {
392 _iconName = caption;
393
394 modified = true;
395 }
396 }
397
398 if (what == 50) {
399 emit profileChangeCommandReceived(caption);
400 return;
401 }
402
403 if ( modified ) {
404 emit titleChanged();
405 }
406}
407
408QString Session::userTitle() const
409{
410 return _userTitle;
411}
412void Session::setTabTitleFormat(TabTitleContext context , const QString & format)
413{
414 if ( context == LocalTabTitle ) {
415 _localTabTitleFormat = format;
416 } else if ( context == RemoteTabTitle ) {
417 _remoteTabTitleFormat = format;
418 }
419}
420QString Session::tabTitleFormat(TabTitleContext context) const
421{
422 if ( context == LocalTabTitle ) {
423 return _localTabTitleFormat;
424 } else if ( context == RemoteTabTitle ) {
425 return _remoteTabTitleFormat;
426 }
427
428 return QString();
429}
430
431void Session::monitorTimerDone()
432{
433 //FIXME: The idea here is that the notification popup will appear to tell the user than output from
434 //the terminal has stopped and the popup will disappear when the user activates the session.
435 //
436 //This breaks with the addition of multiple views of a session. The popup should disappear
437 //when any of the views of the session becomes active
438
439
440 //FIXME: Make message text for this notification and the activity notification more descriptive.
441 if (_monitorSilence) {
442 emit silence();
443 emit stateChanged(NOTIFYSILENCE);
444 } else {
445 emit stateChanged(NOTIFYNORMAL);
446 }
447
448 _notifiedActivity=false;
449}
450
451void Session::activityStateSet(int state)
452{
453 if (state==NOTIFYBELL) {
454 QString s;
455 s.asprintf("Bell in session '%s'",_nameTitle.toUtf8().data());
456
457 emit bellRequest( s );
458 } else if (state==NOTIFYACTIVITY) {
459 if (_monitorSilence) {
460 _monitorTimer->start(_silenceSeconds*1000);
461 }
462
463 if ( _monitorActivity ) {
464 //FIXME: See comments in Session::monitorTimerDone()
465 if (!_notifiedActivity) {
466 _notifiedActivity=true;
467 emit activity();
468 }
469 }
470 }
471
472 if ( state==NOTIFYACTIVITY && !_monitorActivity ) {
473 state = NOTIFYNORMAL;
474 }
475 if ( state==NOTIFYSILENCE && !_monitorSilence ) {
476 state = NOTIFYNORMAL;
477 }
478
479 emit stateChanged(state);
480}
481
482void Session::onViewSizeChange(int /*height*/, int /*width*/)
483{
484 updateTerminalSize();
485}
486void Session::onEmulationSizeChange(QSize size)
487{
488 setSize(size);
489}
490
491void Session::updateTerminalSize()
492{
493 QListIterator<TerminalDisplay *> viewIter(_views);
494
495 int minLines = -1;
496 int minColumns = -1;
497
498 // minimum number of lines and columns that views require for
499 // their size to be taken into consideration ( to avoid problems
500 // with new view widgets which haven't yet been set to their correct size )
501 const int VIEW_LINES_THRESHOLD = 2;
502 const int VIEW_COLUMNS_THRESHOLD = 2;
503
504 //select largest number of lines and columns that will fit in all visible views
505 while ( viewIter.hasNext() ) {
506 TerminalDisplay * view = viewIter.next();
507 if ( view->isHidden() == false &&
508 view->lines() >= VIEW_LINES_THRESHOLD &&
509 view->columns() >= VIEW_COLUMNS_THRESHOLD ) {
510 minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() );
511 minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() );
512 }
513 }
514
515 // backend emulation must have a _terminal of at least 1 column x 1 line in size
516 if ( minLines > 0 && minColumns > 0 ) {
517 _emulation->setImageSize( minLines , minColumns );
518 _shellProcess->setWindowSize( minLines , minColumns );
519 }
520}
521
522void Session::refresh()
523{
524 // attempt to get the shell process to redraw the display
525 //
526 // this requires the program running in the shell
527 // to cooperate by sending an update in response to
528 // a window size change
529 //
530 // the window size is changed twice, first made slightly larger and then
531 // resized back to its normal size so that there is actually a change
532 // in the window size (some shells do nothing if the
533 // new and old sizes are the same)
534 //
535 // if there is a more 'correct' way to do this, please
536 // send an email with method or patches to konsole-devel@kde.org
537
538 const QSize existingSize = _shellProcess->windowSize();
539 _shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1);
540 _shellProcess->setWindowSize(existingSize.height(),existingSize.width());
541}
542
543bool Session::sendSignal(int signal)
544{
545 int result = ::kill(_shellProcess->pid(),signal);
546
547 if ( result == 0 )
548 {
549 _shellProcess->waitForFinished();
550 return true;
551 }
552 else
553 return false;
554}
555
556void Session::close()
557{
558 _autoClose = true;
559 _wantedClose = true;
560 if (!_shellProcess->isRunning() || !sendSignal(SIGHUP)) {
561 // Forced close.
562 QTimer::singleShot(1, this, SIGNAL(finished()));
563 }
564}
565
566void Session::sendText(const QString & text) const
567{
568 _emulation->sendText(text);
569}
570
571Session::~Session()
572{
573 delete _emulation;
574 delete _shellProcess;
575// delete _zmodemProc;
576}
577
578void Session::setProfileKey(const QString & key)
579{
580 _profileKey = key;
581 emit profileChanged(key);
582}
583QString Session::profileKey() const
584{
585 return _profileKey;
586}
587
588void Session::done(int exitStatus)
589{
590 if (!_autoClose) {
591 _userTitle = QString::fromLatin1("This session is done. Finished");
592 emit titleChanged();
593 return;
594 }
595
596 QString message;
597 if (!_wantedClose || exitStatus != 0) {
598
599 if (_shellProcess->exitStatus() == QProcess::NormalExit) {
600 message.asprintf("Session '%s' exited with status %d.",
601 _nameTitle.toUtf8().data(), exitStatus);
602 } else {
603 message.asprintf("Session '%s' crashed.",
604 _nameTitle.toUtf8().data());
605 }
606 }
607
608 if ( !_wantedClose && _shellProcess->exitStatus() != QProcess::NormalExit )
609 message.asprintf("Session '%s' exited unexpectedly.",
610 _nameTitle.toUtf8().data());
611 else
612 emit finished();
613
614}
615
616Emulation * Session::emulation() const
617{
618 return _emulation;
619}
620
621QString Session::keyBindings() const
622{
623 return _emulation->keyBindings();
624}
625
626QStringList Session::environment() const
627{
628 return _environment;
629}
630
631void Session::setEnvironment(const QStringList & environment)
632{
633 _environment = environment;
634}
635
636int Session::sessionId() const
637{
638 return _sessionId;
639}
640
641void Session::setKeyBindings(const QString & id)
642{
643 _emulation->setKeyBindings(id);
644}
645
646void Session::setTitle(TitleRole role , const QString & newTitle)
647{
648 if ( title(role) != newTitle ) {
649 if ( role == NameRole ) {
650 _nameTitle = newTitle;
651 } else if ( role == DisplayedTitleRole ) {
652 _displayTitle = newTitle;
653 }
654
655 emit titleChanged();
656 }
657}
658
659QString Session::title(TitleRole role) const
660{
661 if ( role == NameRole ) {
662 return _nameTitle;
663 } else if ( role == DisplayedTitleRole ) {
664 return _displayTitle;
665 } else {
666 return QString();
667 }
668}
669
670void Session::setIconName(const QString & iconName)
671{
672 if ( iconName != _iconName ) {
673 _iconName = iconName;
674 emit titleChanged();
675 }
676}
677
678void Session::setIconText(const QString & iconText)
679{
680 _iconText = iconText;
681 //kDebug(1211)<<"Session setIconText " << _iconText;
682}
683
684QString Session::iconName() const
685{
686 return _iconName;
687}
688
689QString Session::iconText() const
690{
691 return _iconText;
692}
693
694bool Session::isTitleChanged() const
695{
696 return _isTitleChanged;
697}
698
699void Session::setHistoryType(const HistoryType & hType)
700{
701 _emulation->setHistory(hType);
702}
703
704const HistoryType & Session::historyType() const
705{
706 return _emulation->history();
707}
708
709void Session::clearHistory()
710{
711 _emulation->clearHistory();
712}
713
714QStringList Session::arguments() const
715{
716 return _arguments;
717}
718
719QString Session::program() const
720{
721 return _program;
722}
723
724// unused currently
725bool Session::isMonitorActivity() const
726{
727 return _monitorActivity;
728}
729// unused currently
730bool Session::isMonitorSilence() const
731{
732 return _monitorSilence;
733}
734
735void Session::setMonitorActivity(bool _monitor)
736{
737 _monitorActivity=_monitor;
738 _notifiedActivity=false;
739
740 activityStateSet(NOTIFYNORMAL);
741}
742
743void Session::setMonitorSilence(bool _monitor)
744{
745 if (_monitorSilence==_monitor) {
746 return;
747 }
748
749 _monitorSilence=_monitor;
750 if (_monitorSilence) {
751 _monitorTimer->start(_silenceSeconds*1000);
752 } else {
753 _monitorTimer->stop();
754 }
755
756 activityStateSet(NOTIFYNORMAL);
757}
758
759void Session::setMonitorSilenceSeconds(int seconds)
760{
761 _silenceSeconds=seconds;
762 if (_monitorSilence) {
763 _monitorTimer->start(_silenceSeconds*1000);
764 }
765}
766
767void Session::setAddToUtmp(bool set)
768{
769 _addToUtmp = set;
770}
771
772void Session::setFlowControlEnabled(bool enabled)
773{
774 if (_flowControl == enabled) {
775 return;
776 }
777
778 _flowControl = enabled;
779
780 if (_shellProcess) {
781 _shellProcess->setFlowControlEnabled(_flowControl);
782 }
783
784 emit flowControlEnabledChanged(enabled);
785}
786bool Session::flowControlEnabled() const
787{
788 return _flowControl;
789}
790//void Session::fireZModemDetected()
791//{
792// if (!_zmodemBusy)
793// {
794// QTimer::singleShot(10, this, SIGNAL(zmodemDetected()));
795// _zmodemBusy = true;
796// }
797//}
798
799//void Session::cancelZModem()
800//{
801// _shellProcess->sendData("\030\030\030\030", 4); // Abort
802// _zmodemBusy = false;
803//}
804
805//void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list)
806//{
807// _zmodemBusy = true;
808// _zmodemProc = new KProcess();
809// _zmodemProc->setOutputChannelMode( KProcess::SeparateChannels );
810//
811// *_zmodemProc << zmodem << "-v" << list;
812//
813// if (!dir.isEmpty())
814// _zmodemProc->setWorkingDirectory(dir);
815//
816// _zmodemProc->start();
817//
818// connect(_zmodemProc,SIGNAL (readyReadStandardOutput()),
819// this, SLOT(zmodemReadAndSendBlock()));
820// connect(_zmodemProc,SIGNAL (readyReadStandardError()),
821// this, SLOT(zmodemReadStatus()));
822// connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)),
823// this, SLOT(zmodemFinished()));
824//
825// disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
826// connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) );
827//
828// _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false,
829// i18n("ZModem Progress"));
830//
831// connect(_zmodemProgress, SIGNAL(user1Clicked()),
832// this, SLOT(zmodemDone()));
833//
834// _zmodemProgress->show();
835//}
836
837/*void Session::zmodemReadAndSendBlock()
838{
839 _zmodemProc->setReadChannel( QProcess::StandardOutput );
840 QByteArray data = _zmodemProc->readAll();
841
842 if ( data.count() == 0 )
843 return;
844
845 _shellProcess->sendData(data.constData(),data.count());
846}
847*/
848/*
849void Session::zmodemReadStatus()
850{
851 _zmodemProc->setReadChannel( QProcess::StandardError );
852 QByteArray msg = _zmodemProc->readAll();
853 while(!msg.isEmpty())
854 {
855 int i = msg.indexOf('\015');
856 int j = msg.indexOf('\012');
857 QByteArray txt;
858 if ((i != -1) && ((j == -1) || (i < j)))
859 {
860 msg = msg.mid(i+1);
861 }
862 else if (j != -1)
863 {
864 txt = msg.left(j);
865 msg = msg.mid(j+1);
866 }
867 else
868 {
869 txt = msg;
870 msg.truncate(0);
871 }
872 if (!txt.isEmpty())
873 _zmodemProgress->addProgressText(QString::fromLocal8Bit(txt));
874 }
875}
876*/
877/*
878void Session::zmodemRcvBlock(const char *data, int len)
879{
880 QByteArray ba( data, len );
881
882 _zmodemProc->write( ba );
883}
884*/
885/*
886void Session::zmodemFinished()
887{
888 if (_zmodemProc)
889 {
890 delete _zmodemProc;
891 _zmodemProc = 0;
892 _zmodemBusy = false;
893
894 disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) );
895 connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
896
897 _shellProcess->sendData("\030\030\030\030", 4); // Abort
898 _shellProcess->sendData("\001\013\n", 3); // Try to get prompt back
899 _zmodemProgress->transferDone();
900 }
901}
902*/
903void Session::onReceiveBlock( const char * buf, int len )
904{
905 _emulation->receiveData( buf, len );
906 emit receivedData( QString::fromLatin1( buf, len ) );
907}
908
909QSize Session::size()
910{
911 return _emulation->imageSize();
912}
913
914void Session::setSize(const QSize & size)
915{
916 if ((size.width() <= 1) || (size.height() <= 1)) {
917 return;
918 }
919
920 emit resizeRequest(size);
921}
922int Session::foregroundProcessId() const
923{
924 return _shellProcess->foregroundProcessGroup();
925}
926int Session::processId() const
927{
928 return _shellProcess->pid();
929}
930int Session::getPtySlaveFd() const
931{
932 return ptySlaveFd;
933}
934
935SessionGroup::SessionGroup()
936 : _masterMode(0)
937{
938}
939SessionGroup::~SessionGroup()
940{
941 // disconnect all
942 connectAll(false);
943}
944int SessionGroup::masterMode() const
945{
946 return _masterMode;
947}
948QList<Session *> SessionGroup::sessions() const
949{
950 return _sessions.keys();
951}
952bool SessionGroup::masterStatus(Session * session) const
953{
954 return _sessions[session];
955}
956
957void SessionGroup::addSession(Session * session)
958{
959 _sessions.insert(session,false);
960
961 QListIterator<Session *> masterIter(masters());
962
963 while ( masterIter.hasNext() ) {
964 connectPair(masterIter.next(),session);
965 }
966}
967void SessionGroup::removeSession(Session * session)
968{
969 setMasterStatus(session,false);
970
971 QListIterator<Session *> masterIter(masters());
972
973 while ( masterIter.hasNext() ) {
974 disconnectPair(masterIter.next(),session);
975 }
976
977 _sessions.remove(session);
978}
979void SessionGroup::setMasterMode(int mode)
980{
981 _masterMode = mode;
982
983 connectAll(false);
984 connectAll(true);
985}
986QList<Session *> SessionGroup::masters() const
987{
988 return _sessions.keys(true);
989}
990void SessionGroup::connectAll(bool connect)
991{
992 QListIterator<Session *> masterIter(masters());
993
994 while ( masterIter.hasNext() ) {
995 Session * master = masterIter.next();
996
997 QListIterator<Session *> otherIter(_sessions.keys());
998 while ( otherIter.hasNext() ) {
999 Session * other = otherIter.next();
1000
1001 if ( other != master ) {
1002 if ( connect ) {
1003 connectPair(master,other);
1004 } else {
1005 disconnectPair(master,other);
1006 }
1007 }
1008 }
1009 }
1010}
1011void SessionGroup::setMasterStatus(Session * session, bool master)
1012{
1013 bool wasMaster = _sessions[session];
1014 _sessions[session] = master;
1015
1016 if ((!wasMaster && !master)
1017 || (wasMaster && master)) {
1018 return;
1019 }
1020
1021 QListIterator<Session *> iter(_sessions.keys());
1022 while (iter.hasNext()) {
1023 Session * other = iter.next();
1024
1025 if (other != session) {
1026 if (master) {
1027 connectPair(session, other);
1028 } else {
1029 disconnectPair(session, other);
1030 }
1031 }
1032 }
1033}
1034
1035void SessionGroup::connectPair(Session * master , Session * other)
1036{
1037// qDebug() << k_funcinfo;
1038
1039 if ( _masterMode & CopyInputToAll ) {
1040 qDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle();
1041
1042 connect( master->emulation() , SIGNAL(sendData(const char *,int)) , other->emulation() ,
1043 SLOT(sendString(const char *,int)) );
1044 }
1045}
1046void SessionGroup::disconnectPair(Session * master , Session * other)
1047{
1048// qDebug() << k_funcinfo;
1049
1050 if ( _masterMode & CopyInputToAll ) {
1051 qDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle();
1052
1053 disconnect( master->emulation() , SIGNAL(sendData(const char *,int)) , other->emulation() ,
1054 SLOT(sendString(const char *,int)) );
1055 }
1056}
1057
1058//#include "moc_Session.cpp"
1059