You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1076 lines
28 KiB
1076 lines
28 KiB
/* |
|
This file is part of Konsole |
|
|
|
Copyright (C) 2006-2007 by Robert Knight <robertknight@gmail.com> |
|
Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301 USA. |
|
*/ |
|
|
|
// Own |
|
#include "Session.h" |
|
|
|
// Standard |
|
#include <assert.h> |
|
#include <stdlib.h> |
|
#include <signal.h> |
|
|
|
// Qt |
|
#include <QtGui/QApplication> |
|
#include <QtCore/QByteRef> |
|
#include <QtCore/QDir> |
|
#include <QtCore/QFile> |
|
#include <QtCore/QRegExp> |
|
#include <QtCore/QStringList> |
|
#include <QtDBus/QtDBus> |
|
#include <QtCore/QDate> |
|
|
|
// KDE |
|
#include <KDebug> |
|
#include <KLocale> |
|
#include <KMessageBox> |
|
#include <KNotification> |
|
#include <KProcess> |
|
#include <KRun> |
|
#include <kshell.h> |
|
#include <KStandardDirs> |
|
|
|
// Konsole |
|
#include <config-konsole.h> |
|
#include <sessionadaptor.h> |
|
|
|
#include "Pty.h" |
|
#include "TerminalDisplay.h" |
|
#include "ShellCommand.h" |
|
#include "Vt102Emulation.h" |
|
#include "ZModemDialog.h" |
|
|
|
using namespace Konsole; |
|
|
|
int Session::lastSessionId = 0; |
|
|
|
Session::Session() : |
|
_shellProcess(0) |
|
, _emulation(0) |
|
, _monitorActivity(false) |
|
, _monitorSilence(false) |
|
, _notifiedActivity(false) |
|
, _autoClose(true) |
|
, _wantedClose(false) |
|
, _silenceSeconds(10) |
|
, _addToUtmp(false) // disabled by default because of a bug encountered on certain systems |
|
// which caused Konsole to hang when closing a tab and then opening a new |
|
// one. A 'QProcess destroyed while still running' warning was being |
|
// printed to the terminal. Likely a problem in KPty::logout() |
|
// or KPty::login() which uses a QProcess to start /usr/bin/utempter |
|
, _flowControl(true) |
|
, _fullScripting(false) |
|
, _sessionId(0) |
|
, _zmodemBusy(false) |
|
, _zmodemProc(0) |
|
, _zmodemProgress(0) |
|
, _hasDarkBackground(false) |
|
{ |
|
//prepare DBus communication |
|
new SessionAdaptor(this); |
|
_sessionId = ++lastSessionId; |
|
QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this); |
|
|
|
//create teletype for I/O with shell process |
|
_shellProcess = new Pty(); |
|
|
|
//create emulation backend |
|
_emulation = new Vt102Emulation(); |
|
|
|
connect( _emulation, SIGNAL( titleChanged( int, const QString & ) ), |
|
this, SLOT( setUserTitle( int, const QString & ) ) ); |
|
connect( _emulation, SIGNAL( stateSet(int) ), |
|
this, SLOT( activityStateSet(int) ) ); |
|
connect( _emulation, SIGNAL( zmodemDetected() ), this , |
|
SLOT( fireZModemDetected() ) ); |
|
connect( _emulation, SIGNAL( changeTabTextColorRequest( int ) ), |
|
this, SIGNAL( changeTabTextColorRequest( int ) ) ); |
|
connect( _emulation, SIGNAL(profileChangeCommandReceived(const QString&)), |
|
this, SIGNAL( profileChangeCommandReceived(const QString&)) ); |
|
// TODO |
|
// connect( _emulation,SIGNAL(imageSizeChanged(int,int)) , this , |
|
// SLOT(onEmulationSizeChange(int,int)) ); |
|
|
|
//connect teletype to emulation backend |
|
_shellProcess->setUtf8Mode(_emulation->utf8()); |
|
|
|
connect( _shellProcess,SIGNAL(receivedData(const char*,int)),this, |
|
SLOT(onReceiveBlock(const char*,int)) ); |
|
connect( _emulation,SIGNAL(sendData(const char*,int)),_shellProcess, |
|
SLOT(sendData(const char*,int)) ); |
|
connect( _emulation,SIGNAL(lockPtyRequest(bool)),_shellProcess,SLOT(lockPty(bool)) ); |
|
connect( _emulation,SIGNAL(useUtf8Request(bool)),_shellProcess,SLOT(setUtf8Mode(bool)) ); |
|
|
|
|
|
connect( _shellProcess,SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(done(int)) ); |
|
|
|
//setup timer for monitoring session activity |
|
_monitorTimer = new QTimer(this); |
|
_monitorTimer->setSingleShot(true); |
|
connect(_monitorTimer, SIGNAL(timeout()), this, SLOT(monitorTimerDone())); |
|
} |
|
|
|
WId Session::windowId() const |
|
{ |
|
// Returns a window ID for this session which is used |
|
// to set the WINDOWID environment variable in the shell |
|
// process. |
|
// |
|
// Sessions can have multiple views or no views, which means |
|
// that a single ID is not always going to be accurate. |
|
// |
|
// If there are no views, the window ID is just 0. If |
|
// there are multiple views, then the window ID for the |
|
// top-level window which contains the first view is |
|
// returned |
|
|
|
if ( _views.count() == 0 ) |
|
return 0; |
|
else |
|
{ |
|
QWidget* window = _views.first(); |
|
|
|
Q_ASSERT( window ); |
|
|
|
while ( window->parentWidget() != 0 ) |
|
window = window->parentWidget(); |
|
|
|
return window->winId(); |
|
} |
|
} |
|
|
|
void Session::setDarkBackground(bool darkBackground) |
|
{ |
|
_hasDarkBackground = darkBackground; |
|
} |
|
bool Session::hasDarkBackground() const |
|
{ |
|
return _hasDarkBackground; |
|
} |
|
bool Session::isRunning() const |
|
{ |
|
return _shellProcess->state() == QProcess::Running; |
|
} |
|
|
|
void Session::setCodec(QTextCodec* codec) |
|
{ |
|
emulation()->setCodec(codec); |
|
} |
|
|
|
void Session::setProgram(const QString& program) |
|
{ |
|
_program = ShellCommand::expand(program); |
|
} |
|
void Session::setInitialWorkingDirectory(const QString& dir) |
|
{ |
|
_initialWorkingDir = ShellCommand::expand(dir); |
|
} |
|
void Session::setArguments(const QStringList& arguments) |
|
{ |
|
_arguments = ShellCommand::expand(arguments); |
|
} |
|
|
|
QList<TerminalDisplay*> Session::views() const |
|
{ |
|
return _views; |
|
} |
|
|
|
void Session::addView(TerminalDisplay* widget) |
|
{ |
|
Q_ASSERT( !_views.contains(widget) ); |
|
|
|
_views.append(widget); |
|
|
|
if ( _emulation != 0 ) |
|
{ |
|
// connect emulation - view signals and slots |
|
connect( widget , SIGNAL(keyPressedSignal(QKeyEvent*)) , _emulation , |
|
SLOT(sendKeyEvent(QKeyEvent*)) ); |
|
connect( widget , SIGNAL(mouseSignal(int,int,int,int)) , _emulation , |
|
SLOT(sendMouseEvent(int,int,int,int)) ); |
|
connect( widget , SIGNAL(sendStringToEmu(const char*)) , _emulation , |
|
SLOT(sendString(const char*)) ); |
|
|
|
// allow emulation to notify view when the foreground process |
|
// indicates whether or not it is interested in mouse signals |
|
connect( _emulation , SIGNAL(programUsesMouseChanged(bool)) , widget , |
|
SLOT(setUsesMouse(bool)) ); |
|
|
|
widget->setUsesMouse( _emulation->programUsesMouse() ); |
|
|
|
widget->setScreenWindow(_emulation->createWindow()); |
|
} |
|
|
|
//connect view signals and slots |
|
QObject::connect( widget ,SIGNAL(changedContentSizeSignal(int,int)),this, |
|
SLOT(onViewSizeChange(int,int))); |
|
|
|
QObject::connect( widget ,SIGNAL(destroyed(QObject*)) , this , |
|
SLOT(viewDestroyed(QObject*)) ); |
|
} |
|
|
|
void Session::viewDestroyed(QObject* view) |
|
{ |
|
TerminalDisplay* display = (TerminalDisplay*)view; |
|
|
|
Q_ASSERT( _views.contains(display) ); |
|
|
|
removeView(display); |
|
} |
|
|
|
void Session::removeView(TerminalDisplay* widget) |
|
{ |
|
_views.removeAll(widget); |
|
|
|
disconnect(widget,0,this,0); |
|
|
|
if ( _emulation != 0 ) |
|
{ |
|
// disconnect |
|
// - key presses signals from widget |
|
// - mouse activity signals from widget |
|
// - string sending signals from widget |
|
// |
|
// ... and any other signals connected in addView() |
|
disconnect( widget, 0, _emulation, 0); |
|
|
|
// disconnect state change signals emitted by emulation |
|
disconnect( _emulation , 0 , widget , 0); |
|
} |
|
|
|
// close the session automatically when the last view is removed |
|
if ( _views.count() == 0 ) |
|
{ |
|
close(); |
|
} |
|
} |
|
|
|
QString Session::checkProgram(const QString& program) const |
|
{ |
|
// Upon a KPty error, there is no description on what that error was... |
|
// Check to see if the given program is executable. |
|
QString exec = QFile::encodeName(program); |
|
|
|
if (exec.isEmpty()) |
|
return QString(); |
|
|
|
// if 'exec' is not specified, fall back to default shell. if that |
|
// is not set then fall back to /bin/sh |
|
if ( exec.isEmpty() ) |
|
exec = getenv("SHELL"); |
|
if ( exec.isEmpty() ) |
|
exec = "/bin/sh"; |
|
|
|
exec = KRun::binaryName(exec, false); |
|
exec = KShell::tildeExpand(exec); |
|
QString pexec = KGlobal::dirs()->findExe(exec); |
|
if ( pexec.isEmpty() ) |
|
{ |
|
kError() << i18n("Could not find binary: ") << exec; |
|
return QString(); |
|
} |
|
|
|
return exec; |
|
} |
|
|
|
void Session::terminalWarning(const QString& message) |
|
{ |
|
static const QByteArray warningText = i18n("Warning: ").toLocal8Bit(); |
|
QByteArray messageText = message.toLocal8Bit(); |
|
|
|
static const char* redPenOn = "\E[1m\E[31m"; |
|
static const char* redPenOff = "\E[0m"; |
|
|
|
_emulation->receiveData(redPenOn,strlen(redPenOn)); |
|
_emulation->receiveData("\n\r\n\r",4); |
|
_emulation->receiveData(warningText.constData(),strlen(warningText.constData())); |
|
_emulation->receiveData(messageText.constData(),strlen(messageText.constData())); |
|
_emulation->receiveData("\n\r\n\r",4); |
|
_emulation->receiveData(redPenOff,strlen(redPenOff)); |
|
} |
|
void Session::run() |
|
{ |
|
//check that everything is in place to run the session |
|
if (_program.isEmpty()) |
|
kDebug() << "Session::run() - program to run not set."; |
|
if (_arguments.isEmpty()) |
|
kDebug() << "Session::run() - no command line arguments specified."; |
|
|
|
const int CHOICE_COUNT = 3; |
|
QString programs[CHOICE_COUNT] = {_program,getenv("SHELL"),"/bin/sh"}; |
|
QString exec; |
|
int choice = 0; |
|
while (choice < CHOICE_COUNT) |
|
{ |
|
exec = checkProgram(programs[choice]); |
|
if (exec.isEmpty()) |
|
choice++; |
|
else |
|
break; |
|
} |
|
|
|
// if a program was specified via setProgram(), but it couldn't be found, print a warning |
|
if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) |
|
{ |
|
terminalWarning(i18n("Could not find '%1', starting '%2' instead. Please check your profile settings.",_program,exec)); |
|
} |
|
// if none of the choices are available, print a warning |
|
else if (choice == CHOICE_COUNT) |
|
{ |
|
terminalWarning(i18n("Could not find an interactive shell to start.")); |
|
return; |
|
} |
|
|
|
// if no arguments are specified, fall back to program name |
|
QStringList arguments = _arguments.join(QChar(' ')).isEmpty() ? |
|
QStringList() << exec : _arguments; |
|
|
|
QString dbusService = QDBusConnection::sessionBus().baseService(); |
|
QString cwd_save = QDir::currentPath(); |
|
if (!_initialWorkingDir.isEmpty()) |
|
_shellProcess->setWorkingDirectory(_initialWorkingDir); |
|
else |
|
_shellProcess->setWorkingDirectory(QDir::homePath()); |
|
|
|
_shellProcess->setXonXoff(_flowControl); |
|
_shellProcess->setErase(_emulation->getErase()); |
|
|
|
// this is not strictly accurate use of the COLORFGBG variable. This does not |
|
// tell the terminal exactly which colors are being used, but instead approximates |
|
// the color scheme as "black on white" or "white on black" depending on whether |
|
// the background color is deemed dark or not |
|
QString backgroundColorHint = _hasDarkBackground ? "COLORFGBG=15;0" : "COLORFGBG=0;15"; |
|
|
|
int result = _shellProcess->start(exec, |
|
arguments, |
|
_environment << backgroundColorHint, |
|
windowId(), |
|
_addToUtmp, |
|
dbusService, |
|
(QLatin1String("/Sessions/") + |
|
QString::number(_sessionId))); |
|
|
|
if (result < 0) |
|
{ |
|
terminalWarning(i18n("Could not start program '%1' with arguments '%2'.")); |
|
return; |
|
} |
|
|
|
_shellProcess->setWriteable(false); // We are reachable via kwrited. |
|
|
|
emit started(); |
|
} |
|
|
|
void Session::setUserTitle( int what, const QString &caption ) |
|
{ |
|
//set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) |
|
bool modified = false; |
|
|
|
if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) |
|
{ |
|
if ( _userTitle != caption ) { |
|
_userTitle = caption; |
|
modified = true; |
|
} |
|
} |
|
|
|
if ((what == IconNameAndWindowTitle) || (what == IconName)) |
|
{ |
|
if ( _iconText != caption ) { |
|
_iconText = caption; |
|
modified = true; |
|
} |
|
} |
|
|
|
if (what == TextColor || what == BackgroundColor) |
|
{ |
|
QString colorString = caption.section(';',0,0); |
|
QColor color = QColor(colorString); |
|
if (color.isValid()) |
|
{ |
|
if (what == TextColor) |
|
emit changeForegroundColorRequest(color); |
|
else |
|
emit changeBackgroundColorRequest(color); |
|
} |
|
} |
|
|
|
if (what == SessionName) |
|
{ |
|
if ( _nameTitle != caption ) { |
|
setTitle(Session::NameRole,caption); |
|
return; |
|
} |
|
} |
|
|
|
if (what == 31) |
|
{ |
|
QString cwd=caption; |
|
cwd=cwd.replace( QRegExp("^~"), QDir::homePath() ); |
|
emit openUrlRequest(cwd); |
|
} |
|
|
|
// change icon via \033]32;Icon\007 |
|
if (what == 32) |
|
{ |
|
if ( _iconName != caption ) { |
|
_iconName = caption; |
|
|
|
modified = true; |
|
} |
|
} |
|
|
|
if (what == ProfileChange) |
|
{ |
|
emit profileChangeCommandReceived(caption); |
|
return; |
|
} |
|
|
|
if ( modified ) |
|
emit titleChanged(); |
|
} |
|
|
|
QString Session::userTitle() const |
|
{ |
|
return _userTitle; |
|
} |
|
void Session::setTabTitleFormat(TabTitleContext context , const QString& format) |
|
{ |
|
if ( context == LocalTabTitle ) |
|
_localTabTitleFormat = format; |
|
else if ( context == RemoteTabTitle ) |
|
_remoteTabTitleFormat = format; |
|
} |
|
QString Session::tabTitleFormat(TabTitleContext context) const |
|
{ |
|
if ( context == LocalTabTitle ) |
|
return _localTabTitleFormat; |
|
else if ( context == RemoteTabTitle ) |
|
return _remoteTabTitleFormat; |
|
|
|
return QString(); |
|
} |
|
|
|
void Session::monitorTimerDone() |
|
{ |
|
//FIXME: The idea here is that the notification popup will appear to tell the user than output from |
|
//the terminal has stopped and the popup will disappear when the user activates the session. |
|
// |
|
//This breaks with the addition of multiple views of a session. The popup should disappear |
|
//when any of the views of the session becomes active |
|
|
|
|
|
//FIXME: Make message text for this notification and the activity notification more descriptive. |
|
if (_monitorSilence) { |
|
KNotification::event("Silence", i18n("Silence in session '%1'", _nameTitle), QPixmap(), |
|
QApplication::activeWindow(), |
|
KNotification::CloseWhenWidgetActivated); |
|
emit stateChanged(NOTIFYSILENCE); |
|
} |
|
else |
|
{ |
|
emit stateChanged(NOTIFYNORMAL); |
|
} |
|
|
|
_notifiedActivity=false; |
|
} |
|
|
|
void Session::activityStateSet(int state) |
|
{ |
|
if (state==NOTIFYBELL) |
|
{ |
|
emit bellRequest( i18n("Bell in session '%1'",_nameTitle) ); |
|
} |
|
else if (state==NOTIFYACTIVITY) |
|
{ |
|
if (_monitorSilence) { |
|
_monitorTimer->start(_silenceSeconds*1000); |
|
} |
|
|
|
if ( _monitorActivity ) { |
|
//FIXME: See comments in Session::monitorTimerDone() |
|
if (!_notifiedActivity) { |
|
KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(), |
|
QApplication::activeWindow(), |
|
KNotification::CloseWhenWidgetActivated); |
|
_notifiedActivity=true; |
|
} |
|
} |
|
} |
|
|
|
if ( state==NOTIFYACTIVITY && !_monitorActivity ) |
|
state = NOTIFYNORMAL; |
|
if ( state==NOTIFYSILENCE && !_monitorSilence ) |
|
state = NOTIFYNORMAL; |
|
|
|
emit stateChanged(state); |
|
} |
|
|
|
void Session::onViewSizeChange(int /*height*/, int /*width*/) |
|
{ |
|
updateTerminalSize(); |
|
} |
|
void Session::onEmulationSizeChange(int lines , int columns) |
|
{ |
|
setSize( QSize(lines,columns) ); |
|
} |
|
|
|
void Session::updateTerminalSize() |
|
{ |
|
QListIterator<TerminalDisplay*> viewIter(_views); |
|
|
|
int minLines = -1; |
|
int minColumns = -1; |
|
|
|
// minimum number of lines and columns that views require for |
|
// their size to be taken into consideration ( to avoid problems |
|
// with new view widgets which haven't yet been set to their correct size ) |
|
const int VIEW_LINES_THRESHOLD = 2; |
|
const int VIEW_COLUMNS_THRESHOLD = 2; |
|
|
|
//select largest number of lines and columns that will fit in all visible views |
|
while ( viewIter.hasNext() ) |
|
{ |
|
TerminalDisplay* view = viewIter.next(); |
|
if ( view->isHidden() == false && |
|
view->lines() >= VIEW_LINES_THRESHOLD && |
|
view->columns() >= VIEW_COLUMNS_THRESHOLD ) |
|
{ |
|
minLines = (minLines == -1) ? view->lines() : qMin( minLines , view->lines() ); |
|
minColumns = (minColumns == -1) ? view->columns() : qMin( minColumns , view->columns() ); |
|
} |
|
} |
|
|
|
// backend emulation must have a _terminal of at least 1 column x 1 line in size |
|
if ( minLines > 0 && minColumns > 0 ) |
|
{ |
|
_emulation->setImageSize( minLines , minColumns ); |
|
_shellProcess->setWindowSize( minLines , minColumns ); |
|
} |
|
} |
|
|
|
void Session::refresh() |
|
{ |
|
// attempt to get the shell process to redraw the display |
|
// |
|
// this requires the program running in the shell |
|
// to cooperate by sending an update in response to |
|
// a window size change |
|
// |
|
// the window size is changed twice, first made slightly larger and then |
|
// resized back to its normal size so that there is actually a change |
|
// in the window size (some shells do nothing if the |
|
// new and old sizes are the same) |
|
// |
|
// if there is a more 'correct' way to do this, please |
|
// send an email with method or patches to konsole-devel@kde.org |
|
|
|
const QSize existingSize = _shellProcess->windowSize(); |
|
_shellProcess->setWindowSize(existingSize.height(),existingSize.width()+1); |
|
_shellProcess->setWindowSize(existingSize.height(),existingSize.width()); |
|
} |
|
|
|
bool Session::kill(int signal) |
|
{ |
|
int result = ::kill(_shellProcess->pid(),signal); |
|
|
|
if ( result == 0 ) |
|
{ |
|
_shellProcess->waitForFinished(); |
|
return true; |
|
} |
|
else |
|
return false; |
|
} |
|
|
|
void Session::close() |
|
{ |
|
_autoClose = true; |
|
_wantedClose = true; |
|
if (!isRunning() || !kill(SIGHUP)) |
|
{ |
|
// Forced close. |
|
QTimer::singleShot(1, this, SIGNAL(finished())); |
|
} |
|
} |
|
|
|
void Session::sendText(const QString &text) const |
|
{ |
|
_emulation->sendText(text); |
|
} |
|
|
|
Session::~Session() |
|
{ |
|
delete _emulation; |
|
delete _shellProcess; |
|
delete _zmodemProc; |
|
} |
|
|
|
void Session::setProfileKey(const QString& key) |
|
{ |
|
_profileKey = key; |
|
emit profileChanged(key); |
|
} |
|
QString Session::profileKey() const { return _profileKey; } |
|
|
|
void Session::done(int exitStatus) |
|
{ |
|
if (!_autoClose) |
|
{ |
|
_userTitle = i18n("Finished"); |
|
emit titleChanged(); |
|
return; |
|
} |
|
|
|
QString message; |
|
if (!_wantedClose || exitStatus != 0) |
|
{ |
|
if (_shellProcess->exitStatus() == QProcess::NormalExit) |
|
message = i18n("Program '%1' exited with status %2.", _shellProcess->program().first(), exitStatus); |
|
else |
|
message = i18n("Program '%1' crashed.", _shellProcess->program().first()); |
|
|
|
//FIXME: See comments in Session::monitorTimerDone() |
|
KNotification::event("Finished", message , QPixmap(), |
|
QApplication::activeWindow(), |
|
KNotification::CloseWhenWidgetActivated); |
|
} |
|
|
|
if ( exitStatus != 0 || _shellProcess->exitStatus() != QProcess::NormalExit ) |
|
terminalWarning(message); |
|
else |
|
emit finished(); |
|
} |
|
|
|
Emulation* Session::emulation() const |
|
{ |
|
return _emulation; |
|
} |
|
|
|
QString Session::keyBindings() const |
|
{ |
|
return _emulation->keyBindings(); |
|
} |
|
|
|
QStringList Session::environment() const |
|
{ |
|
return _environment; |
|
} |
|
|
|
void Session::setEnvironment(const QStringList& environment) |
|
{ |
|
_environment = environment; |
|
} |
|
|
|
int Session::sessionId() const |
|
{ |
|
return _sessionId; |
|
} |
|
|
|
void Session::setKeyBindings(const QString &id) |
|
{ |
|
_emulation->setKeyBindings(id); |
|
} |
|
|
|
void Session::setTitle(TitleRole role , const QString& newTitle) |
|
{ |
|
if ( title(role) != newTitle ) |
|
{ |
|
if ( role == NameRole ) |
|
_nameTitle = newTitle; |
|
else if ( role == DisplayedTitleRole ) |
|
_displayTitle = newTitle; |
|
|
|
emit titleChanged(); |
|
} |
|
} |
|
|
|
QString Session::title(TitleRole role) const |
|
{ |
|
if ( role == NameRole ) |
|
return _nameTitle; |
|
else if ( role == DisplayedTitleRole ) |
|
return _displayTitle; |
|
else |
|
return QString(); |
|
} |
|
|
|
void Session::setIconName(const QString& iconName) |
|
{ |
|
if ( iconName != _iconName ) |
|
{ |
|
_iconName = iconName; |
|
emit titleChanged(); |
|
} |
|
} |
|
|
|
void Session::setIconText(const QString& iconText) |
|
{ |
|
_iconText = iconText; |
|
//kDebug(1211)<<"Session setIconText " << _iconText; |
|
} |
|
|
|
QString Session::iconName() const |
|
{ |
|
return _iconName; |
|
} |
|
|
|
QString Session::iconText() const |
|
{ |
|
return _iconText; |
|
} |
|
|
|
void Session::setHistoryType(const HistoryType &hType) |
|
{ |
|
_emulation->setHistory(hType); |
|
} |
|
|
|
const HistoryType& Session::historyType() const |
|
{ |
|
return _emulation->history(); |
|
} |
|
|
|
void Session::clearHistory() |
|
{ |
|
_emulation->clearHistory(); |
|
} |
|
|
|
QStringList Session::arguments() const |
|
{ |
|
return _arguments; |
|
} |
|
|
|
QString Session::program() const |
|
{ |
|
return _program; |
|
} |
|
|
|
// unused currently |
|
bool Session::isMonitorActivity() const { return _monitorActivity; } |
|
// unused currently |
|
bool Session::isMonitorSilence() const { return _monitorSilence; } |
|
|
|
void Session::setMonitorActivity(bool _monitor) |
|
{ |
|
_monitorActivity=_monitor; |
|
_notifiedActivity=false; |
|
|
|
activityStateSet(NOTIFYNORMAL); |
|
} |
|
|
|
void Session::setMonitorSilence(bool _monitor) |
|
{ |
|
if (_monitorSilence==_monitor) |
|
return; |
|
|
|
_monitorSilence=_monitor; |
|
if (_monitorSilence) |
|
{ |
|
_monitorTimer->start(_silenceSeconds*1000); |
|
} |
|
else |
|
_monitorTimer->stop(); |
|
|
|
activityStateSet(NOTIFYNORMAL); |
|
} |
|
|
|
void Session::setMonitorSilenceSeconds(int seconds) |
|
{ |
|
_silenceSeconds=seconds; |
|
if (_monitorSilence) { |
|
_monitorTimer->start(_silenceSeconds*1000); |
|
} |
|
} |
|
|
|
void Session::setAddToUtmp(bool set) |
|
{ |
|
_addToUtmp = set; |
|
} |
|
|
|
void Session::setFlowControlEnabled(bool enabled) |
|
{ |
|
if (_flowControl == enabled) |
|
return; |
|
|
|
_flowControl = enabled; |
|
|
|
if (_shellProcess) |
|
_shellProcess->setXonXoff(_flowControl); |
|
|
|
emit flowControlEnabledChanged(enabled); |
|
} |
|
bool Session::flowControlEnabled() const |
|
{ |
|
return _flowControl; |
|
} |
|
void Session::fireZModemDetected() |
|
{ |
|
if (!_zmodemBusy) |
|
{ |
|
QTimer::singleShot(10, this, SIGNAL(zmodemDetected())); |
|
_zmodemBusy = true; |
|
} |
|
} |
|
|
|
void Session::cancelZModem() |
|
{ |
|
_shellProcess->sendData("\030\030\030\030", 4); // Abort |
|
_zmodemBusy = false; |
|
} |
|
|
|
void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list) |
|
{ |
|
_zmodemBusy = true; |
|
_zmodemProc = new KProcess(); |
|
_zmodemProc->setOutputChannelMode( KProcess::SeparateChannels ); |
|
|
|
*_zmodemProc << zmodem << "-v" << list; |
|
|
|
if (!dir.isEmpty()) |
|
_zmodemProc->setWorkingDirectory(dir); |
|
|
|
_zmodemProc->start(); |
|
|
|
connect(_zmodemProc,SIGNAL (readyReadStandardOutput()), |
|
this, SLOT(zmodemReadAndSendBlock())); |
|
connect(_zmodemProc,SIGNAL (readyReadStandardError()), |
|
this, SLOT(zmodemReadStatus())); |
|
connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)), |
|
this, SLOT(zmodemFinished())); |
|
|
|
disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) ); |
|
connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) ); |
|
|
|
_zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, |
|
i18n("ZModem Progress")); |
|
|
|
connect(_zmodemProgress, SIGNAL(user1Clicked()), |
|
this, SLOT(zmodemDone())); |
|
|
|
_zmodemProgress->show(); |
|
} |
|
|
|
void Session::zmodemReadAndSendBlock() |
|
{ |
|
_zmodemProc->setReadChannel( QProcess::StandardOutput ); |
|
QByteArray data = _zmodemProc->readAll(); |
|
|
|
if ( data.count() == 0 ) |
|
return; |
|
|
|
_shellProcess->sendData(data.constData(),data.count()); |
|
} |
|
|
|
void Session::zmodemReadStatus() |
|
{ |
|
_zmodemProc->setReadChannel( QProcess::StandardError ); |
|
QByteArray msg = _zmodemProc->readAll(); |
|
while(!msg.isEmpty()) |
|
{ |
|
int i = msg.indexOf('\015'); |
|
int j = msg.indexOf('\012'); |
|
QByteArray txt; |
|
if ((i != -1) && ((j == -1) || (i < j))) |
|
{ |
|
msg = msg.mid(i+1); |
|
} |
|
else if (j != -1) |
|
{ |
|
txt = msg.left(j); |
|
msg = msg.mid(j+1); |
|
} |
|
else |
|
{ |
|
txt = msg; |
|
msg.truncate(0); |
|
} |
|
if (!txt.isEmpty()) |
|
_zmodemProgress->addProgressText(QString::fromLocal8Bit(txt)); |
|
} |
|
} |
|
|
|
void Session::zmodemRcvBlock(const char *data, int len) |
|
{ |
|
QByteArray ba( data, len ); |
|
|
|
_zmodemProc->write( ba ); |
|
} |
|
|
|
void Session::zmodemFinished() |
|
{ |
|
if (_zmodemProc) |
|
{ |
|
delete _zmodemProc; |
|
_zmodemProc = 0; |
|
_zmodemBusy = false; |
|
|
|
disconnect( _shellProcess,SIGNAL(block_in(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) ); |
|
connect( _shellProcess,SIGNAL(block_in(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) ); |
|
|
|
_shellProcess->sendData("\030\030\030\030", 4); // Abort |
|
_shellProcess->sendData("\001\013\n", 3); // Try to get prompt back |
|
_zmodemProgress->transferDone(); |
|
} |
|
} |
|
|
|
void Session::onReceiveBlock( const char* buf, int len ) |
|
{ |
|
_emulation->receiveData( buf, len ); |
|
emit receivedData( QString::fromLatin1( buf, len ) ); |
|
} |
|
|
|
QSize Session::size() |
|
{ |
|
return _emulation->imageSize(); |
|
} |
|
|
|
void Session::setSize(const QSize& size) |
|
{ |
|
if ((size.width() <= 1) || (size.height() <= 1)) |
|
return; |
|
|
|
emit resizeRequest(size); |
|
} |
|
int Session::foregroundProcessId() const |
|
{ |
|
return _shellProcess->foregroundProcessGroup(); |
|
} |
|
int Session::processId() const |
|
{ |
|
return _shellProcess->pid(); |
|
} |
|
|
|
SessionGroup::SessionGroup() |
|
: _masterMode(0) |
|
{ |
|
} |
|
SessionGroup::~SessionGroup() |
|
{ |
|
// disconnect all |
|
connectAll(false); |
|
} |
|
int SessionGroup::masterMode() const { return _masterMode; } |
|
QList<Session*> SessionGroup::sessions() const { return _sessions.keys(); } |
|
bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; } |
|
|
|
void SessionGroup::addSession(Session* session) |
|
{ |
|
_sessions.insert(session,false); |
|
|
|
QListIterator<Session*> masterIter(masters()); |
|
|
|
while ( masterIter.hasNext() ) |
|
connectPair(masterIter.next(),session); |
|
} |
|
void SessionGroup::removeSession(Session* session) |
|
{ |
|
setMasterStatus(session,false); |
|
|
|
QListIterator<Session*> masterIter(masters()); |
|
|
|
while ( masterIter.hasNext() ) |
|
disconnectPair(masterIter.next(),session); |
|
|
|
_sessions.remove(session); |
|
} |
|
void SessionGroup::setMasterMode(int mode) |
|
{ |
|
_masterMode = mode; |
|
|
|
connectAll(false); |
|
connectAll(true); |
|
} |
|
QList<Session*> SessionGroup::masters() const |
|
{ |
|
return _sessions.keys(true); |
|
} |
|
void SessionGroup::connectAll(bool connect) |
|
{ |
|
QListIterator<Session*> masterIter(masters()); |
|
|
|
while ( masterIter.hasNext() ) |
|
{ |
|
Session* master = masterIter.next(); |
|
|
|
QListIterator<Session*> otherIter(_sessions.keys()); |
|
while ( otherIter.hasNext() ) |
|
{ |
|
Session* other = otherIter.next(); |
|
|
|
if ( other != master ) |
|
{ |
|
if ( connect ) |
|
connectPair(master,other); |
|
else |
|
disconnectPair(master,other); |
|
} |
|
} |
|
} |
|
} |
|
void SessionGroup::setMasterStatus(Session* session , bool master) |
|
{ |
|
bool wasMaster = _sessions[session]; |
|
_sessions[session] = master; |
|
|
|
if ( !wasMaster && !master |
|
|| wasMaster && master ) |
|
return; |
|
|
|
QListIterator<Session*> iter(_sessions.keys()); |
|
while ( iter.hasNext() ) |
|
{ |
|
Session* other = iter.next(); |
|
|
|
if ( other != session ) |
|
{ |
|
if ( master ) |
|
connectPair(session,other); |
|
else |
|
disconnectPair(session,other); |
|
} |
|
} |
|
} |
|
void SessionGroup::connectPair(Session* master , Session* other) |
|
{ |
|
kDebug() << k_funcinfo; |
|
|
|
if ( _masterMode & CopyInputToAll ) |
|
{ |
|
kDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle(); |
|
|
|
connect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() , |
|
SLOT(sendString(const char*,int)) ); |
|
} |
|
} |
|
void SessionGroup::disconnectPair(Session* master , Session* other) |
|
{ |
|
kDebug() << k_funcinfo; |
|
|
|
if ( _masterMode & CopyInputToAll ) |
|
{ |
|
kDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle(); |
|
|
|
disconnect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() , |
|
SLOT(sendString(const char*,int)) ); |
|
} |
|
} |
|
|
|
#include "Session.moc"
|
|
|