mirror of
https://github.com/KDE/konsole.git
synced 2026-05-04 20:55:56 -04:00
1506 lines
40 KiB
C++
1506 lines
40 KiB
C++
/*
|
|
This file is part of Konsole
|
|
|
|
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
|
|
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
|
|
Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.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 <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 <KRun>
|
|
#include <KShell>
|
|
#include <KProcess>
|
|
#include <KStandardDirs>
|
|
#include <KPtyDevice>
|
|
#include <KConfigGroup>
|
|
|
|
// Konsole
|
|
#include <config-konsole.h>
|
|
#include <sessionadaptor.h>
|
|
|
|
#include "ProcessInfo.h"
|
|
#include "Pty.h"
|
|
#include "TerminalDisplay.h"
|
|
#include "ShellCommand.h"
|
|
#include "Vt102Emulation.h"
|
|
#include "ZModemDialog.h"
|
|
#include "History.h"
|
|
|
|
using namespace Konsole;
|
|
|
|
int Session::lastSessionId = 0;
|
|
|
|
// HACK This is copied out of QUuid::createUuid with reseeding forced.
|
|
// Required because color schemes repeatedly seed the RNG...
|
|
// ...with a constant.
|
|
QUuid createUuid()
|
|
{
|
|
static const int intbits = sizeof(int)*8;
|
|
static int randbits = 0;
|
|
if (!randbits)
|
|
{
|
|
int max = RAND_MAX;
|
|
do { ++randbits; } while ((max=max>>1));
|
|
}
|
|
|
|
qsrand(uint(QDateTime::currentDateTime().toTime_t()));
|
|
qrand(); // Skip first
|
|
|
|
QUuid result;
|
|
uint* data = &(result.data1);
|
|
int chunks = 16 / sizeof(uint);
|
|
while (chunks--) {
|
|
uint randNumber = 0;
|
|
for (int filled = 0; filled < intbits; filled += randbits)
|
|
randNumber |= qrand()<<filled;
|
|
*(data+chunks) = randNumber;
|
|
}
|
|
|
|
result.data4[0] = (result.data4[0] & 0x3F) | 0x80; // UV_DCE
|
|
result.data3 = (result.data3 & 0x0FFF) | 0x4000; // UV_Random
|
|
|
|
return result;
|
|
}
|
|
|
|
Session::Session(QObject* parent) :
|
|
QObject(parent)
|
|
, _shellProcess(0)
|
|
, _emulation(0)
|
|
, _monitorActivity(false)
|
|
, _monitorSilence(false)
|
|
, _notifiedActivity(false)
|
|
, _silenceSeconds(10)
|
|
, _autoClose(true)
|
|
, _closePerUserRequest(false)
|
|
, _addToUtmp(true)
|
|
, _flowControl(true)
|
|
, _sessionId(0)
|
|
, _sessionProcessInfo(0)
|
|
, _foregroundProcessInfo(0)
|
|
, _foregroundPid(0)
|
|
, _zmodemBusy(false)
|
|
, _zmodemProc(0)
|
|
, _zmodemProgress(0)
|
|
, _hasDarkBackground(false)
|
|
{
|
|
_uniqueIdentifier = createUuid();
|
|
|
|
//prepare DBus communication
|
|
new SessionAdaptor(this);
|
|
_sessionId = ++lastSessionId;
|
|
QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/")+QString::number(_sessionId), this);
|
|
|
|
//create emulation backend
|
|
_emulation = new Vt102Emulation();
|
|
|
|
connect( _emulation, SIGNAL(titleChanged(int,QString)),
|
|
this, SLOT(setUserTitle(int,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(QString)),
|
|
this, SIGNAL(profileChangeCommandReceived(QString)) );
|
|
connect( _emulation, SIGNAL(flowControlKeyPressed(bool)) , this,
|
|
SLOT(updateFlowControlState(bool)) );
|
|
connect( _emulation, SIGNAL(primaryScreenInUse(bool)) , this,
|
|
SLOT(onPrimaryScreenInUse(bool)) );
|
|
connect( _emulation, SIGNAL(selectedText(QString)) , this,
|
|
SLOT(onSelectedText(QString)) );
|
|
|
|
//create new teletype for I/O with shell process
|
|
openTeletype(-1);
|
|
|
|
//setup timer for monitoring session activity
|
|
_silenceTimer = new QTimer(this);
|
|
_silenceTimer->setSingleShot(true);
|
|
connect(_silenceTimer, SIGNAL(timeout()), this, SLOT(silenceTimerDone()));
|
|
|
|
_activityTimer= new QTimer(this);
|
|
_activityTimer->setSingleShot(true);
|
|
connect(_activityTimer, SIGNAL(timeout()), this, SLOT(activityTimerDone()));
|
|
}
|
|
|
|
void Session::openTeletype(int fd)
|
|
{
|
|
if ( isRunning() )
|
|
{
|
|
kWarning() << "Attempted to open teletype in a running session.";
|
|
return;
|
|
}
|
|
|
|
delete _shellProcess;
|
|
|
|
if (fd < 0)
|
|
_shellProcess = new Pty();
|
|
else
|
|
_shellProcess = new Pty(fd);
|
|
|
|
_shellProcess->setUtf8Mode(_emulation->utf8());
|
|
|
|
//connect teletype to emulation backend
|
|
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,QProcess::ExitStatus)) );
|
|
connect( _emulation,SIGNAL(imageSizeChanged(int,int)),this,SLOT(updateWindowSize(int,int)) );
|
|
connect( _emulation,SIGNAL(imageSizeInitialized()),this,SLOT(run()) );
|
|
}
|
|
|
|
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 && ( _shellProcess->state() == QProcess::Running );
|
|
}
|
|
|
|
void Session::setCodec(QTextCodec* codec)
|
|
{
|
|
emulation()->setCodec(codec);
|
|
}
|
|
|
|
bool Session::setCodec(QByteArray name)
|
|
{
|
|
QTextCodec* codec = QTextCodec::codecForName(name);
|
|
|
|
if (codec) {
|
|
setCodec(codec);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QByteArray Session::codec()
|
|
{
|
|
return _emulation->codec()->name();
|
|
}
|
|
|
|
void Session::setProgram(const QString& program)
|
|
{
|
|
_program = ShellCommand::expand(program);
|
|
}
|
|
void Session::setInitialWorkingDirectory(const QString& dir)
|
|
{
|
|
_initialWorkingDir = KShell::tildeExpand(ShellCommand::expand(dir));
|
|
}
|
|
void Session::setArguments(const QStringList& arguments)
|
|
{
|
|
_arguments = ShellCommand::expand(arguments);
|
|
}
|
|
|
|
QString Session::currentWorkingDirectory()
|
|
{
|
|
// only returned cached value
|
|
if (_currentWorkingDir.isEmpty())
|
|
updateWorkingDirectory();
|
|
|
|
return _currentWorkingDir;
|
|
}
|
|
ProcessInfo* Session::updateWorkingDirectory()
|
|
{
|
|
ProcessInfo* process = getProcessInfo();
|
|
_currentWorkingDir = process->validCurrentDir();
|
|
return process;
|
|
}
|
|
|
|
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();
|
|
|
|
exec = KRun::binaryName(exec, false);
|
|
exec = KShell::tildeExpand(exec);
|
|
QString pexec = KStandardDirs::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 = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit();
|
|
QByteArray messageText = message.toLocal8Bit();
|
|
|
|
static const char redPenOn[] = "\033[1m\033[31m";
|
|
static const char redPenOff[] = "\033[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));
|
|
}
|
|
|
|
QString Session::shellSessionId() const
|
|
{
|
|
QString friendlyUuid(_uniqueIdentifier.toString());
|
|
friendlyUuid.remove('-').remove('{').remove('}');
|
|
|
|
return friendlyUuid;
|
|
}
|
|
|
|
void Session::run()
|
|
{
|
|
// extra safeguard for potential bug.
|
|
if ( isRunning() )
|
|
{
|
|
kWarning() << "Attempted to re-run an already running session.";
|
|
return;
|
|
}
|
|
|
|
//check that everything is in place to run the session
|
|
if (_program.isEmpty())
|
|
{
|
|
kWarning() << "Session::run() - program to run not set.";
|
|
}
|
|
if (_arguments.isEmpty())
|
|
{
|
|
kWarning() << "Session::run() - no command line arguments specified.";
|
|
}
|
|
if (_uniqueIdentifier.isNull())
|
|
{
|
|
_uniqueIdentifier = createUuid();
|
|
}
|
|
|
|
const int CHOICE_COUNT = 3;
|
|
// if '_program' is empty , fall back to default shell. If that is not set
|
|
// then fall back to /bin/sh
|
|
QString programs[CHOICE_COUNT] = {_program,qgetenv("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();
|
|
|
|
if (!_initialWorkingDir.isEmpty())
|
|
_shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
|
|
else
|
|
_shellProcess->setInitialWorkingDirectory(QDir::homePath());
|
|
|
|
_shellProcess->setFlowControlEnabled(_flowControl);
|
|
_shellProcess->setEraseChar(_emulation->eraseChar());
|
|
|
|
// 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";
|
|
_environment << backgroundColorHint;
|
|
_environment << QString("SHELL_SESSION_ID=%1").arg(shellSessionId());
|
|
|
|
int result = _shellProcess->start( exec,
|
|
arguments,
|
|
_environment,
|
|
windowId(),
|
|
_addToUtmp,
|
|
dbusService,
|
|
(QLatin1String("/Sessions/") +
|
|
QString::number(_sessionId)));
|
|
|
|
if (result < 0)
|
|
{
|
|
terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(" ")));
|
|
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::silenceTimerDone()
|
|
{
|
|
//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::activityTimerDone()
|
|
{
|
|
_notifiedActivity = false;
|
|
}
|
|
|
|
void Session::updateFlowControlState(bool suspended)
|
|
{
|
|
if (suspended)
|
|
{
|
|
if (flowControlEnabled())
|
|
{
|
|
foreach(TerminalDisplay* display,_views)
|
|
{
|
|
if (display->flowControlWarningEnabled())
|
|
display->outputSuspended(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach(TerminalDisplay* display,_views)
|
|
display->outputSuspended(false);
|
|
}
|
|
}
|
|
|
|
void Session::onPrimaryScreenInUse(bool use)
|
|
{
|
|
emit primaryScreenInUse(use);
|
|
}
|
|
|
|
void Session::onSelectedText(const QString & text)
|
|
{
|
|
emit selectedText(text);
|
|
}
|
|
|
|
void Session::activityStateSet(int state)
|
|
{
|
|
if (state == NOTIFYBELL)
|
|
{
|
|
emit bellRequest( i18n("Bell in session '%1'",_nameTitle) );
|
|
}
|
|
else if (state == NOTIFYACTIVITY)
|
|
{
|
|
if ( _monitorActivity && !_notifiedActivity )
|
|
{
|
|
KNotification::event("Activity", i18n("Activity in session '%1'", _nameTitle), QPixmap(),
|
|
QApplication::activeWindow(),
|
|
KNotification::CloseWhenWidgetActivated);
|
|
|
|
// mask activity notification for a while to avoid flooding
|
|
// TODO: should this hardcoded interval be user configurable?
|
|
_notifiedActivity = true;
|
|
const int activitMaskSeconds = 15;
|
|
_activityTimer->start(activitMaskSeconds*1000);
|
|
}
|
|
|
|
if (_monitorSilence)
|
|
{
|
|
_silenceTimer->start(_silenceSeconds*1000);
|
|
}
|
|
}
|
|
|
|
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::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() );
|
|
view->processFilters();
|
|
}
|
|
}
|
|
|
|
// 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 );
|
|
}
|
|
}
|
|
void Session::updateWindowSize(int lines, int columns)
|
|
{
|
|
Q_ASSERT(lines > 0 && columns > 0);
|
|
_shellProcess->setWindowSize(lines,columns);
|
|
}
|
|
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 )
|
|
{
|
|
if ( _shellProcess->waitForFinished(1000) )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Session::close()
|
|
{
|
|
if ( isRunning() )
|
|
{
|
|
if ( !closeInNormalWay() )
|
|
closeInForceWay();
|
|
}
|
|
else
|
|
{
|
|
// terminal process has finished, just close the session
|
|
QTimer::singleShot(1, this, SIGNAL(finished()));
|
|
}
|
|
}
|
|
|
|
bool Session::closeInNormalWay()
|
|
{
|
|
_autoClose = true;
|
|
_closePerUserRequest = true;
|
|
|
|
// for the possible case where following events happen in sequence:
|
|
//
|
|
// 1). the terminal process crashes
|
|
// 2). the tab stays open and displays warning message
|
|
// 3). the user closes the tab explicitly
|
|
//
|
|
if ( !isRunning() )
|
|
{
|
|
emit finished();
|
|
return true;
|
|
}
|
|
|
|
if ( kill(SIGHUP) )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGHUP";
|
|
_shellProcess->pty()->close();
|
|
return (_shellProcess->waitForFinished(1000)) ;
|
|
}
|
|
}
|
|
|
|
bool Session::closeInForceWay()
|
|
{
|
|
_autoClose = true;
|
|
_closePerUserRequest = true;
|
|
|
|
if( kill(SIGKILL) )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
kWarning() << "Process " << _shellProcess->pid() << " did not die with SIGKILL";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Session::sendText(const QString& text) const
|
|
{
|
|
_emulation->sendText(text);
|
|
}
|
|
|
|
void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
|
|
{
|
|
_emulation->sendMouseEvent(buttons, column, line, eventType);
|
|
}
|
|
|
|
Session::~Session()
|
|
{
|
|
delete _foregroundProcessInfo;
|
|
delete _sessionProcessInfo;
|
|
delete _emulation;
|
|
delete _shellProcess;
|
|
delete _zmodemProc;
|
|
}
|
|
|
|
void Session::done(int exitCode, QProcess::ExitStatus exitStatus)
|
|
{
|
|
if ( !_autoClose )
|
|
{
|
|
_userTitle = i18nc("@info:shell This session is done", "Finished");
|
|
emit titleChanged();
|
|
return;
|
|
}
|
|
|
|
if ( _closePerUserRequest )
|
|
{
|
|
emit finished();
|
|
return;
|
|
}
|
|
|
|
QString message;
|
|
|
|
if ( exitCode != 0)
|
|
{
|
|
if (exitStatus != QProcess::NormalExit)
|
|
message = i18n("Program '%1' crashed.", _program);
|
|
else
|
|
message = i18n("Program '%1' exited with status %2.", _program, exitCode);
|
|
|
|
//FIXME: See comments in Session::silenceTimerDone()
|
|
KNotification::event("Finished", message , QPixmap(),
|
|
QApplication::activeWindow(),
|
|
KNotification::CloseWhenWidgetActivated);
|
|
}
|
|
|
|
if ( exitStatus != QProcess::NormalExit )
|
|
{
|
|
// this seeming duplicated line is for the case when exitCode is 0
|
|
message = i18n("Program '%1' crashed.", _program);
|
|
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();
|
|
}
|
|
|
|
ProcessInfo* Session::getProcessInfo()
|
|
{
|
|
ProcessInfo* process = 0;
|
|
|
|
if (isForegroundProcessActive())
|
|
{
|
|
process = _foregroundProcessInfo;
|
|
}
|
|
else
|
|
{
|
|
updateSessionProcessInfo();
|
|
process = _sessionProcessInfo;
|
|
}
|
|
|
|
return process;
|
|
}
|
|
|
|
void Session::updateSessionProcessInfo()
|
|
{
|
|
Q_ASSERT(_shellProcess);
|
|
if (!_sessionProcessInfo)
|
|
{
|
|
_sessionProcessInfo = ProcessInfo::newInstance(processId());
|
|
_sessionProcessInfo->setUserHomeDir();
|
|
}
|
|
_sessionProcessInfo->update();
|
|
}
|
|
|
|
bool Session::updateForegroundProcessInfo()
|
|
{
|
|
Q_ASSERT(_shellProcess);
|
|
|
|
int pid = _shellProcess->foregroundProcessGroup();
|
|
if (pid != _foregroundPid)
|
|
{
|
|
delete _foregroundProcessInfo;
|
|
_foregroundProcessInfo = ProcessInfo::newInstance(pid);
|
|
_foregroundPid = pid;
|
|
}
|
|
|
|
if (_foregroundProcessInfo)
|
|
{
|
|
_foregroundProcessInfo->update();
|
|
return _foregroundProcessInfo->isValid();
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Session::isRemote()
|
|
{
|
|
ProcessInfo* process = getProcessInfo();
|
|
|
|
bool ok = false;
|
|
return ( process->name(&ok) == "ssh" && ok );
|
|
}
|
|
|
|
QString Session::getDynamicTitle()
|
|
{
|
|
// update current directory from process
|
|
ProcessInfo* process = updateWorkingDirectory();
|
|
|
|
// format tab titles using process info
|
|
bool ok = false;
|
|
QString title;
|
|
if ( process->name(&ok) == "ssh" && ok )
|
|
{
|
|
SSHProcessInfo sshInfo(*process);
|
|
title = sshInfo.format(tabTitleFormat(Session::RemoteTabTitle));
|
|
}
|
|
else
|
|
{
|
|
title = process->format(tabTitleFormat(Session::LocalTabTitle));
|
|
}
|
|
|
|
// special handling for the "%w" marker which is replaced with the
|
|
// window title set by the terminal process(shell,mc,vim,etc)
|
|
title.replace("%w", userTitle());
|
|
|
|
// special handling for the "%#" marker which is replaced with the
|
|
// number of the session
|
|
title.replace("%#", QString::number(sessionId()));
|
|
|
|
return title;
|
|
}
|
|
|
|
KUrl Session::getUrl()
|
|
{
|
|
QString path;
|
|
|
|
updateSessionProcessInfo();
|
|
if (_sessionProcessInfo->isValid())
|
|
{
|
|
bool ok = false;
|
|
|
|
// check if foreground process is bookmark-able
|
|
if (isForegroundProcessActive())
|
|
{
|
|
// for remote connections, save the user and host
|
|
// bright ideas to get the directory at the other end are welcome :)
|
|
if (_foregroundProcessInfo->name(&ok) == "ssh" && ok)
|
|
{
|
|
SSHProcessInfo sshInfo(*_foregroundProcessInfo);
|
|
|
|
path = "ssh://" + sshInfo.userName() + '@' + sshInfo.host();
|
|
|
|
QString port = sshInfo.port();
|
|
if (!port.isEmpty() && port != "22")
|
|
{
|
|
path.append(':' + port);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
path = _foregroundProcessInfo->currentDir(&ok);
|
|
if (!ok)
|
|
path.clear();
|
|
}
|
|
}
|
|
else // otherwise use the current working directory of the shell process
|
|
{
|
|
path = _sessionProcessInfo->currentDir(&ok);
|
|
if (!ok)
|
|
path.clear();
|
|
}
|
|
}
|
|
|
|
return KUrl(path);
|
|
}
|
|
|
|
void Session::setIconName(const QString& iconName)
|
|
{
|
|
if ( iconName != _iconName )
|
|
{
|
|
_iconName = iconName;
|
|
emit titleChanged();
|
|
}
|
|
}
|
|
|
|
void Session::setIconText(const QString& iconText)
|
|
{
|
|
_iconText = 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)
|
|
{
|
|
_silenceTimer->start(_silenceSeconds*1000);
|
|
}
|
|
else
|
|
{
|
|
_silenceTimer->stop();
|
|
}
|
|
|
|
activityStateSet(NOTIFYNORMAL);
|
|
}
|
|
|
|
void Session::setMonitorSilenceSeconds(int seconds)
|
|
{
|
|
_silenceSeconds = seconds;
|
|
if (_monitorSilence) {
|
|
_silenceTimer->start(_silenceSeconds*1000);
|
|
}
|
|
}
|
|
|
|
void Session::setAddToUtmp(bool set)
|
|
{
|
|
_addToUtmp = set;
|
|
}
|
|
|
|
void Session::setFlowControlEnabled(bool enabled)
|
|
{
|
|
_flowControl = enabled;
|
|
|
|
if (_shellProcess)
|
|
_shellProcess->setFlowControlEnabled(_flowControl);
|
|
|
|
emit flowControlEnabledChanged(enabled);
|
|
}
|
|
bool Session::flowControlEnabled() const
|
|
{
|
|
if (_shellProcess)
|
|
return _shellProcess->flowControlEnabled();
|
|
else
|
|
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);
|
|
|
|
connect(_zmodemProc,SIGNAL (readyReadStandardOutput()),
|
|
this, SLOT(zmodemReadAndSendBlock()));
|
|
connect(_zmodemProc,SIGNAL (readyReadStandardError()),
|
|
this, SLOT(zmodemReadStatus()));
|
|
connect(_zmodemProc,SIGNAL (finished(int,QProcess::ExitStatus)),
|
|
this, SLOT(zmodemFinished()));
|
|
|
|
_zmodemProc->start();
|
|
|
|
disconnect( _shellProcess,SIGNAL(receivedData(const char*,int)), this, SLOT(onReceiveBlock(const char*,int)) );
|
|
connect( _shellProcess,SIGNAL(receivedData(const char*,int)), this, SLOT(zmodemRcvBlock(const char*,int)) );
|
|
|
|
_zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false,
|
|
i18n("ZModem Progress"));
|
|
|
|
connect(_zmodemProgress, SIGNAL(user1Clicked()),
|
|
this, SLOT(zmodemFinished()));
|
|
|
|
_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()
|
|
{
|
|
/* zmodemFinished() is called by QProcess's finished() and
|
|
ZModemDialog's user1Clicked(). Therefore, an invocation by
|
|
user1Clicked() will recursively invoke this function again
|
|
when the KProcess is deleted! */
|
|
if (_zmodemProc) {
|
|
KProcess* process = _zmodemProc;
|
|
_zmodemProc = 0; // Set _zmodemProc to 0 avoid recursive invocations!
|
|
_zmodemBusy = false;
|
|
delete process; // Now, the KProcess may be disposed safely.
|
|
|
|
disconnect( _shellProcess,SIGNAL(receivedData(const char*,int)), this ,SLOT(zmodemRcvBlock(const char*,int)) );
|
|
connect( _shellProcess,SIGNAL(receivedData(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::processId() const
|
|
{
|
|
return _shellProcess->pid();
|
|
}
|
|
|
|
void Session::setTitle(int role , const QString& title)
|
|
{
|
|
switch (role) {
|
|
case (0):
|
|
this->setTitle(Session::NameRole, title);
|
|
break;
|
|
case (1):
|
|
this->setTitle(Session::DisplayedTitleRole, title);
|
|
|
|
// without these, that title will be overridden by the expansion of
|
|
// title format shortly after, which will confuses users.
|
|
_localTabTitleFormat = title;
|
|
_remoteTabTitleFormat = title;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
QString Session::title(int role) const
|
|
{
|
|
switch (role) {
|
|
case (0):
|
|
return this->title(Session::NameRole);
|
|
case (1):
|
|
return this->title(Session::DisplayedTitleRole);
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
void Session::setTabTitleFormat(int context , const QString& format)
|
|
{
|
|
switch (context) {
|
|
case (0):
|
|
this->setTabTitleFormat(Session::LocalTabTitle, format);
|
|
break;
|
|
case (1):
|
|
this->setTabTitleFormat(Session::RemoteTabTitle, format);
|
|
break;
|
|
}
|
|
}
|
|
|
|
QString Session::tabTitleFormat(int context) const
|
|
{
|
|
switch (context) {
|
|
case (0):
|
|
return this->tabTitleFormat(Session::LocalTabTitle);
|
|
case (1):
|
|
return this->tabTitleFormat(Session::RemoteTabTitle);
|
|
default:
|
|
return QString();
|
|
}
|
|
}
|
|
|
|
int Session::foregroundProcessId()
|
|
{
|
|
int pid;
|
|
|
|
bool ok = false;
|
|
pid = getProcessInfo()->pid(&ok);
|
|
if (!ok)
|
|
pid = -1;
|
|
|
|
return pid;
|
|
}
|
|
|
|
bool Session::isForegroundProcessActive()
|
|
{
|
|
// foreground process info is always updated after this
|
|
return updateForegroundProcessInfo() && (processId() != _foregroundPid);
|
|
}
|
|
|
|
QString Session::foregroundProcessName()
|
|
{
|
|
QString name;
|
|
|
|
if (updateForegroundProcessInfo())
|
|
{
|
|
bool ok = false;
|
|
name = _foregroundProcessInfo->name(&ok);
|
|
if (!ok)
|
|
name.clear();
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
void Session::saveSession(KConfigGroup& group)
|
|
{
|
|
group.writePathEntry("WorkingDir", currentWorkingDirectory());
|
|
group.writeEntry("LocalTab", tabTitleFormat(LocalTabTitle));
|
|
group.writeEntry("RemoteTab", tabTitleFormat(RemoteTabTitle));
|
|
group.writeEntry("SessionGuid", _uniqueIdentifier.toString());
|
|
group.writeEntry("Encoding", QString(codec()));
|
|
}
|
|
|
|
void Session::restoreSession(KConfigGroup& group)
|
|
{
|
|
QString value;
|
|
|
|
value = group.readPathEntry("WorkingDir", QString());
|
|
if (!value.isEmpty()) setInitialWorkingDirectory(value);
|
|
value = group.readEntry("LocalTab");
|
|
if (!value.isEmpty()) setTabTitleFormat(LocalTabTitle, value);
|
|
value = group.readEntry("RemoteTab");
|
|
if (!value.isEmpty()) setTabTitleFormat(RemoteTabTitle, value);
|
|
value = group.readEntry("SessionGuid");
|
|
if (!value.isEmpty()) _uniqueIdentifier = QUuid(value);
|
|
value = group.readEntry("Encoding");
|
|
if (!value.isEmpty()) setCodec(value.toUtf8());
|
|
}
|
|
|
|
SessionGroup::SessionGroup(QObject* parent)
|
|
: QObject(parent), _masterMode(0)
|
|
{
|
|
}
|
|
SessionGroup::~SessionGroup()
|
|
{
|
|
}
|
|
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)
|
|
{
|
|
connect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
|
|
_sessions.insert(session,false);
|
|
}
|
|
void SessionGroup::removeSession(Session* session)
|
|
{
|
|
disconnect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
|
|
setMasterStatus(session,false);
|
|
_sessions.remove(session);
|
|
}
|
|
void SessionGroup::sessionFinished()
|
|
{
|
|
Session* session = qobject_cast<Session*>(sender());
|
|
Q_ASSERT(session);
|
|
removeSession(session);
|
|
}
|
|
void SessionGroup::setMasterMode(int mode)
|
|
{
|
|
_masterMode = mode;
|
|
}
|
|
QList<Session*> SessionGroup::masters() const
|
|
{
|
|
return _sessions.keys(true);
|
|
}
|
|
void SessionGroup::setMasterStatus(Session* session , bool master)
|
|
{
|
|
const bool wasMaster = _sessions[session];
|
|
|
|
if (wasMaster == master) {
|
|
// No status change -> nothing to do.
|
|
return;
|
|
}
|
|
_sessions[session] = master;
|
|
|
|
if(master) {
|
|
connect( session->emulation() , SIGNAL(sendData(const char*,int)) , this,
|
|
SLOT(forwardData(const char*,int)) );
|
|
}
|
|
else
|
|
{
|
|
disconnect( session->emulation() , SIGNAL(sendData(const char*,int)) , this,
|
|
SLOT(forwardData(const char*,int)) );
|
|
}
|
|
}
|
|
void SessionGroup::forwardData(const char* data, int size)
|
|
{
|
|
static bool _inForwardData = false;
|
|
if(_inForwardData) { // Avoid recursive calls among session groups!
|
|
// A recursive call happens when a master in group A calls forwardData()
|
|
// in group B. If one of the destination sessions in group B is also a
|
|
// master of a group including the master session of group A, this would
|
|
// again call forwardData() in group A, and so on.
|
|
return;
|
|
}
|
|
|
|
_inForwardData = true;
|
|
QListIterator<Session*> iter(_sessions.keys());
|
|
while(iter.hasNext()) {
|
|
Session* other = iter.next();
|
|
if(!_sessions[other]) {
|
|
other->emulation()->sendString(data, size);
|
|
}
|
|
}
|
|
_inForwardData = false;
|
|
}
|
|
|
|
#include "Session.moc"
|
|
|