You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
11 KiB
C++

/*
This file is part of Konsole QML plugin,
which is a terminal emulator from KDE.
Copyright 2013 by Dmitry Zagnoyko <hiroshidi@gmail.com>
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 "ksession.h"
#include "TerminalDisplay.h"
// Qt
#include <QApplication>
#include <QDir>
#include <QGuiApplication>
#include <QPointer>
#include <QRegularExpression>
#include <QTimer>
// Konsole
#include "KeyboardTranslator.h"
#include "HistorySearch.h"
KSession::KSession(QObject *parent) :
QObject(parent), m_session(createSession("")), m_foregroundCheck(new QTimer(this))
{
connect(m_session, SIGNAL(started()), this, SIGNAL(started()));
connect(m_session, SIGNAL(finished()), this, SLOT(sessionFinished()));
// Each update notes the process group in front, the one getTitle() went by.
const auto updateTitle = [this] {
m_foregroundGroup = m_session->foregroundProcessId();
emit titleChanged();
};
connect(m_session, &Konsole::Session::titleChanged, this, updateTitle);
connect(m_session->emulation(), &Konsole::Emulation::titleChanged, this,
[this, updateTitle](int what, const QString &title) {
if (what != 0 && what != 2)
return;
m_programTitle = title;
m_programTitleGroup = m_session->foregroundProcessId();
updateTitle();
});
// Nothing announces a program starting or ending, so the foreground is
// looked at after output or a key, at most every 250 ms.
m_foregroundCheck->setSingleShot(true);
m_foregroundCheck->setInterval(250);
connect(m_foregroundCheck, &QTimer::timeout, this, [this, updateTitle] {
if (m_session->foregroundProcessId() != m_foregroundGroup)
updateTitle();
});
const auto checkForeground = [this] {
if (!m_foregroundCheck->isActive())
m_foregroundCheck->start();
};
connect(m_session, &Konsole::Session::receivedData, this, checkForeground);
connect(m_session->emulation(), &Konsole::Emulation::outputFromKeypressEvent, this, checkForeground);
}
KSession::~KSession()
{
if (m_session) {
m_session->close();
m_session->disconnect();
delete m_session;
}
}
void KSession::setTitle(QString name)
{
m_session->setTitle(Session::NameRole, name);
}
Konsole::Session *KSession::createSession(QString name)
{
Session *session = new Session();
session->setTitle(Session::NameRole, name);
/* Thats a freaking bad idea!!!!
* /bin/bash is not there on every system
* better set it to the current $SHELL
* Maybe you can also make a list available and then let the widget-owner decide what to use.
* By setting it to $SHELL right away we actually make the first filecheck obsolete.
* But as iam not sure if you want to do anything else ill just let both checks in and set this to $SHELL anyway.
*/
//cool-old-term: There is another check in the code. Not sure if useful.
const QByteArray envshell = qgetenv("SHELL");
const QString shellProg = envshell.isEmpty() ? QStringLiteral("/bin/bash") : QString::fromUtf8(envshell);
m_shellProgram = shellProg;
session->setProgram(shellProg);
setenv("TERM", "xterm-256color", 1);
//session->setProgram();
QStringList args("");
m_shellArgs = args;
session->setArguments(args);
session->setAutoClose(true);
session->setFlowControlEnabled(true);
session->setHistoryType(HistoryTypeBuffer(1000));
session->setDarkBackground(true);
session->setKeyBindings("");
return session;
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
int KSession::getRandomSeed()
{
return m_session->sessionId() * 31;
}
void KSession::addView(Konsole::TerminalDisplay *display)
{
m_session->addView(display);
}
void KSession::removeView(Konsole::TerminalDisplay *display)
{
m_session->removeView(display);
}
void KSession::sessionFinished()
{
emit finished();
}
void KSession::selectionChanged(bool textSelected)
{
Q_UNUSED(textSelected)
}
void KSession::startShellProgram()
{
if ( m_session->isRunning() ) {
return;
}
m_session->run();
}
bool KSession::sendSignal(int signal)
{
if ( !m_session->isRunning() ) {
return false;
}
return m_session->sendSignal(signal);
}
int KSession::getShellPID()
{
return m_session->processId();
}
void KSession::changeDir(const QString &dir)
{
/*
this is a very hackish way of trying to determine if the shell is in
the foreground before attempting to change the directory. It may not
be portable to anything other than Linux.
*/
QString strCmd;
strCmd.setNum(getShellPID());
strCmd.prepend("ps -j ");
strCmd.append(" | tail -1 | awk '{ print $5 }' | grep -q \\+");
int retval = system(strCmd.toStdString().c_str());
if (!retval) {
QString cmd = "cd " + dir + "\n";
sendText(cmd);
}
}
void KSession::setEnvironment(const QStringList &environment)
{
m_session->setEnvironment(environment);
}
void KSession::setShellProgram(const QString &progname)
{
m_shellProgram = progname;
m_session->setProgram(progname);
}
void KSession::setInitialWorkingDirectory(const QString &dir)
{
if ( _initialWorkingDirectory != dir ) {
_initialWorkingDirectory = dir;
m_session->setInitialWorkingDirectory(dir);
emit initialWorkingDirectoryChanged();
} }
QString KSession::getInitialWorkingDirectory()
{
return _initialWorkingDirectory;
}
QString KSession::getShellProgram() const
{
return m_shellProgram;
}
QStringList KSession::getShellProgramArgs() const
{
return m_shellArgs;
}
void KSession::setArgs(const QStringList &args)
{
m_shellArgs = args;
m_session->setArguments(args);
}
void KSession::setHistorySize(int lines)
{
if ( historySize() != lines ) {
if (lines < 0)
m_session->setHistoryType(HistoryTypeFile());
else
m_session->setHistoryType(HistoryTypeBuffer(lines));
emit historySizeChanged();
}
}
int KSession::historySize() const
{
if ( m_session->historyType().isUnlimited() ) {
return -1;
} else {
return m_session->historyType().maximumLineCount();
}
}
QString KSession::getHistory() const
{
QString history;
QTextStream historyStream(&history);
PlainTextDecoder historyDecoder;
historyDecoder.begin(&historyStream);
m_session->emulation()->writeToStream(&historyDecoder);
historyDecoder.end();
return history;
}
void KSession::sendText(QString text)
{
m_session->sendText(text);
}
void KSession::sendKey(int rep, int key, int mod) const
{
//TODO implement or remove this function.
// Qt::KeyboardModifier kbm = Qt::KeyboardModifier(mod);
// QKeyEvent qkey(QEvent::KeyPress, key, kbm);
// while (rep > 0){
// m_session->sendKey(&qkey);
// --rep;
// }
}
void KSession::clearScreen()
{
m_session->emulation()->clearEntireScreen();
}
void KSession::clearScrollback()
{
m_session->clearHistory();
}
bool KSession::clearScreenAndScrollback()
{
return m_session->emulation()->clearScreenAndHistory(!hasActiveProcess());
}
void KSession::resetTerminal()
{
// Resetting clears the screen into the history, so the history goes after it.
m_session->emulation()->reset();
m_session->clearHistory();
// A size change makes the shell, or a full-screen program, draw itself again;
// Ctrl+L would clear the screen into the history once more.
m_session->refresh();
}
void KSession::search(const QString &regexp, int startLine, int startColumn, bool forwards)
{
HistorySearch *history = new HistorySearch(QPointer<Emulation>(m_session->emulation()),
QRegularExpression(regexp),
forwards,
startColumn,
startLine,
this);
connect( history, SIGNAL(matchFound(int,int,int,int)), this, SIGNAL(matchFound(int,int,int,int)));
connect( history, SIGNAL(noMatchFound()), this, SIGNAL(noMatchFound()));
history->search();
}
void KSession::setFlowControlEnabled(bool enabled)
{
m_session->setFlowControlEnabled(enabled);
}
bool KSession::flowControlEnabled()
{
return m_session->flowControlEnabled();
}
void KSession::setKeyBindings(const QString &kb)
{
m_session->setKeyBindings(kb);
emit changedKeyBindings(kb);
}
QString KSession::getKeyBindings()
{
return m_session->keyBindings();
}
QStringList KSession::availableKeyBindings()
{
return KeyboardTranslatorManager::instance()->allTranslators();
}
QString KSession::keyBindings()
{
return m_session->keyBindings();
}
QString KSession::getTitle()
{
// A program in front of the shell gives the title it set, or else its name.
const int foreground = m_session->foregroundProcessId();
if (foreground > 0 && foreground != m_session->processId()) {
if (foreground == m_programTitleGroup && !m_programTitle.isEmpty())
return m_programTitle;
const QString name = m_session->foregroundProcessName();
if (!name.isEmpty())
return name;
}
const QString dir = m_session->currentDir();
// At the prompt tabs are titled by the working directory; home and root keep their full path.
if (dir == QDir::homePath() || dir == QLatin1String("/"))
return dir;
return QDir(dir).dirName();
}
bool KSession::hasActiveProcess() const
{
return m_session->processId() != m_session->foregroundProcessId();
}
QString KSession::foregroundProcessName()
{
return m_session->foregroundProcessName();
}
Konsole::Emulation *KSession::emulation() const
{
return m_session->emulation();
}
QString KSession::currentDir()
{
return m_session->currentDir();
}