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 ¤tHistory = 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