1 /*
2     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
3     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
4     SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 // Own
10 #include "Session.h"
11 
12 // Standard
13 #include <csignal>
14 #include <cstdlib>
15 #include <unistd.h>
16 
17 // Qt
18 #include <QApplication>
19 #include <QColor>
20 #include <QDir>
21 #include <QFile>
22 #include <QKeyEvent>
23 
24 // KDE
25 #include <KConfigGroup>
26 #include <KIO/DesktopExecParser>
27 #include <KLocalizedString>
28 #include <KNotification>
29 #include <KProcess>
30 #include <KShell>
31 #include <kcoreaddons_version.h>
32 
33 // Konsole
34 #include <sessionadaptor.h>
35 
36 #include "Pty.h"
37 #include "SSHProcessInfo.h"
38 #include "SessionManager.h"
39 #include "ShellCommand.h"
40 #include "Vt102Emulation.h"
41 #include "ZModemDialog.h"
42 #include "history/HistoryTypeFile.h"
43 #include "history/HistoryTypeNone.h"
44 #include "history/compact/CompactHistoryType.h"
45 #include "konsoledebug.h"
46 #include "profile/Profile.h"
47 #include "profile/ProfileManager.h"
48 
49 #include "terminalDisplay/TerminalDisplay.h"
50 #include "terminalDisplay/TerminalScrollBar.h"
51 
52 // Linux
53 #ifdef HAVE_GETPWUID
54 #include <pwd.h>
55 #include <sys/types.h>
56 #endif
57 
58 using namespace Konsole;
59 
60 int Session::lastSessionId = 0;
61 static bool show_disallow_certain_dbus_methods_message = true;
62 
63 static const int ZMODEM_BUFFER_SIZE = 1048576; // 1 Mb
64 
Session(QObject * parent)65 Session::Session(QObject *parent)
66     : QObject(parent)
67     , _uniqueIdentifier(QUuid())
68     , _shellProcess(nullptr)
69     , _emulation(nullptr)
70     , _views(QList<TerminalDisplay *>())
71     , _monitorActivity(false)
72     , _monitorSilence(false)
73     , _notifiedActivity(false)
74     , _silenceSeconds(10)
75     , _silenceTimer(nullptr)
76     , _activityTimer(nullptr)
77     , _autoClose(true)
78     , _closePerUserRequest(false)
79     , _nameTitle(QString())
80     , _displayTitle(QString())
81     , _userTitle(QString())
82     , _localTabTitleFormat(QString())
83     , _remoteTabTitleFormat(QString())
84     , _tabTitleSetByUser(false)
85     , _tabColorSetByUser(false)
86     , _iconName(QString())
87     , _iconText(QString())
88     , _addToUtmp(true)
89     , _flowControlEnabled(true)
90     , _program(QString())
91     , _arguments(QStringList())
92     , _environment(QStringList())
93     , _sessionId(0)
94     , _initialWorkingDir(QString())
95     , _currentWorkingDir(QString())
96     , _reportedWorkingUrl(QUrl())
97     , _sessionProcessInfo(nullptr)
98     , _foregroundProcessInfo(nullptr)
99     , _foregroundPid(0)
100     , _zmodemBusy(false)
101     , _zmodemProc(nullptr)
102     , _zmodemProgress(nullptr)
103     , _hasDarkBackground(false)
104     , _preferredSize(QSize())
105     , _readOnly(false)
106     , _isPrimaryScreen(true)
107 {
108     _uniqueIdentifier = QUuid::createUuid();
109 
110     // prepare DBus communication
111     new SessionAdaptor(this);
112     _sessionId = ++lastSessionId;
113     QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/") + QString::number(_sessionId), this);
114 
115     // create emulation backend
116     _emulation = new Vt102Emulation();
117     _emulation->reset();
118 
119     connect(_emulation, &Konsole::Emulation::sessionAttributeChanged, this, &Konsole::Session::setSessionAttribute);
120     connect(_emulation, &Konsole::Emulation::bell, this, [this]() {
121         Q_EMIT bellRequest(i18n("Bell in '%1' (Session '%2')", _displayTitle, _nameTitle));
122         this->setPendingNotification(Notification::Bell);
123     });
124     connect(_emulation, &Konsole::Emulation::zmodemDownloadDetected, this, &Konsole::Session::fireZModemDownloadDetected);
125     connect(_emulation, &Konsole::Emulation::zmodemUploadDetected, this, &Konsole::Session::fireZModemUploadDetected);
126     connect(_emulation, &Konsole::Emulation::profileChangeCommandReceived, this, &Konsole::Session::profileChangeCommandReceived);
127     connect(_emulation, &Konsole::Emulation::flowControlKeyPressed, this, &Konsole::Session::updateFlowControlState);
128     connect(_emulation, &Konsole::Emulation::primaryScreenInUse, this, &Konsole::Session::onPrimaryScreenInUse);
129     connect(_emulation, &Konsole::Emulation::selectionChanged, this, &Konsole::Session::selectionChanged);
130     connect(_emulation, &Konsole::Emulation::imageResizeRequest, this, &Konsole::Session::resizeRequest);
131     connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest);
132 
133     // create new teletype for I/O with shell process
134     openTeletype(-1, true);
135 
136     // setup timer for monitoring session activity & silence
137     _silenceTimer = new QTimer(this);
138     _silenceTimer->setSingleShot(true);
139     connect(_silenceTimer, &QTimer::timeout, this, &Konsole::Session::silenceTimerDone);
140 
141     _activityTimer = new QTimer(this);
142     _activityTimer->setSingleShot(true);
143     connect(_activityTimer, &QTimer::timeout, this, &Konsole::Session::activityTimerDone);
144 }
145 
~Session()146 Session::~Session()
147 {
148     delete _foregroundProcessInfo;
149     delete _sessionProcessInfo;
150     delete _emulation;
151     delete _shellProcess;
152     delete _zmodemProc;
153 }
154 
openTeletype(int fd,bool runShell)155 void Session::openTeletype(int fd, bool runShell)
156 {
157     if (isRunning()) {
158         qWarning() << "Attempted to open teletype in a running session.";
159         return;
160     }
161 
162     delete _shellProcess;
163 
164     if (fd < 0) {
165         _shellProcess = new Pty();
166     } else {
167         _shellProcess = new Pty(fd);
168     }
169 
170     _shellProcess->setUtf8Mode(_emulation->utf8());
171 
172     // connect the I/O between emulator and pty process
173     connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
174     connect(_emulation, &Konsole::Emulation::sendData, _shellProcess, &Konsole::Pty::sendData);
175 
176     // UTF8 mode
177     connect(_emulation, &Konsole::Emulation::useUtf8Request, _shellProcess, &Konsole::Pty::setUtf8Mode);
178 
179     // get notified when the pty process is finished
180     connect(_shellProcess, QOverload<int, QProcess::ExitStatus>::of(&Konsole::Pty::finished), this, &Konsole::Session::done);
181 
182     // emulator size
183     connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize);
184     if (fd < 0 || runShell) {
185         // Using a queued connection guarantees that starting the session
186         // is delayed until all (both) image size updates at startup have
187         // been processed. See #203185 and #412598.
188         connect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run, Qt::QueuedConnection);
189     } else {
190         // run needs to be disconnected, as it may be already connected by the constructor
191         disconnect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run);
192     }
193 }
194 
windowId() const195 WId Session::windowId() const
196 {
197     // Returns a window ID for this session which is used
198     // to set the WINDOWID environment variable in the shell
199     // process.
200     //
201     // Sessions can have multiple views or no views, which means
202     // that a single ID is not always going to be accurate.
203     //
204     // If there are no views, the window ID is just 0.  If
205     // there are multiple views, then the window ID for the
206     // top-level window which contains the first view is
207     // returned
208 
209     if (_views.count() == 0) {
210         return 0;
211     } else {
212         /**
213          * compute the windows id to use
214          * doesn't call winId on some widget, as this might lead
215          * to rendering artifacts as this will trigger the
216          * creation of a native window, see https://doc.qt.io/qt-5/qwidget.html#winId
217          * instead, use https://doc.qt.io/qt-5/qwidget.html#effectiveWinId
218          */
219         QWidget *widget = _views.first();
220         Q_ASSERT(widget);
221         return widget->effectiveWinId();
222     }
223 }
224 
setDarkBackground(bool darkBackground)225 void Session::setDarkBackground(bool darkBackground)
226 {
227     _hasDarkBackground = darkBackground;
228 }
229 
isRunning() const230 bool Session::isRunning() const
231 {
232     return (_shellProcess != nullptr) && (_shellProcess->state() == QProcess::Running);
233 }
234 
hasFocus() const235 bool Session::hasFocus() const
236 {
237     return std::any_of(_views.constBegin(), _views.constEnd(), [](const TerminalDisplay *display) {
238         return display->hasFocus();
239     });
240 }
241 
setCodec(QTextCodec * codec)242 void Session::setCodec(QTextCodec *codec)
243 {
244     if (isReadOnly()) {
245         return;
246     }
247 
248     emulation()->setCodec(codec);
249 
250     Q_EMIT sessionCodecChanged(codec);
251 }
252 
setCodec(const QByteArray & name)253 bool Session::setCodec(const QByteArray &name)
254 {
255     QTextCodec *codec = QTextCodec::codecForName(name);
256 
257     if (codec != nullptr) {
258         setCodec(codec);
259         return true;
260     } else {
261         return false;
262     }
263 }
264 
codec()265 QByteArray Session::codec()
266 {
267     return _emulation->codec()->name();
268 }
269 
setProgram(const QString & program)270 void Session::setProgram(const QString &program)
271 {
272     _program = ShellCommand::expand(program);
273 }
274 
setArguments(const QStringList & arguments)275 void Session::setArguments(const QStringList &arguments)
276 {
277     _arguments = ShellCommand::expand(arguments);
278 }
279 
setInitialWorkingDirectory(const QString & dir)280 void Session::setInitialWorkingDirectory(const QString &dir)
281 {
282     _initialWorkingDir = validDirectory(KShell::tildeExpand(ShellCommand::expand(dir)));
283 }
284 
currentWorkingDirectory()285 QString Session::currentWorkingDirectory()
286 {
287     if (_reportedWorkingUrl.isValid() && _reportedWorkingUrl.isLocalFile()) {
288         return _reportedWorkingUrl.path();
289     }
290 
291     // only returned cached value
292     if (_currentWorkingDir.isEmpty()) {
293         updateWorkingDirectory();
294     }
295 
296     return _currentWorkingDir;
297 }
updateWorkingDirectory()298 void Session::updateWorkingDirectory()
299 {
300     updateSessionProcessInfo();
301 
302     const QString currentDir = _sessionProcessInfo->validCurrentDir();
303     if (currentDir != _currentWorkingDir) {
304         _currentWorkingDir = currentDir;
305         Q_EMIT currentDirectoryChanged(_currentWorkingDir);
306     }
307 }
308 
views() const309 QList<TerminalDisplay *> Session::views() const
310 {
311     return _views;
312 }
313 
addView(TerminalDisplay * widget)314 void Session::addView(TerminalDisplay *widget)
315 {
316     Q_ASSERT(!_views.contains(widget));
317 
318     _views.append(widget);
319 
320     // connect emulation - view signals and slots
321     connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, _emulation, &Konsole::Emulation::sendKeyEvent);
322     connect(widget, &Konsole::TerminalDisplay::mouseSignal, _emulation, &Konsole::Emulation::sendMouseEvent);
323     connect(widget, &Konsole::TerminalDisplay::sendStringToEmu, _emulation, &Konsole::Emulation::sendString);
324     connect(widget, &Konsole::TerminalDisplay::peekPrimaryRequested, _emulation, &Konsole::Emulation::setPeekPrimary);
325 
326     // allow emulation to notify the view when the foreground process
327     // indicates whether or not it is interested in Mouse Tracking events
328     connect(_emulation, &Konsole::Emulation::programRequestsMouseTracking, widget, &Konsole::TerminalDisplay::setUsesMouseTracking);
329 
330     widget->setUsesMouseTracking(_emulation->programUsesMouseTracking());
331 
332     connect(_emulation, &Konsole::Emulation::enableAlternateScrolling, widget->scrollBar(), &Konsole::TerminalScrollBar::setAlternateScrolling);
333 
334     connect(_emulation, &Konsole::Emulation::programBracketedPasteModeChanged, widget, &Konsole::TerminalDisplay::setBracketedPasteMode);
335 
336     widget->setBracketedPasteMode(_emulation->programBracketedPasteMode());
337 
338     widget->setScreenWindow(_emulation->createWindow());
339 
340     _emulation->setCurrentTerminalDisplay(widget);
341 
342     // connect view signals and slots
343     connect(widget, &Konsole::TerminalDisplay::changedContentSizeSignal, this, &Konsole::Session::onViewSizeChange);
344 
345     connect(widget, &Konsole::TerminalDisplay::destroyed, this, &Konsole::Session::viewDestroyed);
346 
347     connect(widget, &Konsole::TerminalDisplay::compositeFocusChanged, _emulation, &Konsole::Emulation::focusChanged);
348 
349     connect(_emulation, &Konsole::Emulation::setCursorStyleRequest, widget, &Konsole::TerminalDisplay::setCursorStyle);
350     connect(_emulation, &Konsole::Emulation::resetCursorStyleRequest, widget, &Konsole::TerminalDisplay::resetCursorStyle);
351 
352     connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::Session::resetNotifications);
353 }
354 
viewDestroyed(QObject * view)355 void Session::viewDestroyed(QObject *view)
356 {
357     auto *display = reinterpret_cast<TerminalDisplay *>(view);
358 
359     Q_ASSERT(_views.contains(display));
360 
361     removeView(display);
362 }
363 
removeView(TerminalDisplay * widget)364 void Session::removeView(TerminalDisplay *widget)
365 {
366     _views.removeAll(widget);
367 
368     disconnect(widget, nullptr, this, nullptr);
369 
370     // disconnect
371     //  - key presses signals from widget
372     //  - mouse activity signals from widget
373     //  - string sending signals from widget
374     //
375     //  ... and any other signals connected in addView()
376     disconnect(widget, nullptr, _emulation, nullptr);
377 
378     // disconnect state change signals emitted by emulation
379     disconnect(_emulation, nullptr, widget, nullptr);
380 
381     // close the session automatically when the last view is removed
382     if (_views.count() == 0) {
383         close();
384     }
385 }
386 
387 // Upon a KPty error, there is no description on what that error was...
388 // Check to see if the given program is executable.
checkProgram(const QString & program)389 QString Session::checkProgram(const QString &program)
390 {
391     QString exec = program;
392 
393     if (exec.isEmpty()) {
394         return QString();
395     }
396 
397     QFileInfo info(exec);
398     if (info.isAbsolute() && info.exists() && info.isExecutable()) {
399         return exec;
400     }
401 
402     exec = KIO::DesktopExecParser::executablePath(exec);
403     exec = KShell::tildeExpand(exec);
404     const QString pexec = QStandardPaths::findExecutable(exec);
405     if (pexec.isEmpty()) {
406         qCritical() << i18n("Could not find binary: ") << exec;
407         return QString();
408     }
409 
410     return exec;
411 }
412 
terminalWarning(const QString & message)413 void Session::terminalWarning(const QString &message)
414 {
415     static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit();
416     QByteArray messageText = message.toLocal8Bit();
417 
418     static const char redPenOn[] = "\033[1m\033[31m";
419     static const char redPenOff[] = "\033[0m";
420 
421     _emulation->receiveData(redPenOn, qstrlen(redPenOn));
422     _emulation->receiveData("\n\r\n\r", 4);
423     _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData()));
424     _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData()));
425     _emulation->receiveData("\n\r\n\r", 4);
426     _emulation->receiveData(redPenOff, qstrlen(redPenOff));
427 }
428 
shellSessionId() const429 QString Session::shellSessionId() const
430 {
431     QString friendlyUuid(_uniqueIdentifier.toString());
432     friendlyUuid.remove(QLatin1Char('-')).remove(QLatin1Char('{')).remove(QLatin1Char('}'));
433 
434     return friendlyUuid;
435 }
436 
run()437 void Session::run()
438 {
439     // FIXME: run() is called twice in some instances
440     if (isRunning()) {
441         qCDebug(KonsoleDebug) << "Attempted to re-run an already running session (" << processId() << ")";
442         return;
443     }
444 
445     // check that everything is in place to run the session
446     if (_program.isEmpty()) {
447         qWarning() << "Program to run not set.";
448     }
449     if (_arguments.isEmpty()) {
450         qWarning() << "No command line arguments specified.";
451     }
452     if (_uniqueIdentifier.isNull()) {
453         _uniqueIdentifier = QUuid::createUuid();
454     }
455 
456     QStringList programs = {_program, QString::fromUtf8(qgetenv("SHELL")), QStringLiteral("/bin/sh")};
457 
458 #ifdef HAVE_GETPWUID
459     auto pw = getpwuid(getuid());
460     // pw may be NULL
461     if (pw != nullptr) {
462         programs.insert(1, QString::fromLocal8Bit(pw->pw_shell));
463     }
464     // pw: Do not pass the returned pointer to free.
465 #endif
466 
467     QString exec;
468     for (const auto &choice : programs) {
469         exec = checkProgram(choice);
470         if (!exec.isEmpty()) {
471             break;
472         }
473     }
474 
475     // if nothing could be found (not even the fallbacks), print a warning and do not run
476     if (exec.isEmpty()) {
477         terminalWarning(i18n("Could not find an interactive shell to start."));
478         return;
479     }
480 
481     // if a program was specified via setProgram(), but it couldn't be found (but a fallback was), print a warning
482     if (exec != checkProgram(_program)) {
483         terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", _program, exec));
484     } else if (exec != checkProgram(exec)) {
485         terminalWarning(i18n("Could not find '%1', starting '%2' instead.  Please check your profile settings.", exec, checkProgram(exec)));
486     }
487 
488     // if no arguments are specified, fall back to program name
489     QStringList arguments = _arguments.join(QLatin1Char(' ')).isEmpty() ? QStringList() << exec : _arguments;
490 
491     if (!_initialWorkingDir.isEmpty()) {
492         _shellProcess->setInitialWorkingDirectory(_initialWorkingDir);
493     } else {
494         _shellProcess->setInitialWorkingDirectory(QDir::currentPath());
495     }
496 
497     _shellProcess->setFlowControlEnabled(_flowControlEnabled);
498     _shellProcess->setEraseChar(_emulation->eraseChar());
499     _shellProcess->setUseUtmp(_addToUtmp);
500 
501     // this is not strictly accurate use of the COLORFGBG variable.  This does not
502     // tell the terminal exactly which colors are being used, but instead approximates
503     // the color scheme as "black on white" or "white on black" depending on whether
504     // the background color is deemed dark or not
505     const QString backgroundColorHint = _hasDarkBackground ? QStringLiteral("COLORFGBG=15;0") : QStringLiteral("COLORFGBG=0;15");
506     addEnvironmentEntry(backgroundColorHint);
507 
508     addEnvironmentEntry(QStringLiteral("SHELL_SESSION_ID=%1").arg(shellSessionId()));
509 
510     addEnvironmentEntry(QStringLiteral("WINDOWID=%1").arg(QString::number(windowId())));
511 
512     const QString dbusService = QDBusConnection::sessionBus().baseService();
513     addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SERVICE=%1").arg(dbusService));
514 
515     const QString dbusObject = QStringLiteral("/Sessions/%1").arg(QString::number(_sessionId));
516     addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SESSION=%1").arg(dbusObject));
517 
518     int result = _shellProcess->start(exec, arguments, _environment);
519     if (result < 0) {
520         terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(QLatin1String(" "))));
521         terminalWarning(_shellProcess->errorString());
522         return;
523     }
524 
525     _shellProcess->setWriteable(false); // We are reachable via kwrited.
526 
527     Q_EMIT started();
528 }
529 
setSessionAttribute(int what,const QString & caption)530 void Session::setSessionAttribute(int what, const QString &caption)
531 {
532     // set to true if anything has actually changed
533     // eg. old _nameTitle != new _nameTitle
534     bool modified = false;
535 
536     if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) {
537         if (_userTitle != caption) {
538             _userTitle = caption;
539             modified = true;
540         }
541     }
542 
543     if ((what == IconNameAndWindowTitle) || (what == IconName)) {
544         if (_iconText != caption) {
545             _iconText = caption;
546             modified = true;
547         }
548     }
549 
550     if (what == TextColor || what == BackgroundColor) {
551         QString colorString = caption.section(QLatin1Char(';'), 0, 0);
552         QColor color = QColor(colorString);
553         if (color.isValid()) {
554             if (what == TextColor) {
555                 Q_EMIT changeForegroundColorRequest(color);
556             } else {
557                 Q_EMIT changeBackgroundColorRequest(color);
558             }
559         }
560     }
561 
562     if (what == SessionName) {
563         if (_localTabTitleFormat != caption) {
564             _localTabTitleFormat = caption;
565             setTitle(Session::DisplayedTitleRole, caption);
566             modified = true;
567         }
568     }
569 
570     /* The below use of 32 works but appears to non-standard.
571        It is from a commit from 2004 c20973eca8776f9b4f15bee5fdcb5a3205aa69de
572      */
573     // change icon via \033]32;Icon\007
574     if (what == SessionIcon) {
575         if (_iconName != caption) {
576             _iconName = caption;
577 
578             modified = true;
579         }
580     }
581 
582     if (what == CurrentDirectory) {
583         _reportedWorkingUrl = QUrl::fromUserInput(caption);
584         Q_EMIT currentDirectoryChanged(currentWorkingDirectory());
585         modified = true;
586     }
587 
588     if (what == ProfileChange) {
589         Q_EMIT profileChangeCommandReceived(caption);
590         return;
591     }
592 
593     if (modified) {
594         Q_EMIT sessionAttributeChanged();
595     }
596 }
597 
userTitle() const598 QString Session::userTitle() const
599 {
600     return _userTitle;
601 }
602 
setTabTitleFormat(TabTitleContext context,const QString & format)603 void Session::setTabTitleFormat(TabTitleContext context, const QString &format)
604 {
605     if (context == LocalTabTitle) {
606         _localTabTitleFormat = format;
607         ProcessInfo *process = getProcessInfo();
608         process->setUserNameRequired(format.contains(QLatin1String("%u")));
609     } else if (context == RemoteTabTitle) {
610         _remoteTabTitleFormat = format;
611     }
612 }
613 
tabTitleFormat(TabTitleContext context) const614 QString Session::tabTitleFormat(TabTitleContext context) const
615 {
616     if (context == LocalTabTitle) {
617         return _localTabTitleFormat;
618     } else if (context == RemoteTabTitle) {
619         return _remoteTabTitleFormat;
620     }
621 
622     return QString();
623 }
624 
tabTitleSetByUser(bool set)625 void Session::tabTitleSetByUser(bool set)
626 {
627     _tabTitleSetByUser = set;
628 }
629 
isTabTitleSetByUser() const630 bool Session::isTabTitleSetByUser() const
631 {
632     return _tabTitleSetByUser;
633 }
634 
tabColorSetByUser(bool set)635 void Session::tabColorSetByUser(bool set)
636 {
637     _tabColorSetByUser = set;
638 }
639 
isTabColorSetByUser() const640 bool Session::isTabColorSetByUser() const
641 {
642     return _tabColorSetByUser;
643 }
644 
silenceTimerDone()645 void Session::silenceTimerDone()
646 {
647     // FIXME: The idea here is that the notification popup will appear to tell the user than output from
648     // the terminal has stopped and the popup will disappear when the user activates the session.
649     //
650     // This breaks with the addition of multiple views of a session.  The popup should disappear
651     // when any of the views of the session becomes active
652 
653     // FIXME: Make message text for this notification and the activity notification more descriptive.
654     if (!_monitorSilence) {
655         setPendingNotification(Notification::Silence, false);
656         return;
657     }
658 
659     KNotification::event(hasFocus() ? QStringLiteral("Silence") : QStringLiteral("SilenceHidden"),
660                          i18n("Silence in '%1' (Session '%2')", _displayTitle, _nameTitle),
661                          QPixmap(),
662                          QApplication::activeWindow(),
663                          KNotification::CloseWhenWidgetActivated);
664     setPendingNotification(Notification::Silence);
665 }
666 
activityTimerDone()667 void Session::activityTimerDone()
668 {
669     _notifiedActivity = false;
670 }
671 
resetNotifications()672 void Session::resetNotifications()
673 {
674     static const Notification availableNotifications[] = {Activity, Silence, Bell};
675     for (auto notification : availableNotifications) {
676         setPendingNotification(notification, false);
677     }
678 }
679 
updateFlowControlState(bool suspended)680 void Session::updateFlowControlState(bool suspended)
681 {
682     if (suspended) {
683         if (flowControlEnabled()) {
684             for (TerminalDisplay *display : qAsConst(_views)) {
685                 if (display->flowControlWarningEnabled()) {
686                     display->outputSuspended(true);
687                 }
688             }
689         }
690     } else {
691         for (TerminalDisplay *display : qAsConst(_views)) {
692             display->outputSuspended(false);
693         }
694     }
695 }
696 
onPrimaryScreenInUse(bool use)697 void Session::onPrimaryScreenInUse(bool use)
698 {
699     _isPrimaryScreen = use;
700     Q_EMIT primaryScreenInUse(use);
701 }
702 
isPrimaryScreen()703 bool Session::isPrimaryScreen()
704 {
705     return _isPrimaryScreen;
706 }
707 
sessionAttributeRequest(int id,uint terminator)708 void Session::sessionAttributeRequest(int id, uint terminator)
709 {
710     switch (id) {
711     case TextColor:
712         // Get 'TerminalDisplay' (_view) foreground color
713         Q_EMIT getForegroundColor(terminator);
714         break;
715     case BackgroundColor:
716         // Get 'TerminalDisplay' (_view) background color
717         Q_EMIT getBackgroundColor(terminator);
718         break;
719     }
720 }
721 
onViewSizeChange(int,int)722 void Session::onViewSizeChange(int /*height*/, int /*width*/)
723 {
724     updateTerminalSize();
725 }
726 
updateTerminalSize()727 void Session::updateTerminalSize()
728 {
729     int minLines = -1;
730     int minColumns = -1;
731 
732     // minimum number of lines and columns that views require for
733     // their size to be taken into consideration ( to avoid problems
734     // with new view widgets which haven't yet been set to their correct size )
735     const int VIEW_LINES_THRESHOLD = 2;
736     const int VIEW_COLUMNS_THRESHOLD = 2;
737 
738     // select largest number of lines and columns that will fit in all visible views
739     for (TerminalDisplay *view : qAsConst(_views)) {
740         if (!view->isHidden() && view->lines() >= VIEW_LINES_THRESHOLD && view->columns() >= VIEW_COLUMNS_THRESHOLD) {
741             minLines = (minLines == -1) ? view->lines() : qMin(minLines, view->lines());
742             minColumns = (minColumns == -1) ? view->columns() : qMin(minColumns, view->columns());
743             view->processFilters();
744         }
745     }
746 
747     // backend emulation must have a _terminal of at least 1 column x 1 line in size
748     if (minLines > 0 && minColumns > 0) {
749         _emulation->setImageSize(minLines, minColumns);
750     }
751 }
updateWindowSize(int lines,int columns)752 void Session::updateWindowSize(int lines, int columns)
753 {
754     Q_ASSERT(lines > 0 && columns > 0);
755     _shellProcess->setWindowSize(columns, lines);
756 }
refresh()757 void Session::refresh()
758 {
759     // attempt to get the shell process to redraw the display
760     //
761     // this requires the program running in the shell
762     // to cooperate by sending an update in response to
763     // a window size change
764     //
765     // the window size is changed twice, first made slightly larger and then
766     // resized back to its normal size so that there is actually a change
767     // in the window size (some shells do nothing if the
768     // new and old sizes are the same)
769     //
770     // if there is a more 'correct' way to do this, please
771     // send an email with method or patches to konsole-devel@kde.org
772 
773     const QSize existingSize = _shellProcess->windowSize();
774     _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height());
775     // introduce small delay to avoid changing size too quickly
776     QThread::usleep(500);
777     _shellProcess->setWindowSize(existingSize.width(), existingSize.height());
778 }
779 
sendSignal(int signal)780 void Session::sendSignal(int signal)
781 {
782     const ProcessInfo *process = getProcessInfo();
783     bool ok = false;
784     int pid;
785     pid = process->foregroundPid(&ok);
786 
787     if (ok) {
788         ::kill(pid, signal);
789     } else {
790         qWarning() << "foreground process id not set, unable to send signal " << signal;
791     }
792 }
793 
reportColor(SessionAttributes r,const QColor & c,uint terminator)794 void Session::reportColor(SessionAttributes r, const QColor &c, uint terminator)
795 {
796 #define to65k(a) (QStringLiteral("%1").arg(int(((a)*0xFFFF)), 4, 16, QLatin1Char('0')))
797     QString msg = QStringLiteral("\033]%1;rgb:").arg(r) + to65k(c.redF()) + QLatin1Char('/') + to65k(c.greenF()) + QLatin1Char('/') + to65k(c.blueF());
798 
799     // Match termination of OSC reply to termination of OSC request.
800     if (terminator == '\a') { // non standard BEL terminator
801         msg += QLatin1Char('\a');
802     } else { // standard 7-bit ST terminator
803         msg += QStringLiteral("\033\\");
804     }
805     _emulation->sendString(msg.toUtf8());
806 #undef to65k
807 }
808 
reportForegroundColor(const QColor & c,uint terminator)809 void Session::reportForegroundColor(const QColor &c, uint terminator)
810 {
811     reportColor(SessionAttributes::TextColor, c, terminator);
812 }
813 
reportBackgroundColor(const QColor & c,uint terminator)814 void Session::reportBackgroundColor(const QColor &c, uint terminator)
815 {
816     reportColor(SessionAttributes::BackgroundColor, c, terminator);
817 }
818 
kill(int signal)819 bool Session::kill(int signal)
820 {
821     if (processId() <= 0) {
822         return false;
823     }
824 
825     int result = ::kill(processId(), signal);
826 
827     if (result == 0) {
828         return _shellProcess->waitForFinished(1000);
829     } else {
830         return false;
831     }
832 }
833 
close()834 void Session::close()
835 {
836     if (isRunning()) {
837         if (!closeInNormalWay()) {
838             closeInForceWay();
839         }
840     } else {
841         // terminal process has finished, just close the session
842         QTimer::singleShot(1, this, &Konsole::Session::finished);
843     }
844 }
845 
closeInNormalWay()846 bool Session::closeInNormalWay()
847 {
848     _autoClose = true;
849     _closePerUserRequest = true;
850 
851     // for the possible case where following events happen in sequence:
852     //
853     // 1). the terminal process crashes
854     // 2). the tab stays open and displays warning message
855     // 3). the user closes the tab explicitly
856     //
857     if (!isRunning()) {
858         Q_EMIT finished();
859         return true;
860     }
861 
862     // try SIGHUP, afterwards do hard kill
863     // this is the sequence used by most other terminal emulators like xterm, gnome-terminal, ...
864     // see bug 401898 for details about tries to have some "soft-terminate" via EOF character
865     if (kill(SIGHUP)) {
866         return true;
867     }
868 
869     qWarning() << "Process " << processId() << " did not die with SIGHUP";
870     _shellProcess->closePty();
871     return (_shellProcess->waitForFinished(1000));
872 }
873 
closeInForceWay()874 bool Session::closeInForceWay()
875 {
876     _autoClose = true;
877     _closePerUserRequest = true;
878 
879     if (kill(SIGKILL)) {
880         return true;
881     } else {
882         qWarning() << "Process " << processId() << " did not die with SIGKILL";
883         return false;
884     }
885 }
886 
sendTextToTerminal(const QString & text,const QChar & eol) const887 void Session::sendTextToTerminal(const QString &text, const QChar &eol) const
888 {
889     if (isReadOnly()) {
890         return;
891     }
892 
893     if (eol.isNull()) {
894         _emulation->sendText(text);
895     } else {
896         _emulation->sendText(text + eol);
897     }
898 }
899 
900 // Only D-Bus calls this function (via SendText or runCommand)
sendText(const QString & text) const901 void Session::sendText(const QString &text) const
902 {
903     if (isReadOnly()) {
904         return;
905     }
906 
907 #if !defined(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS)
908     if (show_disallow_certain_dbus_methods_message) {
909         KNotification::event(KNotification::Warning,
910                              QStringLiteral("Konsole D-Bus Warning"),
911                              i18n("The D-Bus methods sendText/runCommand were just used.  There are security concerns about allowing these methods to be "
912                                   "public.  If desired, these methods can be changed to internal use only by re-compiling Konsole. <p>This warning will only "
913                                   "show once for this Konsole instance.</p>"));
914 
915         show_disallow_certain_dbus_methods_message = false;
916     }
917 #endif
918 
919     _emulation->sendText(text);
920 }
921 
922 // Only D-Bus calls this function
runCommand(const QString & command) const923 void Session::runCommand(const QString &command) const
924 {
925     if (isReadOnly()) {
926         return;
927     }
928 
929     sendText(command + QLatin1Char('\n'));
930 }
931 
sendMouseEvent(int buttons,int column,int line,int eventType)932 void Session::sendMouseEvent(int buttons, int column, int line, int eventType)
933 {
934     if (isReadOnly()) {
935         return;
936     }
937 
938     _emulation->sendMouseEvent(buttons, column, line, eventType);
939 }
940 
done(int exitCode,QProcess::ExitStatus exitStatus)941 void Session::done(int exitCode, QProcess::ExitStatus exitStatus)
942 {
943     // This slot should be triggered only one time
944     disconnect(_shellProcess, QOverload<int, QProcess::ExitStatus>::of(&Konsole::Pty::finished), this, &Konsole::Session::done);
945 
946     if (!_autoClose) {
947         _userTitle = i18nc("@info:shell This session is done", "Finished");
948         Q_EMIT sessionAttributeChanged();
949         return;
950     }
951 
952     if (_closePerUserRequest) {
953         Q_EMIT finished();
954         return;
955     }
956 
957     QString message;
958 
959     if (exitCode != 0) {
960         if (exitStatus != QProcess::NormalExit) {
961             message = i18n("Program '%1' crashed.", _program);
962         } else {
963             message = i18n("Program '%1' exited with status %2.", _program, exitCode);
964         }
965 
966         // FIXME: See comments in Session::silenceTimerDone()
967         KNotification::event(QStringLiteral("Finished"), message, QPixmap(), QApplication::activeWindow(), KNotification::CloseWhenWidgetActivated);
968     }
969 
970     if (exitStatus != QProcess::NormalExit) {
971         // this seeming duplicated line is for the case when exitCode is 0
972         message = i18n("Program '%1' crashed.", _program);
973         terminalWarning(message);
974     } else {
975         Q_EMIT finished();
976     }
977 }
978 
emulation() const979 Emulation *Session::emulation() const
980 {
981     return _emulation;
982 }
983 
keyBindings() const984 QString Session::keyBindings() const
985 {
986     return _emulation->keyBindings();
987 }
988 
environment() const989 QStringList Session::environment() const
990 {
991     return _environment;
992 }
993 
setEnvironment(const QStringList & environment)994 void Session::setEnvironment(const QStringList &environment)
995 {
996     if (isReadOnly()) {
997         return;
998     }
999 
1000     _environment = environment;
1001 }
1002 
addEnvironmentEntry(const QString & entry)1003 void Session::addEnvironmentEntry(const QString &entry)
1004 {
1005     _environment << entry;
1006 }
1007 
sessionId() const1008 int Session::sessionId() const
1009 {
1010     return _sessionId;
1011 }
1012 
setKeyBindings(const QString & name)1013 void Session::setKeyBindings(const QString &name)
1014 {
1015     _emulation->setKeyBindings(name);
1016 }
1017 
setTitle(TitleRole role,const QString & newTitle)1018 void Session::setTitle(TitleRole role, const QString &newTitle)
1019 {
1020     if (title(role) != newTitle) {
1021         if (role == NameRole) {
1022             _nameTitle = newTitle;
1023         } else if (role == DisplayedTitleRole) {
1024             _displayTitle = newTitle;
1025         }
1026 
1027         Q_EMIT sessionAttributeChanged();
1028     }
1029 }
1030 
title(TitleRole role) const1031 QString Session::title(TitleRole role) const
1032 {
1033     if (role == NameRole) {
1034         return _nameTitle;
1035     } else if (role == DisplayedTitleRole) {
1036         return _displayTitle;
1037     } else {
1038         return QString();
1039     }
1040 }
1041 
getProcessInfo()1042 ProcessInfo *Session::getProcessInfo()
1043 {
1044     ProcessInfo *process = nullptr;
1045 
1046     if (isForegroundProcessActive() && updateForegroundProcessInfo()) {
1047         process = _foregroundProcessInfo;
1048     } else {
1049         updateSessionProcessInfo();
1050         process = _sessionProcessInfo;
1051     }
1052 
1053     return process;
1054 }
1055 
updateSessionProcessInfo()1056 void Session::updateSessionProcessInfo()
1057 {
1058     Q_ASSERT(_shellProcess);
1059 
1060     bool ok;
1061     // The checking for pid changing looks stupid, but it is needed
1062     // at the moment to workaround the problem that processId() might
1063     // return 0
1064     if ((_sessionProcessInfo == nullptr) || (processId() != 0 && processId() != _sessionProcessInfo->pid(&ok))) {
1065         delete _sessionProcessInfo;
1066         _sessionProcessInfo = ProcessInfo::newInstance(processId());
1067         _sessionProcessInfo->setUserHomeDir();
1068     }
1069     _sessionProcessInfo->update();
1070 }
1071 
updateForegroundProcessInfo()1072 bool Session::updateForegroundProcessInfo()
1073 {
1074     Q_ASSERT(_shellProcess);
1075 
1076     const int foregroundPid = _shellProcess->foregroundProcessGroup();
1077     if (foregroundPid != _foregroundPid) {
1078         delete _foregroundProcessInfo;
1079         _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid);
1080         _foregroundPid = foregroundPid;
1081     }
1082 
1083     if (_foregroundProcessInfo != nullptr) {
1084         _foregroundProcessInfo->update();
1085         return _foregroundProcessInfo->isValid();
1086     } else {
1087         return false;
1088     }
1089 }
1090 
isRemote()1091 bool Session::isRemote()
1092 {
1093     ProcessInfo *process = getProcessInfo();
1094 
1095     bool ok = false;
1096     return (process->name(&ok) == QLatin1String("ssh") && ok);
1097 }
1098 
getDynamicTitle()1099 QString Session::getDynamicTitle()
1100 {
1101     ProcessInfo *process = getProcessInfo();
1102 
1103     // format tab titles using process info
1104     bool ok = false;
1105     if (process->name(&ok) == QLatin1String("ssh") && ok) {
1106         SSHProcessInfo sshInfo(*process);
1107         return sshInfo.format(tabTitleFormat(Session::RemoteTabTitle));
1108     }
1109 
1110     /*
1111      * Parses an input string, looking for markers beginning with a '%'
1112      * character and returns a string with the markers replaced
1113      * with information from this process description.
1114      * <br>
1115      * The markers recognized are:
1116      * <ul>
1117      * <li> %B - User's Bourne prompt sigil ($, or # for superuser). </li>
1118      * <li> %u - Name of the user which owns the process. </li>
1119      * <li> %n - Replaced with the name of the process.   </li>
1120      * <li> %d - Replaced with the last part of the path name of the
1121      *      process' current working directory.
1122      *
1123      *      (eg. if the current directory is '/home/bob' then
1124      *      'bob' would be returned)
1125      * </li>
1126      * <li> %D - Replaced with the current working directory of the process. </li>
1127      * </ul>
1128      */
1129     QString title = tabTitleFormat(Session::LocalTabTitle);
1130     // search for and replace known marker
1131 
1132     int UID = process->userId(&ok);
1133     if (!ok) {
1134         title.replace(QLatin1String("%B"), QStringLiteral("-"));
1135     } else {
1136         // title.replace(QLatin1String("%I"), QString::number(UID));
1137         if (UID == 0) {
1138             title.replace(QLatin1String("%B"), QStringLiteral("#"));
1139         } else {
1140             title.replace(QLatin1String("%B"), QStringLiteral("$"));
1141         }
1142     }
1143 
1144     title.replace(QLatin1String("%u"), process->userName());
1145     title.replace(QLatin1String("%h"), Konsole::ProcessInfo::localHost());
1146     title.replace(QLatin1String("%n"), process->name(&ok));
1147 
1148     QString dir = _reportedWorkingUrl.toLocalFile();
1149     ok = true;
1150     if (dir.isEmpty()) {
1151         // update current directory from process
1152         updateWorkingDirectory();
1153         // Previous process may have been freed in updateSessionProcessInfo()
1154         process = getProcessInfo();
1155         dir = process->currentDir(&ok);
1156     }
1157     if (!ok) {
1158         title.replace(QLatin1String("%d"), QStringLiteral("-"));
1159         title.replace(QLatin1String("%D"), QStringLiteral("-"));
1160     } else {
1161         // allow for shortname to have the ~ as homeDir
1162         const QString homeDir = process->userHomeDir();
1163         if (!homeDir.isEmpty()) {
1164             if (dir.startsWith(homeDir)) {
1165                 dir.remove(0, homeDir.length());
1166                 dir.prepend(QLatin1Char('~'));
1167             }
1168         }
1169         title.replace(QLatin1String("%D"), dir);
1170         title.replace(QLatin1String("%d"), process->formatShortDir(dir));
1171     }
1172 
1173     return title;
1174 }
1175 
getUrl()1176 QUrl Session::getUrl()
1177 {
1178     if (_reportedWorkingUrl.isValid()) {
1179         return _reportedWorkingUrl;
1180     }
1181 
1182     QString path;
1183 
1184     updateSessionProcessInfo();
1185     if (_sessionProcessInfo->isValid()) {
1186         bool ok = false;
1187 
1188         // check if foreground process is bookmark-able
1189         if (isForegroundProcessActive() && _foregroundProcessInfo->isValid()) {
1190             // for remote connections, save the user and host
1191             // bright ideas to get the directory at the other end are welcome :)
1192             if (_foregroundProcessInfo->name(&ok) == QLatin1String("ssh") && ok) {
1193                 SSHProcessInfo sshInfo(*_foregroundProcessInfo);
1194 
1195                 QUrl url;
1196                 url.setScheme(QStringLiteral("ssh"));
1197                 url.setUserName(sshInfo.userName());
1198                 url.setHost(sshInfo.host());
1199 
1200                 const QString port = sshInfo.port();
1201                 if (!port.isEmpty() && port != QLatin1String("22")) {
1202                     url.setPort(port.toInt());
1203                 }
1204                 return url;
1205             } else {
1206                 path = _foregroundProcessInfo->currentDir(&ok);
1207                 if (!ok) {
1208                     path.clear();
1209                 }
1210             }
1211         } else { // otherwise use the current working directory of the shell process
1212             path = _sessionProcessInfo->currentDir(&ok);
1213             if (!ok) {
1214                 path.clear();
1215             }
1216         }
1217     }
1218 
1219     return QUrl::fromLocalFile(path);
1220 }
1221 
setIconName(const QString & iconName)1222 void Session::setIconName(const QString &iconName)
1223 {
1224     if (iconName != _iconName) {
1225         _iconName = iconName;
1226         Q_EMIT sessionAttributeChanged();
1227     }
1228 }
1229 
setIconText(const QString & iconText)1230 void Session::setIconText(const QString &iconText)
1231 {
1232     _iconText = iconText;
1233 }
1234 
iconName() const1235 QString Session::iconName() const
1236 {
1237     return _iconName;
1238 }
1239 
iconText() const1240 QString Session::iconText() const
1241 {
1242     return _iconText;
1243 }
1244 
setHistoryType(const HistoryType & hType)1245 void Session::setHistoryType(const HistoryType &hType)
1246 {
1247     _emulation->setHistory(hType);
1248 }
1249 
historyType() const1250 const HistoryType &Session::historyType() const
1251 {
1252     return _emulation->history();
1253 }
1254 
clearHistory()1255 void Session::clearHistory()
1256 {
1257     _emulation->clearHistory();
1258 }
1259 
arguments() const1260 QStringList Session::arguments() const
1261 {
1262     return _arguments;
1263 }
1264 
program() const1265 QString Session::program() const
1266 {
1267     return _program;
1268 }
1269 
isMonitorActivity() const1270 bool Session::isMonitorActivity() const
1271 {
1272     return _monitorActivity;
1273 }
isMonitorSilence() const1274 bool Session::isMonitorSilence() const
1275 {
1276     return _monitorSilence;
1277 }
1278 
setMonitorActivity(bool monitor)1279 void Session::setMonitorActivity(bool monitor)
1280 {
1281     if (_monitorActivity == monitor) {
1282         return;
1283     }
1284 
1285     _monitorActivity = monitor;
1286     _notifiedActivity = false;
1287 
1288     // This timer is meaningful only after activity has been notified
1289     _activityTimer->stop();
1290 
1291     setPendingNotification(Notification::Activity, false);
1292 }
1293 
setMonitorSilence(bool monitor)1294 void Session::setMonitorSilence(bool monitor)
1295 {
1296     if (_monitorSilence == monitor) {
1297         return;
1298     }
1299 
1300     _monitorSilence = monitor;
1301     if (_monitorSilence) {
1302         _silenceTimer->start(_silenceSeconds * 1000);
1303     } else {
1304         _silenceTimer->stop();
1305     }
1306 
1307     setPendingNotification(Notification::Silence, false);
1308 }
1309 
setMonitorSilenceSeconds(int seconds)1310 void Session::setMonitorSilenceSeconds(int seconds)
1311 {
1312     _silenceSeconds = seconds;
1313     if (_monitorSilence) {
1314         _silenceTimer->start(_silenceSeconds * 1000);
1315     }
1316 }
1317 
setAddToUtmp(bool add)1318 void Session::setAddToUtmp(bool add)
1319 {
1320     _addToUtmp = add;
1321 }
1322 
setAutoClose(bool close)1323 void Session::setAutoClose(bool close)
1324 {
1325     _autoClose = close;
1326 }
1327 
autoClose() const1328 bool Session::autoClose() const
1329 {
1330     return _autoClose;
1331 }
1332 
setFlowControlEnabled(bool enabled)1333 void Session::setFlowControlEnabled(bool enabled)
1334 {
1335     if (isReadOnly()) {
1336         return;
1337     }
1338 
1339     _flowControlEnabled = enabled;
1340 
1341     if (_shellProcess != nullptr) {
1342         _shellProcess->setFlowControlEnabled(_flowControlEnabled);
1343     }
1344 
1345     Q_EMIT flowControlEnabledChanged(enabled);
1346 }
flowControlEnabled() const1347 bool Session::flowControlEnabled() const
1348 {
1349     if (_shellProcess != nullptr) {
1350         return _shellProcess->flowControlEnabled();
1351     } else {
1352         return _flowControlEnabled;
1353     }
1354 }
fireZModemDownloadDetected()1355 void Session::fireZModemDownloadDetected()
1356 {
1357     if (!_zmodemBusy) {
1358         QTimer::singleShot(10, this, &Konsole::Session::zmodemDownloadDetected);
1359         _zmodemBusy = true;
1360     }
1361 }
1362 
fireZModemUploadDetected()1363 void Session::fireZModemUploadDetected()
1364 {
1365     if (!_zmodemBusy) {
1366         QTimer::singleShot(10, this, &Konsole::Session::zmodemUploadDetected);
1367     }
1368 }
1369 
cancelZModem()1370 void Session::cancelZModem()
1371 {
1372     _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort
1373     _zmodemBusy = false;
1374 }
1375 
startZModem(const QString & zmodem,const QString & dir,const QStringList & list)1376 void Session::startZModem(const QString &zmodem, const QString &dir, const QStringList &list)
1377 {
1378     _zmodemBusy = true;
1379     _zmodemProc = new KProcess();
1380     _zmodemProc->setOutputChannelMode(KProcess::SeparateChannels);
1381 
1382     *_zmodemProc << zmodem << QStringLiteral("-v") << QStringLiteral("-e") << list;
1383 
1384     if (!dir.isEmpty()) {
1385         _zmodemProc->setWorkingDirectory(dir);
1386     }
1387 
1388     connect(_zmodemProc, &KProcess::readyReadStandardOutput, this, &Konsole::Session::zmodemReadAndSendBlock);
1389     connect(_zmodemProc, &KProcess::readyReadStandardError, this, &Konsole::Session::zmodemReadStatus);
1390     connect(_zmodemProc, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &Konsole::Session::zmodemFinished);
1391 
1392     _zmodemProc->start();
1393 
1394     disconnect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
1395     connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::zmodemReceiveBlock);
1396 
1397     _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, i18n("ZModem Progress"));
1398 
1399     connect(_zmodemProgress, &Konsole::ZModemDialog::zmodemCancel, this, &Konsole::Session::zmodemFinished);
1400 
1401     _zmodemProgress->show();
1402 }
1403 
zmodemReadAndSendBlock()1404 void Session::zmodemReadAndSendBlock()
1405 {
1406     _zmodemProc->setReadChannel(QProcess::StandardOutput);
1407     QByteArray data = _zmodemProc->read(ZMODEM_BUFFER_SIZE);
1408 
1409     while (data.count() != 0) {
1410         _shellProcess->sendData(data);
1411         data = _zmodemProc->read(ZMODEM_BUFFER_SIZE);
1412     }
1413 }
1414 
zmodemReadStatus()1415 void Session::zmodemReadStatus()
1416 {
1417     _zmodemProc->setReadChannel(QProcess::StandardError);
1418     QByteArray msg = _zmodemProc->readAll();
1419     while (!msg.isEmpty()) {
1420         int i = msg.indexOf('\015');
1421         int j = msg.indexOf('\012');
1422         QByteArray txt;
1423         if ((i != -1) && ((j == -1) || (i < j))) {
1424             msg = msg.mid(i + 1);
1425         } else if (j != -1) {
1426             txt = msg.left(j);
1427             msg = msg.mid(j + 1);
1428         } else {
1429             txt = msg;
1430             msg.truncate(0);
1431         }
1432         if (!txt.isEmpty()) {
1433             _zmodemProgress->addText(QString::fromLocal8Bit(txt));
1434         }
1435     }
1436 }
1437 
zmodemReceiveBlock(const char * data,int len)1438 void Session::zmodemReceiveBlock(const char *data, int len)
1439 {
1440     static int steps = 0;
1441     QByteArray bytes(data, len);
1442 
1443     _zmodemProc->write(bytes);
1444 
1445     // Provide some feedback to dialog
1446     if (steps > 100) {
1447         _zmodemProgress->addProgressText(QStringLiteral("."));
1448         steps = 0;
1449     }
1450     steps++;
1451 }
1452 
zmodemFinished()1453 void Session::zmodemFinished()
1454 {
1455     /* zmodemFinished() is called by QProcess's finished() and
1456        ZModemDialog's user1Clicked(). Therefore, an invocation by
1457        user1Clicked() will recursively invoke this function again
1458        when the KProcess is deleted! */
1459     if (_zmodemProc != nullptr) {
1460         KProcess *process = _zmodemProc;
1461         _zmodemProc = nullptr; // Set _zmodemProc to 0 avoid recursive invocations!
1462         _zmodemBusy = false;
1463         delete process; // Now, the KProcess may be disposed safely.
1464 
1465         disconnect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::zmodemReceiveBlock);
1466         connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock);
1467 
1468         _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort
1469         _shellProcess->sendData(QByteArrayLiteral("\001\013\n")); // Try to get prompt back
1470         _zmodemProgress->transferDone();
1471     }
1472 }
1473 
onReceiveBlock(const char * buf,int len)1474 void Session::onReceiveBlock(const char *buf, int len)
1475 {
1476     handleActivity();
1477     _emulation->receiveData(buf, len);
1478 }
1479 
size()1480 QSize Session::size()
1481 {
1482     return _emulation->imageSize();
1483 }
1484 
setSize(const QSize & size)1485 void Session::setSize(const QSize &size)
1486 {
1487     if ((size.width() <= 1) || (size.height() <= 1)) {
1488         return;
1489     }
1490 
1491     Q_EMIT resizeRequest(size);
1492 }
1493 
preferredSize() const1494 QSize Session::preferredSize() const
1495 {
1496     return _preferredSize;
1497 }
1498 
setPreferredSize(const QSize & size)1499 void Session::setPreferredSize(const QSize &size)
1500 {
1501     _preferredSize = size;
1502 }
1503 
processId() const1504 int Session::processId() const
1505 {
1506     return _shellProcess->processId();
1507 }
1508 
setTitle(int role,const QString & title)1509 void Session::setTitle(int role, const QString &title)
1510 {
1511     switch (role) {
1512     case 0:
1513         setTitle(Session::NameRole, title);
1514         break;
1515     case 1:
1516         setTitle(Session::DisplayedTitleRole, title);
1517 
1518         // without these, that title will be overridden by the expansion of
1519         // title format shortly after, which will confuses users.
1520         _localTabTitleFormat = title;
1521         _remoteTabTitleFormat = title;
1522 
1523         break;
1524     }
1525 }
1526 
title(int role) const1527 QString Session::title(int role) const
1528 {
1529     switch (role) {
1530     case 0:
1531         return title(Session::NameRole);
1532     case 1:
1533         return title(Session::DisplayedTitleRole);
1534     default:
1535         return QString();
1536     }
1537 }
1538 
setTabTitleFormat(int context,const QString & format)1539 void Session::setTabTitleFormat(int context, const QString &format)
1540 {
1541     switch (context) {
1542     case 0:
1543         setTabTitleFormat(Session::LocalTabTitle, format);
1544         break;
1545     case 1:
1546         setTabTitleFormat(Session::RemoteTabTitle, format);
1547         break;
1548     }
1549 }
1550 
tabTitleFormat(int context) const1551 QString Session::tabTitleFormat(int context) const
1552 {
1553     switch (context) {
1554     case 0:
1555         return tabTitleFormat(Session::LocalTabTitle);
1556     case 1:
1557         return tabTitleFormat(Session::RemoteTabTitle);
1558     default:
1559         return QString();
1560     }
1561 }
1562 
setHistorySize(int lines)1563 void Session::setHistorySize(int lines)
1564 {
1565     if (isReadOnly()) {
1566         return;
1567     }
1568 
1569     if (lines < 0) {
1570         setHistoryType(HistoryTypeFile());
1571     } else if (lines == 0) {
1572         setHistoryType(HistoryTypeNone());
1573     } else {
1574         setHistoryType(CompactHistoryType(lines));
1575     }
1576 }
1577 
historySize() const1578 int Session::historySize() const
1579 {
1580     const HistoryType &currentHistory = historyType();
1581 
1582     if (currentHistory.isEnabled()) {
1583         if (currentHistory.isUnlimited()) {
1584             return -1;
1585         } else {
1586             return currentHistory.maximumLineCount();
1587         }
1588     } else {
1589         return 0;
1590     }
1591 }
1592 
profile()1593 QString Session::profile()
1594 {
1595     return SessionManager::instance()->sessionProfile(this)->name();
1596 }
1597 
setProfile(const QString & profileName)1598 void Session::setProfile(const QString &profileName)
1599 {
1600     const QList<Profile::Ptr> profiles = ProfileManager::instance()->allProfiles();
1601     for (const Profile::Ptr &profile : profiles) {
1602         if (profile->name() == profileName) {
1603             SessionManager::instance()->setSessionProfile(this, profile);
1604         }
1605     }
1606 }
1607 
foregroundProcessId()1608 int Session::foregroundProcessId()
1609 {
1610     int pid;
1611 
1612     bool ok = false;
1613     pid = getProcessInfo()->pid(&ok);
1614     if (!ok) {
1615         pid = -1;
1616     }
1617 
1618     return pid;
1619 }
1620 
isForegroundProcessActive()1621 bool Session::isForegroundProcessActive()
1622 {
1623     const auto pid = processId();
1624     const auto fgid = _shellProcess->foregroundProcessGroup();
1625 
1626     // On FreeBSD, after exiting the shell, the foreground GID is
1627     // an invalid value, and the "shell" PID is 0. Those are not equal,
1628     // so the check below would return true.
1629     if (pid == 0) {
1630         return false;
1631     }
1632 
1633     // This check is wrong when Konsole is started with '-e cmd'
1634     // as there will only be one process.
1635     // See BKO 134581 for no popup when closing session
1636     return (pid != fgid);
1637 }
1638 
foregroundProcessName()1639 QString Session::foregroundProcessName()
1640 {
1641     QString name;
1642 
1643     if (updateForegroundProcessInfo()) {
1644         bool ok = false;
1645         name = _foregroundProcessInfo->name(&ok);
1646         if (!ok) {
1647             name.clear();
1648         }
1649     }
1650 
1651     return name;
1652 }
1653 
saveSession(KConfigGroup & group)1654 void Session::saveSession(KConfigGroup &group)
1655 {
1656     group.writePathEntry("WorkingDir", currentWorkingDirectory());
1657     group.writeEntry("LocalTab", tabTitleFormat(LocalTabTitle));
1658     group.writeEntry("RemoteTab", tabTitleFormat(RemoteTabTitle));
1659     group.writeEntry("TabColor", color().isValid() ? color().name(QColor::HexArgb) : QString());
1660     group.writeEntry("SessionGuid", _uniqueIdentifier.toString());
1661     group.writeEntry("Encoding", QString::fromUtf8(codec()));
1662 }
1663 
restoreSession(KConfigGroup & group)1664 void Session::restoreSession(KConfigGroup &group)
1665 {
1666     QString value;
1667 
1668     value = group.readPathEntry("WorkingDir", QString());
1669     if (!value.isEmpty()) {
1670         setInitialWorkingDirectory(value);
1671     }
1672     value = group.readEntry("LocalTab");
1673     if (!value.isEmpty()) {
1674         setTabTitleFormat(LocalTabTitle, value);
1675     }
1676     value = group.readEntry("RemoteTab");
1677     if (!value.isEmpty()) {
1678         setTabTitleFormat(RemoteTabTitle, value);
1679     }
1680     value = group.readEntry("TabColor");
1681     if (!value.isEmpty()) {
1682         setColor(QColor(value));
1683     }
1684     value = group.readEntry("SessionGuid");
1685     if (!value.isEmpty()) {
1686         _uniqueIdentifier = QUuid(value);
1687     }
1688     value = group.readEntry("Encoding");
1689     if (!value.isEmpty()) {
1690         setCodec(value.toUtf8());
1691     }
1692 }
1693 
validDirectory(const QString & dir) const1694 QString Session::validDirectory(const QString &dir) const
1695 {
1696     QString validDir = dir;
1697     if (validDir.isEmpty()) {
1698         validDir = QDir::currentPath();
1699     }
1700 
1701     const QFileInfo fi(validDir);
1702     if (!fi.exists() || !fi.isDir()) {
1703         validDir = QDir::homePath();
1704     }
1705 
1706     return validDir;
1707 }
1708 
setPendingNotification(Session::Notification notification,bool enable)1709 void Session::setPendingNotification(Session::Notification notification, bool enable)
1710 {
1711     if (enable != _activeNotifications.testFlag(notification)) {
1712         _activeNotifications.setFlag(notification, enable);
1713         Q_EMIT notificationsChanged(notification, enable);
1714     }
1715 }
1716 
handleActivity()1717 void Session::handleActivity()
1718 {
1719     // TODO: should this hardcoded interval be user configurable?
1720     const int activityMaskInSeconds = 15;
1721 
1722     if (_monitorActivity && !_notifiedActivity) {
1723         KNotification::event(hasFocus() ? QStringLiteral("Activity") : QStringLiteral("ActivityHidden"),
1724                              i18n("Activity in '%1' (Session '%2')", _displayTitle, _nameTitle),
1725                              QPixmap(),
1726                              QApplication::activeWindow(),
1727                              KNotification::CloseWhenWidgetActivated);
1728 
1729         // mask activity notification for a while to avoid flooding
1730         _notifiedActivity = true;
1731         _activityTimer->start(activityMaskInSeconds * 1000);
1732     }
1733 
1734     // reset the counter for monitoring continuous silence since there is activity
1735     if (_monitorSilence) {
1736         _silenceTimer->start(_silenceSeconds * 1000);
1737     }
1738 
1739     if (_monitorActivity) {
1740         setPendingNotification(Notification::Activity);
1741     }
1742 }
1743 
isReadOnly() const1744 bool Session::isReadOnly() const
1745 {
1746     return _readOnly;
1747 }
1748 
setReadOnly(bool readOnly)1749 void Session::setReadOnly(bool readOnly)
1750 {
1751     if (_readOnly != readOnly) {
1752         _readOnly = readOnly;
1753 
1754         // Needed to update the tab icons and all
1755         // attached views.
1756         Q_EMIT readOnlyChanged();
1757     }
1758 }
1759 
setColor(const QColor & color)1760 void Session::setColor(const QColor &color)
1761 {
1762     _tabColor = color;
1763     Q_EMIT sessionAttributeChanged();
1764 }
1765 
color() const1766 QColor Session::color() const
1767 {
1768     return _tabColor;
1769 }
1770