Warning, file /utilities/konsole/src/session/SessionController.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0003     SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 // Own
0009 #include "SessionController.h"
0010 
0011 #include "profile/ProfileManager.h"
0012 #include "terminalDisplay/TerminalColor.h"
0013 #include "terminalDisplay/TerminalFonts.h"
0014 
0015 // Qt
0016 #include <QAction>
0017 #include <QApplication>
0018 
0019 #include <QFileDialog>
0020 #include <QIcon>
0021 #include <QKeyEvent>
0022 #include <QList>
0023 #include <QMenu>
0024 #include <QStandardPaths>
0025 #include <QUrl>
0026 
0027 // KDE
0028 #include <KActionCollection>
0029 #include <KActionMenu>
0030 #include <KCodecAction>
0031 #include <KConfigGroup>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 #include <KNotification>
0035 #include <KSelectAction>
0036 #include <KSharedConfig>
0037 #include <KShell>
0038 #include <KStringHandler>
0039 #include <KToggleAction>
0040 #include <KUriFilter>
0041 #include <KXMLGUIBuilder>
0042 #include <KXMLGUIFactory>
0043 #include <KXmlGuiWindow>
0044 
0045 #include <KIO/CommandLauncherJob>
0046 
0047 #include <KIO/JobUiDelegateFactory>
0048 #include <KIO/OpenFileManagerWindowJob>
0049 #include <KIO/OpenUrlJob>
0050 
0051 #include <KFileItemListProperties>
0052 
0053 // Konsole
0054 #include "CopyInputDialog.h"
0055 #include "Emulation.h"
0056 #include "HistorySizeDialog.h"
0057 #include "RenameTabDialog.h"
0058 #include "SaveHistoryTask.h"
0059 #include "ScreenWindow.h"
0060 #include "SearchHistoryTask.h"
0061 #include "konsoledebug.h"
0062 
0063 #include "filterHotSpots/ColorFilter.h"
0064 #include "filterHotSpots/EscapeSequenceUrlFilter.h"
0065 #include "filterHotSpots/FileFilter.h"
0066 #include "filterHotSpots/FileFilterHotspot.h"
0067 #include "filterHotSpots/Filter.h"
0068 #include "filterHotSpots/FilterChain.h"
0069 #include "filterHotSpots/HotSpot.h"
0070 #include "filterHotSpots/RegExpFilter.h"
0071 #include "filterHotSpots/UrlFilter.h"
0072 
0073 #include "history/HistoryType.h"
0074 #include "history/HistoryTypeFile.h"
0075 #include "history/HistoryTypeNone.h"
0076 #include "history/compact/CompactHistoryType.h"
0077 
0078 #include "profile/ProfileList.h"
0079 
0080 #include "SessionGroup.h"
0081 #include "SessionManager.h"
0082 
0083 #include "widgets/EditProfileDialog.h"
0084 #include "widgets/IncrementalSearchBar.h"
0085 
0086 #include "terminalDisplay/TerminalColor.h"
0087 #include "terminalDisplay/TerminalDisplay.h"
0088 
0089 // For Unix signal names
0090 #include <csignal>
0091 
0092 using namespace Konsole;
0093 
0094 QSet<SessionController *> SessionController::_allControllers;
0095 int SessionController::_lastControllerId;
0096 
0097 SessionController::SessionController(Session *sessionParam, TerminalDisplay *viewParam, QObject *parent)
0098     : ViewProperties(parent)
0099     , KXMLGUIClient()
0100     , _copyToGroup(nullptr)
0101     , _profileList(nullptr)
0102     , _sessionIcon(QIcon())
0103     , _sessionIconName(QString())
0104     , _searchFilter(nullptr)
0105     , _urlFilter(nullptr)
0106     , _fileFilter(nullptr)
0107     , _colorFilter(nullptr)
0108     , _copyInputToAllTabsAction(nullptr)
0109     , _findAction(nullptr)
0110     , _findNextAction(nullptr)
0111     , _findPreviousAction(nullptr)
0112     , _interactionTimer(nullptr)
0113     , _searchStartLine(0)
0114     , _prevSearchResultLine(0)
0115     , _codecAction(nullptr)
0116     , _switchProfileMenu(nullptr)
0117     , _webSearchMenu(nullptr)
0118     , _listenForScreenWindowUpdates(false)
0119     , _preventClose(false)
0120     , _selectionEmpty(false)
0121     , _selectionChanged(true)
0122     , _selectedText(QString())
0123     , _showMenuAction(nullptr)
0124     , _bookmarkValidProgramsToClear(QStringList())
0125     , _isSearchBarEnabled(false)
0126     , _searchBar(viewParam->searchBar())
0127     , _monitorProcessFinish(false)
0128     , _monitorOnce(false)
0129     , _escapedUrlFilter(nullptr)
0130 {
0131     Q_ASSERT(sessionParam);
0132     Q_ASSERT(viewParam);
0133 
0134     _sessionDisplayConnection = new SessionDisplayConnection(sessionParam, viewParam, this);
0135     viewParam->setSessionController(this);
0136 
0137     // handle user interface related to session (menus etc.)
0138     if (isKonsolePart()) {
0139         setComponentName(QStringLiteral("konsole"), i18n("Konsole"));
0140         setXMLFile(QStringLiteral("partui.rc"));
0141         setupCommonActions();
0142     } else {
0143         setXMLFile(QStringLiteral("sessionui.rc"));
0144         setupCommonActions();
0145         setupExtraActions();
0146     }
0147 
0148     connect(this, &SessionController::requestPrint, view(), &TerminalDisplay::printScreen);
0149 
0150     actionCollection()->addAssociatedWidget(viewParam);
0151 
0152     const QList<QAction *> actionsList = actionCollection()->actions();
0153     for (QAction *action : actionsList) {
0154         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0155     }
0156 
0157     setIdentifier(++_lastControllerId);
0158     sessionAttributeChanged();
0159 
0160     connect(view(), &TerminalDisplay::compositeFocusChanged, this, &SessionController::viewFocusChangeHandler);
0161 
0162     Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(session());
0163 
0164     // install filter on the view to highlight URLs and files
0165     updateFilterList(currentProfile);
0166 
0167     // listen for changes in session, we might need to change the enabled filters
0168     connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList);
0169 
0170     // listen for session resize requests
0171     connect(session(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest);
0172 
0173     // listen for popup menu requests
0174     connect(view(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu);
0175 
0176     // move view to newest output when keystrokes occur
0177     connect(view(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput);
0178 
0179     // listen to activity / silence notifications from session
0180     connect(session(), &Konsole::Session::notificationsChanged, this, &Konsole::SessionController::sessionNotificationsChanged);
0181     // listen to title and icon changes
0182     connect(session(), &Konsole::Session::sessionAttributeChanged, this, &Konsole::SessionController::sessionAttributeChanged);
0183     connect(session(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged);
0184 
0185     connect(this, &Konsole::SessionController::tabRenamedByUser, session(), &Konsole::Session::tabTitleSetByUser);
0186     connect(this, &Konsole::SessionController::tabColoredByUser, session(), &Konsole::Session::tabColorSetByUser);
0187 
0188     connect(session(), &Konsole::Session::currentDirectoryChanged, this, &Konsole::SessionController::currentDirectoryChanged);
0189 
0190     // listen for color changes
0191     connect(session(), &Konsole::Session::changeBackgroundColorRequest, view()->terminalColor(), &TerminalColor::setBackgroundColor);
0192     connect(session(), &Konsole::Session::changeForegroundColorRequest, view()->terminalColor(), &TerminalColor::setForegroundColor);
0193 
0194     // update the title when the session starts
0195     connect(session(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot);
0196 
0197     // listen for output changes to set activity flag
0198     connect(session()->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity);
0199 
0200     // listen for detection of ZModem transfer
0201     connect(session(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload);
0202     connect(session(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload);
0203 
0204     // listen for flow control status changes
0205     connect(session(), &Konsole::Session::flowControlEnabledChanged, view(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled);
0206     view()->setFlowControlWarningEnabled(session()->flowControlEnabled());
0207 
0208     // take a snapshot of the session state every so often when
0209     // user activity occurs
0210     //
0211     // the timer is owned by the session so that it will be destroyed along
0212     // with the session
0213     _interactionTimer = new QTimer(session());
0214     _interactionTimer->setSingleShot(true);
0215     _interactionTimer->setInterval(2000);
0216     connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
0217     connect(view(), &Konsole::TerminalDisplay::compositeFocusChanged, this, [this](bool focused) {
0218         if (focused) {
0219             interactionHandler();
0220         }
0221     });
0222     connect(view(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::interactionHandler);
0223     connect(session()->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::interactionHandler);
0224 
0225     // xterm '10;?' request
0226     connect(session(), &Konsole::Session::getForegroundColor, this, &Konsole::SessionController::sendForegroundColor);
0227     // xterm '11;?' request
0228     connect(session(), &Konsole::Session::getBackgroundColor, this, &Konsole::SessionController::sendBackgroundColor);
0229 
0230     _allControllers.insert(this);
0231 
0232     // A list of programs that accept Ctrl+C to clear command line used
0233     // before outputting bookmark.
0234     _bookmarkValidProgramsToClear =
0235         QStringList({QStringLiteral("bash"), QStringLiteral("fish"), QStringLiteral("sh"), QStringLiteral("tcsh"), QStringLiteral("zsh")});
0236 
0237     setupSearchBar();
0238     _searchBar->setVisible(_isSearchBarEnabled);
0239 
0240     // Setup default state for mouse tracking
0241     const bool allowMouseTracking = currentProfile->property<bool>(Profile::AllowMouseTracking);
0242     view()->setAllowMouseTracking(allowMouseTracking);
0243     actionCollection()->action(QStringLiteral("allow-mouse-tracking"))->setChecked(allowMouseTracking);
0244 }
0245 
0246 SessionController::~SessionController()
0247 {
0248     _allControllers.remove(this);
0249 
0250     if (factory() != nullptr) {
0251         factory()->removeClient(this);
0252     }
0253 }
0254 void SessionController::trackOutput(QKeyEvent *event)
0255 {
0256     Q_ASSERT(view()->screenWindow());
0257 
0258     // Qt has broken something, so we can't rely on just checking if certain
0259     // keys are passed as modifiers anymore.
0260     const int key = event->key();
0261 
0262     /* clang-format off */
0263     const bool shouldNotTriggerScroll =
0264         key == Qt::Key_Super_L
0265         || key == Qt::Key_Super_R
0266         || key == Qt::Key_Hyper_L
0267         || key == Qt::Key_Hyper_R
0268         || key == Qt::Key_Shift
0269         || key == Qt::Key_Control
0270         || key == Qt::Key_Meta
0271         || key == Qt::Key_Alt
0272         || key == Qt::Key_AltGr
0273         || key == Qt::Key_CapsLock
0274         || key == Qt::Key_NumLock
0275         || key == Qt::Key_ScrollLock;
0276     /* clang-format on */
0277 
0278     // Only jump to the bottom if the user actually typed something in,
0279     // not if the user e. g. just pressed a modifier.
0280     if (event->text().isEmpty() && ((event->modifiers() != 0u) || shouldNotTriggerScroll)) {
0281         return;
0282     }
0283 
0284     view()->screenWindow()->setTrackOutput(true);
0285 }
0286 
0287 void SessionController::viewFocusChangeHandler(bool focused)
0288 {
0289     if (focused) {
0290         // notify the world that the view associated with this session has been focused
0291         // used by the view manager to update the title of the MainWindow widget containing the view
0292         Q_EMIT viewFocused(this);
0293 
0294         // when the view is focused, set bell events from the associated session to be delivered
0295         // by the focused view
0296 
0297         // first, disconnect any other views which are listening for bell signals from the session
0298         disconnect(session(), &Konsole::Session::bellRequest, nullptr, nullptr);
0299         // second, connect the newly focused view to listen for the session's bell signal
0300         connect(session(), &Konsole::Session::bellRequest, view(), &Konsole::TerminalDisplay::bell);
0301 
0302         if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) {
0303             // A session with "Copy To All Tabs" has come into focus:
0304             // Ensure that newly created sessions are included in _copyToGroup!
0305             copyInputToAllTabs();
0306         }
0307     }
0308 }
0309 
0310 void SessionController::interactionHandler()
0311 {
0312     if (!_interactionTimer->isActive()) {
0313         _interactionTimer->start();
0314     }
0315 }
0316 
0317 void SessionController::snapshot()
0318 {
0319     Q_ASSERT(!session().isNull());
0320 
0321     QString title = session()->getDynamicTitle();
0322     title = title.simplified();
0323 
0324     // Visualize that the session is broadcasting to others
0325     if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) {
0326         title.append(QLatin1Char('*'));
0327     }
0328 
0329     // use the fallback title if needed
0330     if (title.isEmpty()) {
0331         title = session()->title(Session::NameRole);
0332     }
0333 
0334     QColor color = session()->color();
0335     // use the fallback color if needed
0336     if (!color.isValid()) {
0337         color = QColor(QColor::Invalid);
0338     }
0339 
0340     // apply new title
0341     session()->setTitle(Session::DisplayedTitleRole, title);
0342 
0343     // apply new color
0344     session()->setColor(color);
0345 
0346     // check if foreground process ended and notify if this option was requested
0347     if (_monitorProcessFinish) {
0348         bool isForegroundProcessActive = session()->isForegroundProcessActive();
0349         if (!_previousForegroundProcessName.isNull() && !isForegroundProcessActive) {
0350             KNotification *notification = new KNotification(session()->hasFocus() ? QStringLiteral("ProcessFinished") : QStringLiteral("ProcessFinishedHidden"),
0351                                                             KNotification::CloseWhenWindowActivated);
0352             notification->setWindow(view()->windowHandle());
0353             notification->setText(i18n("The process '%1' has finished running in session '%2'", _previousForegroundProcessName, session()->nameTitle()));
0354             auto action = notification->addDefaultAction(i18n("Show session"));
0355             connect(action, &KNotificationAction::activated, this, [this, notification] {
0356                 view()->notificationClicked(notification->xdgActivationToken());
0357             });
0358             notification->sendEvent();
0359             if (_monitorOnce) {
0360                 actionCollection()->action(QStringLiteral("monitor-process-finish"))->setChecked(false);
0361             }
0362         }
0363         _previousForegroundProcessName = isForegroundProcessActive ? session()->foregroundProcessName() : QString();
0364     }
0365 
0366     // do not forget icon
0367     updateSessionIcon();
0368 }
0369 
0370 QString SessionController::currentDir() const
0371 {
0372     return session()->currentWorkingDirectory();
0373 }
0374 
0375 QUrl SessionController::url() const
0376 {
0377     return session()->getUrl();
0378 }
0379 
0380 void SessionController::rename()
0381 {
0382     renameSession();
0383 }
0384 
0385 void SessionController::openUrl(const QUrl &url)
0386 {
0387     // Clear shell's command line
0388     if (!session()->isForegroundProcessActive() && _bookmarkValidProgramsToClear.contains(session()->foregroundProcessName())) {
0389         session()->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C
0390     }
0391 
0392     // handle local paths
0393     if (url.isLocalFile()) {
0394         QString path = url.toLocalFile();
0395         session()->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r'));
0396     } else if (url.scheme().isEmpty()) {
0397         // QUrl couldn't parse what the user entered into the URL field
0398         // so just dump it to the shell
0399         // If you change this, change it also in autotests/BookMarkTest.cpp
0400         QString command = QUrl::fromPercentEncoding(url.toEncoded());
0401         if (!command.isEmpty()) {
0402             session()->sendTextToTerminal(command, QLatin1Char('\r'));
0403         }
0404     } else if (url.scheme() == QLatin1String("ssh")) {
0405         QString sshCommand = QStringLiteral("ssh ");
0406 
0407         if (url.port() > -1) {
0408             sshCommand += QStringLiteral("-p %1 ").arg(url.port());
0409         }
0410         if (!url.userName().isEmpty()) {
0411             sshCommand += (url.userName() + QLatin1Char('@'));
0412         }
0413         if (!url.host().isEmpty()) {
0414             sshCommand += url.host();
0415         }
0416         session()->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
0417 
0418     } else if (url.scheme() == QLatin1String("telnet")) {
0419         QString telnetCommand = QStringLiteral("telnet ");
0420 
0421         if (!url.userName().isEmpty()) {
0422             telnetCommand += QStringLiteral("-l %1 ").arg(url.userName());
0423         }
0424         if (!url.host().isEmpty()) {
0425             telnetCommand += (url.host() + QLatin1Char(' '));
0426         }
0427         if (url.port() > -1) {
0428             telnetCommand += QString::number(url.port());
0429         }
0430 
0431         session()->sendTextToTerminal(telnetCommand, QLatin1Char('\r'));
0432 
0433     } else {
0434         // TODO Implement handling for other Url types
0435 
0436         KMessageBox::error(view()->window(), i18n("Konsole does not know how to open the bookmark: ") + url.toDisplayString());
0437 
0438         qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know"
0439                               << " how to handle the protocol " << url.scheme();
0440     }
0441 }
0442 
0443 void SessionController::setupPrimaryScreenSpecificActions(bool use)
0444 {
0445     KActionCollection *collection = actionCollection();
0446     QAction *clearAction = collection->action(QStringLiteral("clear-history"));
0447     QAction *resetAction = collection->action(QStringLiteral("clear-history-and-reset"));
0448     QAction *selectAllAction = collection->action(QStringLiteral("select-all"));
0449     QAction *selectLineAction = collection->action(QStringLiteral("select-line"));
0450 
0451     // these actions are meaningful only when primary screen is used.
0452     clearAction->setEnabled(use);
0453     resetAction->setEnabled(use);
0454     selectAllAction->setEnabled(use);
0455     selectLineAction->setEnabled(use);
0456 }
0457 
0458 void SessionController::selectionChanged(const bool selectionEmpty)
0459 {
0460     _selectionChanged = true;
0461     _selectionEmpty = selectionEmpty;
0462     updateCopyAction(selectionEmpty);
0463 }
0464 
0465 void SessionController::updateCopyAction(const bool selectionEmpty)
0466 {
0467     QAction *copyAction = actionCollection()->action(QStringLiteral("edit_copy"));
0468     QAction *copyContextMenu = actionCollection()->action(QStringLiteral("edit_copy_contextmenu"));
0469     // copy action is meaningful only when some text is selected.
0470     // Or when semantic integration is used.
0471     bool hasRepl = view() && view()->screenWindow() && view()->screenWindow()->screen() && view()->screenWindow()->screen()->hasRepl();
0472     copyAction->setEnabled(!selectionEmpty || hasRepl);
0473     copyContextMenu->setVisible(!selectionEmpty || hasRepl);
0474     QAction *Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_in"));
0475     Action->setVisible(!selectionEmpty && hasRepl);
0476     Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_out"));
0477     Action->setVisible(!selectionEmpty && hasRepl);
0478     Action = actionCollection()->action(QStringLiteral("edit_copy_contextmenu_in_out"));
0479     Action->setVisible(!selectionEmpty && hasRepl);
0480 }
0481 
0482 void SessionController::updateWebSearchMenu()
0483 {
0484     // reset
0485     _webSearchMenu->setVisible(false);
0486     _webSearchMenu->menu()->clear();
0487 
0488     if (_selectionEmpty) {
0489         return;
0490     }
0491 
0492     if (_selectionChanged) {
0493         _selectedText = view()->screenWindow()->selectedText(Screen::PreserveLineBreaks);
0494         _selectionChanged = false;
0495     }
0496     QString searchText = _selectedText;
0497     searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
0498 
0499     if (searchText.isEmpty()) {
0500         return;
0501     }
0502 
0503     // Is 'Enable Web shortcuts' checked in System Settings?
0504     KSharedConfigPtr kuriikwsConfig = KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"));
0505     if (!kuriikwsConfig->group(QStringLiteral("General")).readEntry("EnableWebShortcuts", true)) {
0506         return;
0507     }
0508 
0509     KUriFilterData filterData(searchText);
0510     filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);
0511 
0512     if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) {
0513         const QStringList searchProviders = filterData.preferredSearchProviders();
0514         if (!searchProviders.isEmpty()) {
0515             _webSearchMenu->setText(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 16)));
0516 
0517             QAction *action = nullptr;
0518 
0519             for (const QString &searchProvider : searchProviders) {
0520                 action = new QAction(searchProvider, _webSearchMenu);
0521                 action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
0522                 action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
0523                 connect(action, &QAction::triggered, this, [this, action]() {
0524                     handleWebShortcutAction(action);
0525                 });
0526                 _webSearchMenu->addAction(action);
0527             }
0528 
0529             _webSearchMenu->addSeparator();
0530 
0531             action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu);
0532             action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0533             connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts);
0534             _webSearchMenu->addAction(action);
0535 
0536             _webSearchMenu->setVisible(true);
0537         }
0538     }
0539 }
0540 
0541 void SessionController::handleWebShortcutAction(QAction *action)
0542 {
0543     if (action == nullptr) {
0544         return;
0545     }
0546 
0547     KUriFilterData filterData(action->data().toString());
0548 
0549     if (KUriFilter::self()->filterUri(filterData, {QStringLiteral("kurisearchfilter")})) {
0550         const QUrl url = filterData.uri();
0551 
0552         auto *job = new KIO::OpenUrlJob(url);
0553         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
0554         job->start();
0555     }
0556 }
0557 
0558 void SessionController::configureWebShortcuts()
0559 {
0560     auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), {QStringLiteral("webshortcuts")});
0561     job->start();
0562 }
0563 
0564 void SessionController::sendSignal(QAction *action)
0565 {
0566     const auto signal = action->data().toInt();
0567     session()->sendSignal(signal);
0568 }
0569 
0570 void SessionController::sendForegroundColor(uint terminator)
0571 {
0572     const QColor c = view()->terminalColor()->foregroundColor();
0573     session()->reportForegroundColor(c, terminator);
0574 }
0575 
0576 void Konsole::SessionController::sendBackgroundColor(uint terminator)
0577 {
0578     const QColor c = view()->terminalColor()->backgroundColor();
0579     session()->reportBackgroundColor(c, terminator);
0580 }
0581 
0582 void SessionController::toggleReadOnly(QAction *action)
0583 {
0584     if (action != nullptr) {
0585         bool readonly = !isReadOnly();
0586         session()->setReadOnly(readonly);
0587     }
0588 }
0589 
0590 void SessionController::toggleAllowMouseTracking(QAction *action)
0591 {
0592     if (action == nullptr) {
0593         // Crash if running in debug build (aka. someone developing)
0594         Q_ASSERT(false && "Invalid function called toggleAllowMouseTracking");
0595         return;
0596     }
0597 
0598     _sessionDisplayConnection->view()->setAllowMouseTracking(action->isChecked());
0599 }
0600 
0601 void SessionController::removeSearchFilter()
0602 {
0603     if (_searchFilter == nullptr) {
0604         return;
0605     }
0606 
0607     view()->filterChain()->removeFilter(_searchFilter);
0608     delete _searchFilter;
0609     _searchFilter = nullptr;
0610 }
0611 
0612 void SessionController::setupSearchBar()
0613 {
0614     connect(_searchBar, &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived);
0615     connect(_searchBar, &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed);
0616     connect(_searchBar, &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom);
0617     connect(_searchBar, &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory);
0618     connect(_searchBar, &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory);
0619     connect(_searchBar,
0620             &Konsole::IncrementalSearchBar::reverseSearchToggled,
0621             this,
0622             &Konsole::SessionController::updateMenuIconsAccordingToReverseSearchSetting);
0623     connect(_searchBar, &Konsole::IncrementalSearchBar::highlightMatchesToggled, this, &Konsole::SessionController::highlightMatches);
0624     connect(_searchBar, &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch);
0625     connect(_searchBar, &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch);
0626 
0627     updateMenuIconsAccordingToReverseSearchSetting();
0628 }
0629 
0630 void SessionController::setShowMenuAction(QAction *action)
0631 {
0632     _showMenuAction = action;
0633 }
0634 
0635 void SessionController::setupCommonActions()
0636 {
0637     KActionCollection *collection = actionCollection();
0638 
0639     // Close Session
0640     QAction *action = collection->addAction(QStringLiteral("close-session"), this, &SessionController::closeSession);
0641     action->setText(i18n("&Close Session"));
0642 
0643     action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
0644     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_W);
0645 
0646     // Open Browser
0647     action = collection->addAction(QStringLiteral("open-browser"), this, &SessionController::openBrowser);
0648     action->setText(i18n("Open File Manager"));
0649     action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
0650 
0651     // Copy and Paste
0652     action = KStandardAction::copy(this, &SessionController::copy, collection);
0653     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_C);
0654     // disabled at first, since nothing has been selected now
0655     action->setEnabled(false);
0656 
0657     // We need a different QAction on the context menu because one will be disabled when there's no selection,
0658     // other will be hidden.
0659     action = collection->addAction(QStringLiteral("edit_copy_contextmenu"));
0660     action->setText(i18n("Copy"));
0661     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0662     action->setVisible(false);
0663     connect(action, &QAction::triggered, this, &SessionController::copy);
0664 
0665     action = collection->addAction(QStringLiteral("split-view-left-right"));
0666     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0667     action->setText(i18n("Split View Left-Right"));
0668     connect(action, &QAction::triggered, this, &SessionController::requestSplitViewLeftRight);
0669 
0670     action = collection->addAction(QStringLiteral("split-view-top-bottom"));
0671     action->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0672     action->setText(i18n("Split View Top-Bottom"));
0673     connect(action, &QAction::triggered, this, &SessionController::requestSplitViewTopBotton);
0674 
0675     action = collection->addAction(QStringLiteral("edit_copy_contextmenu_in_out"));
0676     action->setText(i18n("Copy except prompts"));
0677     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0678     action->setVisible(false);
0679     connect(action, &QAction::triggered, this, &SessionController::copyInputOutput);
0680 
0681     action = collection->addAction(QStringLiteral("edit_copy_contextmenu_in"));
0682     action->setText(i18n("Copy user input"));
0683     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0684     action->setVisible(false);
0685     connect(action, &QAction::triggered, this, &SessionController::copyInput);
0686 
0687     action = collection->addAction(QStringLiteral("edit_copy_contextmenu_out"));
0688     action->setText(i18n("Copy command output"));
0689     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0690     action->setVisible(false);
0691     connect(action, &QAction::triggered, this, &SessionController::copyOutput);
0692 
0693     action = KStandardAction::paste(this, &SessionController::paste, collection);
0694     QList<QKeySequence> pasteShortcut;
0695     pasteShortcut.append(QKeySequence(Konsole::ACCEL | Qt::Key_V));
0696 #ifndef Q_OS_MACOS
0697     // No Insert key on Mac keyboards
0698     pasteShortcut.append(QKeySequence(Qt::SHIFT | Qt::Key_Insert));
0699 #endif
0700     collection->setDefaultShortcuts(action, pasteShortcut);
0701 
0702     action = collection->addAction(QStringLiteral("paste-selection"), this, &SessionController::pasteFromX11Selection);
0703     action->setText(i18n("Paste Selection"));
0704 #ifdef Q_OS_MACOS
0705     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_V);
0706 #else
0707     collection->setDefaultShortcut(action, Qt::CTRL | Qt::SHIFT | Qt::Key_Insert);
0708 #endif
0709 
0710     _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
0711     _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
0712     _webSearchMenu->setVisible(false);
0713     collection->addAction(QStringLiteral("web-search"), _webSearchMenu);
0714 
0715     action = collection->addAction(QStringLiteral("select-all"), this, &SessionController::selectAll);
0716     action->setText(i18n("&Select All"));
0717     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
0718 
0719     action = collection->addAction(QStringLiteral("select-mode"), this, &SessionController::selectMode);
0720     action->setText(i18n("Select &Mode"));
0721     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select")));
0722     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D));
0723 
0724     action = collection->addAction(QStringLiteral("select-line"), this, &SessionController::selectLine);
0725     action->setText(i18n("Select &Line"));
0726 
0727     action = KStandardAction::saveAs(this, &SessionController::saveHistory, collection);
0728     action->setText(i18n("Save Output &As..."));
0729 #ifdef Q_OS_MACOS
0730     action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
0731 #endif
0732 
0733     action = KStandardAction::print(this, &SessionController::requestPrint, collection);
0734     action->setText(i18n("&Print Screen..."));
0735     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_P);
0736 
0737     action = collection->addAction(QStringLiteral("adjust-history"), this, &SessionController::showHistoryOptions);
0738     action->setText(i18n("Adjust Scrollback..."));
0739     action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0740 
0741     action = collection->addAction(QStringLiteral("clear-history"), this, &SessionController::clearHistory);
0742     action->setText(i18n("Clear Scrollback"));
0743     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
0744 
0745     action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, &SessionController::clearHistoryAndReset);
0746     action->setText(i18n("Clear Scrollback and Reset"));
0747     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
0748     collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_K);
0749 
0750     // Profile Options
0751     action = collection->addAction(QStringLiteral("edit-current-profile"), this, &SessionController::editCurrentProfile);
0752     action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
0753     setEditProfileActionText(SessionManager::instance()->sessionProfile(session()));
0754 
0755     _switchProfileMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("exchange-positions")), i18n("Switch Profile"), this);
0756     collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu);
0757     connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu);
0758     _switchProfileMenu->setPopupMode(QToolButton::MenuButtonPopup);
0759 
0760     // History
0761     _findAction = KStandardAction::find(this, &SessionController::searchBarEvent, collection);
0762 
0763     _findNextAction = KStandardAction::findNext(this, &SessionController::findNextInHistory, collection);
0764     _findNextAction->setEnabled(false);
0765 
0766     _findPreviousAction = KStandardAction::findPrev(this, &SessionController::findPreviousInHistory, collection);
0767     _findPreviousAction->setEnabled(false);
0768 
0769 #ifdef Q_OS_MACOS
0770     collection->setDefaultShortcut(_findAction, Qt::CTRL | Qt::Key_F);
0771     collection->setDefaultShortcut(_findNextAction, Qt::CTRL | Qt::Key_G);
0772     collection->setDefaultShortcut(_findPreviousAction, Qt::CTRL | Qt::SHIFT | Qt::Key_G);
0773 #else
0774     collection->setDefaultShortcut(_findAction, Qt::CTRL | Qt::SHIFT | Qt::Key_F);
0775     collection->setDefaultShortcut(_findNextAction, Qt::Key_F3);
0776     collection->setDefaultShortcut(_findPreviousAction, QKeySequence{Qt::SHIFT | Qt::Key_F3});
0777 #endif
0778 
0779     // Character Encoding
0780     _codecAction = new KCodecAction(i18n("Set &Encoding"), this);
0781     _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set")));
0782     collection->addAction(QStringLiteral("set-encoding"), _codecAction);
0783     _codecAction->setCurrentCodec(QString::fromUtf8(session()->codec()));
0784     connect(session(), &Konsole::Session::sessionCodecChanged, this, &Konsole::SessionController::updateCodecAction);
0785 
0786     connect(_codecAction, &KCodecAction::codecNameTriggered, this, [this](const QByteArray &codecName) {
0787         changeCodec(QTextCodec::codecForName(codecName));
0788     });
0789 
0790     connect(_codecAction, &KCodecAction::defaultItemTriggered, this, [this] {
0791         Profile::Ptr profile = SessionManager::instance()->sessionProfile(session());
0792         QByteArray name = profile->defaultEncoding().toUtf8();
0793 
0794         changeCodec(QTextCodec::codecForName(name));
0795     });
0796 
0797     // Mouse tracking enabled
0798     action = collection->addAction(QStringLiteral("allow-mouse-tracking"), this);
0799     connect(action, &QAction::toggled, this, [this, action]() {
0800         toggleAllowMouseTracking(action);
0801     });
0802     action->setText(i18nc("@item:inmenu Allows terminal applications to request mouse tracking", "Allow Mouse Tracking"));
0803     action->setIcon(QIcon::fromTheme(QStringLiteral("input-mouse-symbolic")));
0804     action->setCheckable(true);
0805 
0806     // Read-only
0807     action = collection->addAction(QStringLiteral("view-readonly"), this);
0808     connect(action, &QAction::toggled, this, [this, action]() {
0809         toggleReadOnly(action);
0810     });
0811     action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-Only"));
0812     action->setCheckable(true);
0813     updateReadOnlyActionStates();
0814 }
0815 
0816 void SessionController::setupExtraActions()
0817 {
0818     KActionCollection *collection = actionCollection();
0819 
0820     // Rename Session
0821     QAction *action = collection->addAction(QStringLiteral("rename-session"), this, &SessionController::renameSession);
0822     action->setText(i18n("&Configure or Rename Tab..."));
0823     action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0824     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_S);
0825 
0826     // Copy input to ==> all tabs
0827     auto *copyInputToAllTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-all-tabs"));
0828     copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
0829     copyInputToAllTabsAction->setData(CopyInputToAllTabsMode);
0830     // this action is also used in other place, so remember it
0831     _copyInputToAllTabsAction = copyInputToAllTabsAction;
0832 
0833     // Copy input to ==> selected tabs
0834     auto *copyInputToSelectedTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-selected-tabs"));
0835     copyInputToSelectedTabsAction->setText(i18n("&Select Tabs..."));
0836     collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL | Qt::Key_Period);
0837     copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode);
0838 
0839     // Copy input to ==> none
0840     auto *copyInputToNoneAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-none"));
0841     copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
0842     collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL | Qt::Key_Slash);
0843     copyInputToNoneAction->setData(CopyInputToNoneMode);
0844     copyInputToNoneAction->setChecked(true); // the default state
0845 
0846     // The "Copy Input To" submenu
0847     // The above three choices are represented as combo boxes
0848     auto *copyInputActions = collection->add<KSelectAction>(QStringLiteral("copy-input-to"));
0849     copyInputActions->setText(i18n("Copy Input To"));
0850     copyInputActions->addAction(copyInputToAllTabsAction);
0851     copyInputActions->addAction(copyInputToSelectedTabsAction);
0852     copyInputActions->addAction(copyInputToNoneAction);
0853     connect(copyInputActions, &KSelectAction::actionTriggered, this, &Konsole::SessionController::copyInputActionsTriggered);
0854     _copyInputActions = copyInputActions;
0855 
0856     action = collection->addAction(QStringLiteral("zmodem-upload"), this, &SessionController::zmodemUpload);
0857     action->setText(i18n("&ZModem Upload..."));
0858     action->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
0859     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_U);
0860 
0861     // Monitor
0862     KToggleAction *toggleAction = new KToggleAction(i18n("One-Shot Monitors"), this);
0863     action = collection->addAction(QStringLiteral("monitor-once"), toggleAction);
0864     connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorOnce);
0865     action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn")));
0866 
0867     toggleAction = new KToggleAction(i18n("Monitor for &Prompt"), this);
0868     collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_R);
0869     action = collection->addAction(QStringLiteral("monitor-prompt"), toggleAction);
0870     connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorPrompt);
0871     action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn")));
0872     action->setVisible(false);
0873 
0874     toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this);
0875     collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_A);
0876     action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction);
0877     connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity);
0878     action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn")));
0879 
0880     toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this);
0881     collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_I);
0882     action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction);
0883     connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence);
0884     action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-copy")));
0885 
0886     toggleAction = new KToggleAction(i18n("Monitor for Process Finishing"), this);
0887     action = collection->addAction(QStringLiteral("monitor-process-finish"), toggleAction);
0888     connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorProcessFinish);
0889     action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn-image")));
0890 
0891     // Text Size
0892     action = collection->addAction(QStringLiteral("enlarge-font"), this, &SessionController::increaseFontSize);
0893     action->setText(i18n("Enlarge Font"));
0894     action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more")));
0895     QList<QKeySequence> enlargeFontShortcut;
0896     enlargeFontShortcut.append(QKeySequence(Qt::CTRL | Qt::Key_Plus));
0897     enlargeFontShortcut.append(QKeySequence(Qt::CTRL | Qt::Key_Equal));
0898     collection->setDefaultShortcuts(action, enlargeFontShortcut);
0899 
0900     action = collection->addAction(QStringLiteral("shrink-font"), this, &SessionController::decreaseFontSize);
0901     action->setText(i18n("Shrink Font"));
0902     action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less")));
0903     collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Minus));
0904 
0905     action = collection->addAction(QStringLiteral("reset-font-size"), this, &SessionController::resetFontSize);
0906     action->setText(i18n("Reset Font Size"));
0907     collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_0);
0908 
0909 #ifndef Q_OS_WIN
0910     // Send signal
0911     auto *sendSignalActions = collection->add<KSelectAction>(QStringLiteral("send-signal"));
0912     sendSignalActions->setText(i18n("Send Signal"));
0913     connect(sendSignalActions, &KSelectAction::actionTriggered, this, &Konsole::SessionController::sendSignal);
0914 
0915     action = collection->addAction(QStringLiteral("sigstop-signal"));
0916     action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)"));
0917     action->setData(SIGSTOP);
0918     sendSignalActions->addAction(action);
0919 
0920     action = collection->addAction(QStringLiteral("sigcont-signal"));
0921     action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)"));
0922     action->setData(SIGCONT);
0923     sendSignalActions->addAction(action);
0924 
0925     action = collection->addAction(QStringLiteral("sighup-signal"));
0926     action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)"));
0927     action->setData(SIGHUP);
0928     sendSignalActions->addAction(action);
0929 
0930     action = collection->addAction(QStringLiteral("sigint-signal"));
0931     action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)"));
0932     action->setData(SIGINT);
0933     sendSignalActions->addAction(action);
0934 
0935     action = collection->addAction(QStringLiteral("sigterm-signal"));
0936     action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)"));
0937     action->setData(SIGTERM);
0938     sendSignalActions->addAction(action);
0939 
0940     action = collection->addAction(QStringLiteral("sigkill-signal"));
0941     action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)"));
0942     action->setData(SIGKILL);
0943     sendSignalActions->addAction(action);
0944 
0945     action = collection->addAction(QStringLiteral("sigusr1-signal"));
0946     action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)"));
0947     action->setData(SIGUSR1);
0948     sendSignalActions->addAction(action);
0949 
0950     action = collection->addAction(QStringLiteral("sigusr2-signal"));
0951     action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)"));
0952     action->setData(SIGUSR2);
0953     sendSignalActions->addAction(action);
0954 #endif
0955 }
0956 
0957 void SessionController::switchProfile(const Profile::Ptr &profile)
0958 {
0959     SessionManager::instance()->setSessionProfile(session(), profile);
0960     _switchProfileMenu->setIcon(QIcon::fromTheme(profile->icon()));
0961     updateFilterList(profile);
0962     setEditProfileActionText(profile);
0963 }
0964 
0965 void SessionController::setEditProfileActionText(const Profile::Ptr &profile)
0966 {
0967     QAction *action = actionCollection()->action(QStringLiteral("edit-current-profile"));
0968     if (!profile->isEditable()) {
0969         action->setText(i18n("Create New Profile..."));
0970     } else {
0971         action->setText(i18n("Edit Current Profile..."));
0972     }
0973 }
0974 
0975 void SessionController::prepareSwitchProfileMenu()
0976 {
0977     if (_switchProfileMenu->menu()->isEmpty()) {
0978         _profileList = new ProfileList(false, this);
0979         connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile);
0980     }
0981 
0982     _switchProfileMenu->menu()->clear();
0983     _switchProfileMenu->menu()->addActions(_profileList->actions());
0984 }
0985 void SessionController::updateCodecAction(QTextCodec *codec)
0986 {
0987     _codecAction->setCurrentCodec(QString::fromUtf8(codec->name()));
0988 }
0989 
0990 void SessionController::changeCodec(QTextCodec *codec)
0991 {
0992     session()->setCodec(codec);
0993 }
0994 
0995 void SessionController::editCurrentProfile()
0996 {
0997     auto *dialog = new EditProfileDialog(QApplication::activeWindow());
0998     dialog->setAttribute(Qt::WA_DeleteOnClose);
0999     dialog->setModal(true);
1000 
1001     auto profile = SessionManager::instance()->sessionProfile(session());
1002     auto state = EditProfileDialog::ExistingProfile;
1003     // Don't edit uneditable profiles, instead create a new one
1004     if (!profile->isEditable()) {
1005         auto newProfile = Profile::Ptr(new Profile(profile));
1006         newProfile->clone(profile, true);
1007         const QString uniqueName = ProfileManager::instance()->generateUniqueName();
1008         newProfile->setProperty(Profile::Name, uniqueName);
1009         newProfile->setProperty(Profile::UntranslatedName, uniqueName);
1010         profile = newProfile;
1011         SessionManager::instance()->setSessionProfile(session(), profile);
1012         state = EditProfileDialog::NewProfile;
1013 
1014         connect(dialog, &QDialog::accepted, this, [this, profile]() {
1015             setEditProfileActionText(profile);
1016         });
1017     }
1018 
1019     dialog->setProfile(profile, state);
1020 
1021     dialog->show();
1022 }
1023 
1024 void SessionController::renameSession()
1025 {
1026     const QString sessionLocalTabTitleFormat = session()->tabTitleFormat(Session::LocalTabTitle);
1027     const QString sessionRemoteTabTitleFormat = session()->tabTitleFormat(Session::RemoteTabTitle);
1028     const QColor sessionTabColor = session()->color();
1029 
1030     auto *dialog = new RenameTabDialog(QApplication::activeWindow());
1031     dialog->setAttribute(Qt::WA_DeleteOnClose);
1032     dialog->setModal(true);
1033     dialog->setTabTitleText(sessionLocalTabTitleFormat);
1034     dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat);
1035     dialog->setColor(sessionTabColor);
1036 
1037     if (session()->isRemote()) {
1038         dialog->focusRemoteTabTitleText();
1039     } else {
1040         dialog->focusTabTitleText();
1041     }
1042 
1043     connect(dialog, &QDialog::accepted, this, [=]() {
1044         const QString tabTitle = dialog->tabTitleText();
1045         const QString remoteTabTitle = dialog->remoteTabTitleText();
1046         const QColor tabColor = dialog->color();
1047 
1048         if (tabTitle != sessionLocalTabTitleFormat) {
1049             session()->setTabTitleFormat(Session::LocalTabTitle, tabTitle);
1050             Q_EMIT tabRenamedByUser(true);
1051             // trigger an update of the tab text
1052             snapshot();
1053         }
1054 
1055         if (remoteTabTitle != sessionRemoteTabTitleFormat) {
1056             session()->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle);
1057             Q_EMIT tabRenamedByUser(true);
1058             snapshot();
1059         }
1060 
1061         if (tabColor != sessionTabColor) {
1062             session()->setColor(tabColor);
1063             Q_EMIT tabColoredByUser(true);
1064             snapshot();
1065         }
1066     });
1067 
1068     dialog->show();
1069 }
1070 
1071 // This is called upon Menu->Close Session and right-click on tab->Close Tab
1072 bool SessionController::confirmClose() const
1073 {
1074     if (session()->isForegroundProcessActive()) {
1075         QString title = session()->foregroundProcessName();
1076 
1077         // hard coded for now.  In future make it possible for the user to specify which programs
1078         // are ignored when considering whether to display a confirmation
1079         QStringList ignoreList;
1080         ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1);
1081         if (ignoreList.contains(title)) {
1082             return true;
1083         }
1084 
1085         QString question;
1086         if (title.isEmpty()) {
1087             question = i18n(
1088                 "A program is currently running in this session."
1089                 "  Are you sure you want to close it?");
1090         } else {
1091             question = i18n(
1092                 "The program '%1' is currently running in this session."
1093                 "  Are you sure you want to close it?",
1094                 title);
1095         }
1096 
1097         int result = KMessageBox::warningTwoActions(view()->window(),
1098                                                     question,
1099                                                     i18n("Confirm Close"),
1100                                                     KGuiItem(i18nc("@action:button", "Close Program"), QStringLiteral("application-exit")),
1101                                                     KStandardGuiItem::cancel(),
1102                                                     QStringLiteral("CloseSingleTab"));
1103         return result == KMessageBox::PrimaryAction;
1104     }
1105     return true;
1106 }
1107 bool SessionController::confirmForceClose() const
1108 {
1109     if (session()->isRunning()) {
1110         QString title = session()->program();
1111 
1112         // hard coded for now.  In future make it possible for the user to specify which programs
1113         // are ignored when considering whether to display a confirmation
1114         QStringList ignoreList;
1115         ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1);
1116         if (ignoreList.contains(title)) {
1117             return true;
1118         }
1119 
1120         QString question;
1121         if (title.isEmpty()) {
1122             question = i18n(
1123                 "A program in this session would not die."
1124                 "  Are you sure you want to kill it by force?");
1125         } else {
1126             question = i18n(
1127                 "The program '%1' is in this session would not die."
1128                 "  Are you sure you want to kill it by force?",
1129                 title);
1130         }
1131 
1132         int result = KMessageBox::warningTwoActions(view()->window(),
1133                                                     question,
1134                                                     i18n("Confirm Close"),
1135                                                     KGuiItem(i18nc("@action:button", "Kill Program"), QStringLiteral("application-exit")),
1136                                                     KStandardGuiItem::cancel());
1137         return result == KMessageBox::PrimaryAction;
1138     }
1139     return true;
1140 }
1141 void SessionController::closeSession()
1142 {
1143     if (_preventClose) {
1144         return;
1145     }
1146 
1147     if (!confirmClose()) {
1148         return;
1149     }
1150 
1151     if (!session()->closeInNormalWay()) {
1152         if (!confirmForceClose()) {
1153             return;
1154         }
1155 
1156         if (!session()->closeInForceWay()) {
1157             qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way.";
1158             return;
1159         }
1160     }
1161 
1162     if (factory() != nullptr) {
1163         factory()->removeClient(this);
1164     }
1165 }
1166 
1167 // Trying to open a remote Url may produce unexpected results.
1168 // Therefore, if a remote url, open the user's home path.
1169 // TODO consider: 1) disable menu upon remote session
1170 //   2) transform url to get the desired result (ssh -> sftp, etc)
1171 void SessionController::openBrowser()
1172 {
1173     // if we requested the browser on a file, we can't use OpenUrlJob
1174     // because it does not open the file in a browser, it opens another program
1175     // based on it's mime type.
1176     // so force open dolphin with  it selected.
1177     // TODO: and for people that have other default file browsers such as
1178     // konqueror and krusader?
1179 
1180     if (_currentHotSpot && _currentHotSpot->type() == HotSpot::File) {
1181         auto *fileHotSpot = qobject_cast<FileFilterHotSpot *>(_currentHotSpot.get());
1182         assert(fileHotSpot);
1183         auto *job = new KIO::OpenFileManagerWindowJob();
1184         job->setHighlightUrls({fileHotSpot->fileItem().url()});
1185         job->start();
1186     } else {
1187         const QUrl currentUrl = url().isLocalFile() ? url() : QUrl::fromLocalFile(QDir::homePath());
1188         auto *job = new KIO::OpenUrlJob(currentUrl);
1189         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
1190         job->start();
1191     }
1192 }
1193 
1194 void SessionController::copy()
1195 {
1196     view()->copyToClipboard();
1197 }
1198 
1199 void SessionController::copyInput()
1200 {
1201     view()->copyToClipboard(Screen::ExcludePrompt | Screen::ExcludeOutput);
1202 }
1203 
1204 void SessionController::copyOutput()
1205 {
1206     view()->copyToClipboard(Screen::ExcludePrompt | Screen::ExcludeInput);
1207 }
1208 
1209 void SessionController::copyInputOutput()
1210 {
1211     view()->copyToClipboard(Screen::ExcludePrompt);
1212 }
1213 
1214 void SessionController::paste()
1215 {
1216     view()->pasteFromClipboard();
1217 }
1218 void SessionController::pasteFromX11Selection()
1219 {
1220     view()->pasteFromX11Selection();
1221 }
1222 void SessionController::selectAll()
1223 {
1224     view()->selectAll();
1225 }
1226 void SessionController::selectMode()
1227 {
1228     if (!session().isNull()) {
1229         bool Mode = session()->getSelectMode();
1230         setSelectMode(!Mode);
1231     }
1232 }
1233 void SessionController::setSelectMode(bool mode)
1234 {
1235     if (!session().isNull()) {
1236         session()->setSelectMode(mode);
1237         view()->setSelectMode(mode);
1238     }
1239 }
1240 
1241 void SessionController::selectLine()
1242 {
1243     view()->selectCurrentLine();
1244 }
1245 static const KXmlGuiWindow *findWindow(const QObject *object)
1246 {
1247     // Walk up the QObject hierarchy to find a KXmlGuiWindow.
1248     while (object != nullptr) {
1249         const auto *window = qobject_cast<const KXmlGuiWindow *>(object);
1250         if (window != nullptr) {
1251             return (window);
1252         }
1253         object = object->parent();
1254     }
1255     return (nullptr);
1256 }
1257 
1258 static bool hasTerminalDisplayInSameWindow(const Session *session, const KXmlGuiWindow *window)
1259 {
1260     // Iterate all TerminalDisplays of this Session ...
1261     const QList<TerminalDisplay *> views = session->views();
1262     for (const TerminalDisplay *terminalDisplay : views) {
1263         // ... and check whether a TerminalDisplay has the same
1264         // window as given in the parameter
1265         if (window == findWindow(terminalDisplay)) {
1266             return (true);
1267         }
1268     }
1269     return (false);
1270 }
1271 
1272 void SessionController::copyInputActionsTriggered(QAction *action)
1273 {
1274     const auto mode = action->data().toInt();
1275 
1276     switch (mode) {
1277     case CopyInputToAllTabsMode:
1278         copyInputToAllTabs();
1279         break;
1280     case CopyInputToSelectedTabsMode:
1281         copyInputToSelectedTabs();
1282         break;
1283     case CopyInputToNoneMode:
1284         copyInputToNone();
1285         break;
1286     default:
1287         Q_ASSERT(false);
1288     }
1289 }
1290 
1291 void SessionController::copyInputToAllTabs()
1292 {
1293     if (_copyToGroup == nullptr) {
1294         _copyToGroup = new SessionGroup(this);
1295     }
1296 
1297     // Find our window ...
1298     const KXmlGuiWindow *myWindow = findWindow(view());
1299 
1300     const QList<Session *> sessionsList = SessionManager::instance()->sessions();
1301     QSet<Session *> group(sessionsList.begin(), sessionsList.end());
1302     for (auto session : group) {
1303         // First, ensure that the session is removed
1304         // (necessary to avoid duplicates on addSession()!)
1305         _copyToGroup->removeSession(session);
1306 
1307         // Add current session if it is displayed our window
1308         if (hasTerminalDisplayInSameWindow(session, myWindow)) {
1309             _copyToGroup->addSession(session);
1310         }
1311     }
1312     _copyToGroup->setMasterStatus(session(), true);
1313     _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1314 
1315     snapshot();
1316     Q_EMIT copyInputChanged(this);
1317 }
1318 
1319 void SessionController::copyInputToSelectedTabs(QList<Session *> *sessions)
1320 {
1321     if (_copyToGroup == nullptr) {
1322         _copyToGroup = new SessionGroup(this);
1323         _copyToGroup->addSession(session());
1324         _copyToGroup->setMasterStatus(session(), true);
1325         _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1326     }
1327 
1328     const QList<Session *> sessionsList = _copyToGroup->sessions();
1329     QSet<Session *> currentGroup(sessionsList.begin(), sessionsList.end());
1330 
1331     currentGroup.remove(session());
1332 
1333     auto update = [this](QSet<Session *> newGroup, QSet<Session *> currentGroup) {
1334         const QSet<Session *> completeGroup = newGroup | currentGroup;
1335         for (Session *session : completeGroup) {
1336             if (newGroup.contains(session) && !currentGroup.contains(session)) {
1337                 _copyToGroup->addSession(session);
1338             } else if (!newGroup.contains(session) && currentGroup.contains(session)) {
1339                 _copyToGroup->removeSession(session);
1340             }
1341         }
1342 
1343         _copyToGroup->setMasterStatus(session(), true);
1344         _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1345         snapshot();
1346         Q_EMIT copyInputChanged(this);
1347     };
1348 
1349     if (sessions != nullptr) {
1350         QSet<Session *> newGroup(sessions->begin(), sessions->end());
1351         newGroup.remove(session());
1352         update(newGroup, currentGroup);
1353     } else {
1354         auto *dialog = new CopyInputDialog(view());
1355         dialog->setAttribute(Qt::WA_DeleteOnClose);
1356         dialog->setModal(true);
1357         dialog->setMasterSession(session());
1358         dialog->setChosenSessions(currentGroup);
1359 
1360         connect(dialog, &QDialog::accepted, this, [=]() {
1361             QSet<Session *> newGroup = dialog->chosenSessions();
1362             newGroup.remove(session());
1363             update(newGroup, currentGroup);
1364         });
1365 
1366         dialog->show();
1367     }
1368 }
1369 
1370 void SessionController::copyInputToNone()
1371 {
1372     if (_copyToGroup == nullptr) { // No 'Copy To' is active
1373         return;
1374     }
1375 
1376     // Once Qt5.14+ is the minimum, change to use range constructors
1377     const QList<Session *> groupList = SessionManager::instance()->sessions();
1378     QSet<Session *> group(groupList.begin(), groupList.end());
1379 
1380     for (auto iterator : group) {
1381         Session *s = iterator;
1382 
1383         if (s != session()) {
1384             _copyToGroup->removeSession(iterator);
1385         }
1386     }
1387     delete _copyToGroup;
1388     _copyToGroup = nullptr;
1389     snapshot();
1390     Q_EMIT copyInputChanged(this);
1391 }
1392 
1393 void SessionController::searchClosed()
1394 {
1395     _isSearchBarEnabled = false;
1396     searchHistory(false);
1397 }
1398 
1399 void SessionController::updateFilterList(const Profile::Ptr &profile)
1400 {
1401     if (profile != SessionManager::instance()->sessionProfile(session())) {
1402         return;
1403     }
1404 
1405     auto *filterChain = view()->filterChain();
1406 
1407     const QString currentWordCharacters = profile->wordCharacters();
1408     static QString _wordChars = currentWordCharacters;
1409 
1410     if (profile->underlineFilesEnabled()) {
1411         if (_fileFilter == nullptr) { // Initialize
1412             _fileFilter = new FileFilter(session(), currentWordCharacters);
1413             filterChain->addFilter(_fileFilter);
1414         } else {
1415             // If wordCharacters changed, we need to change the static regex
1416             // pattern in _fileFilter
1417             if (_wordChars != currentWordCharacters) {
1418                 _wordChars = currentWordCharacters;
1419                 _fileFilter->updateRegex(currentWordCharacters);
1420             }
1421         }
1422     } else if (_fileFilter != nullptr) { // It became disabled, clean up
1423         filterChain->removeFilter(_fileFilter);
1424         delete _fileFilter;
1425         _fileFilter = nullptr;
1426     }
1427 
1428     if (profile->underlineLinksEnabled()) {
1429         if (_urlFilter == nullptr) { // Initialize
1430             _urlFilter = new UrlFilter();
1431             filterChain->addFilter(_urlFilter);
1432         }
1433     } else if (_urlFilter != nullptr) { // It became disabled, clean up
1434         filterChain->removeFilter(_urlFilter);
1435         delete _urlFilter;
1436         _urlFilter = nullptr;
1437     }
1438 
1439     if (profile->allowEscapedLinks()) {
1440         if (_escapedUrlFilter == nullptr) { // Initialize
1441             _escapedUrlFilter = new EscapeSequenceUrlFilter(session(), view());
1442             filterChain->addFilter(_escapedUrlFilter);
1443         }
1444     } else if (_escapedUrlFilter != nullptr) { // It became disabled, clean up
1445         filterChain->removeFilter(_escapedUrlFilter);
1446         delete _escapedUrlFilter;
1447         _escapedUrlFilter = nullptr;
1448     }
1449 
1450     const bool allowColorFilters = profile->colorFilterEnabled();
1451     if (!allowColorFilters && (_colorFilter != nullptr)) {
1452         filterChain->removeFilter(_colorFilter);
1453         delete _colorFilter;
1454         _colorFilter = nullptr;
1455     } else if (allowColorFilters && (_colorFilter == nullptr)) {
1456         _colorFilter = new ColorFilter();
1457         filterChain->addFilter(_colorFilter);
1458     }
1459 }
1460 
1461 void SessionController::setSearchStartToWindowCurrentLine()
1462 {
1463     setSearchStartTo(-1);
1464 }
1465 
1466 void SessionController::setSearchStartTo(int line)
1467 {
1468     _searchStartLine = line;
1469     _prevSearchResultLine = line;
1470 }
1471 
1472 void SessionController::listenForScreenWindowUpdates()
1473 {
1474     if (_listenForScreenWindowUpdates) {
1475         return;
1476     }
1477 
1478     connect(view()->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter);
1479     connect(view()->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter);
1480     connect(view()->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, view(), QOverload<>::of(&Konsole::TerminalDisplay::update));
1481 
1482     _listenForScreenWindowUpdates = true;
1483 }
1484 
1485 void SessionController::updateSearchFilter()
1486 {
1487     if ((_searchFilter != nullptr) && (!_searchBar.isNull())) {
1488         view()->processFilters();
1489     }
1490 }
1491 
1492 void SessionController::searchBarEvent()
1493 {
1494     QString selectedText = view()->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace);
1495     if (!selectedText.isEmpty()) {
1496         _searchBar->setSearchText(selectedText);
1497     }
1498 
1499     if (_searchBar->isVisible()) {
1500         _searchBar->focusLineEdit();
1501     } else {
1502         searchHistory(true);
1503         searchTextChanged(_searchBar->searchText());
1504         _isSearchBarEnabled = true;
1505     }
1506 }
1507 
1508 void SessionController::enableSearchBar(bool showSearchBar)
1509 {
1510     if (_searchBar.isNull()) {
1511         return;
1512     }
1513 
1514     if (showSearchBar && !_searchBar->isVisible()) {
1515         setSearchStartToWindowCurrentLine();
1516     }
1517 
1518     _searchBar->setVisible(showSearchBar);
1519     if (showSearchBar) {
1520         connect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged);
1521         connect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory);
1522         connect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory);
1523     } else {
1524         disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged);
1525         disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory);
1526         disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory);
1527         if ((!view().isNull()) && (view()->screenWindow() != nullptr)) {
1528             view()->screenWindow()->setCurrentResultLine(-1);
1529         }
1530     }
1531 }
1532 
1533 bool SessionController::reverseSearchChecked() const
1534 {
1535     Q_ASSERT(_searchBar);
1536 
1537     QBitArray options = _searchBar->optionsChecked();
1538     return options.at(IncrementalSearchBar::ReverseSearch);
1539 }
1540 
1541 QRegularExpression SessionController::regexpFromSearchBarOptions() const
1542 {
1543     QBitArray options = _searchBar->optionsChecked();
1544 
1545     QString text(_searchBar->searchText());
1546 
1547     QRegularExpression regExp;
1548     if (options.at(IncrementalSearchBar::RegExp)) {
1549         regExp.setPattern(text);
1550     } else {
1551         regExp.setPattern(QRegularExpression::escape(text));
1552     }
1553 
1554     if (!options.at(IncrementalSearchBar::MatchCase)) {
1555         regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
1556     }
1557 
1558     return regExp;
1559 }
1560 
1561 // searchHistory() may be called either as a result of clicking a menu item or
1562 // as a result of changing the search bar widget
1563 void SessionController::searchHistory(bool showSearchBar)
1564 {
1565     enableSearchBar(showSearchBar);
1566 
1567     if (!_searchBar.isNull()) {
1568         if (showSearchBar) {
1569             removeSearchFilter();
1570 
1571             listenForScreenWindowUpdates();
1572 
1573             _searchFilter = new RegExpFilter();
1574             _searchFilter->setRegExp(regexpFromSearchBarOptions());
1575             view()->filterChain()->addFilter(_searchFilter);
1576             view()->processFilters();
1577 
1578             setFindNextPrevEnabled(true);
1579         } else {
1580             setFindNextPrevEnabled(false);
1581 
1582             removeSearchFilter();
1583 
1584             view()->setFocus(Qt::ActiveWindowFocusReason);
1585         }
1586     }
1587 }
1588 
1589 void SessionController::setFindNextPrevEnabled(bool enabled)
1590 {
1591     _findNextAction->setEnabled(enabled);
1592     _findPreviousAction->setEnabled(enabled);
1593 }
1594 
1595 void SessionController::searchTextChanged(const QString &text)
1596 {
1597     Q_ASSERT(view()->screenWindow());
1598 
1599     if (_searchText == text && _isSearchBarEnabled) {
1600         return;
1601     }
1602 
1603     _searchText = text;
1604 
1605     if (text.isEmpty()) {
1606         view()->clearMouseSelection();
1607         view()->screenWindow()->scrollTo(_searchStartLine);
1608     }
1609 
1610     // update search.  this is called even when the text is
1611     // empty to clear the view's filters
1612     beginSearch(text, reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1613 }
1614 void SessionController::searchCompleted(bool success)
1615 {
1616     _prevSearchResultLine = view()->screenWindow()->currentResultLine();
1617 
1618     if (!_searchBar.isNull()) {
1619         _searchBar->setFoundMatch(success);
1620     }
1621 }
1622 
1623 void SessionController::beginSearch(const QString &text, Enum::SearchDirection direction)
1624 {
1625     Q_ASSERT(_searchBar);
1626     Q_ASSERT(_searchFilter);
1627 
1628     QRegularExpression regExp = regexpFromSearchBarOptions();
1629     _searchFilter->setRegExp(regExp);
1630 
1631     if (_searchStartLine < 0 || _searchStartLine > view()->screenWindow()->lineCount()) {
1632         if (direction == Enum::ForwardsSearch) {
1633             setSearchStartTo(view()->screenWindow()->currentLine());
1634         } else {
1635             setSearchStartTo(view()->screenWindow()->currentLine() + view()->screenWindow()->windowLines());
1636         }
1637     }
1638 
1639     if (!regExp.pattern().isEmpty()) {
1640         view()->screenWindow()->setCurrentResultLine(-1);
1641         auto task = new SearchHistoryTask(this);
1642 
1643         connect(task, &Konsole::SearchHistoryTask::completed, this, &Konsole::SessionController::searchCompleted);
1644 
1645         task->setRegExp(regExp);
1646         task->setSearchDirection(direction);
1647         task->setAutoDelete(true);
1648         task->setStartLine(_searchStartLine);
1649         task->addScreenWindow(session(), view()->screenWindow());
1650         task->execute();
1651     } else if (text.isEmpty()) {
1652         searchCompleted(false);
1653     }
1654 
1655     view()->processFilters();
1656 }
1657 void SessionController::highlightMatches(bool highlight)
1658 {
1659     if (highlight) {
1660         view()->filterChain()->addFilter(_searchFilter);
1661         view()->processFilters();
1662     } else {
1663         view()->filterChain()->removeFilter(_searchFilter);
1664     }
1665 
1666     view()->update();
1667 }
1668 
1669 void SessionController::searchFrom()
1670 {
1671     Q_ASSERT(_searchBar);
1672     Q_ASSERT(_searchFilter);
1673 
1674     if (reverseSearchChecked()) {
1675         setSearchStartTo(view()->screenWindow()->lineCount());
1676     } else {
1677         setSearchStartTo(0);
1678     }
1679 
1680     beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1681 }
1682 void SessionController::findNextInHistory()
1683 {
1684     Q_ASSERT(_searchBar);
1685     Q_ASSERT(_searchFilter);
1686 
1687     setSearchStartTo(_prevSearchResultLine);
1688 
1689     beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1690 }
1691 void SessionController::findPreviousInHistory()
1692 {
1693     Q_ASSERT(_searchBar);
1694     Q_ASSERT(_searchFilter);
1695 
1696     setSearchStartTo(_prevSearchResultLine);
1697 
1698     beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::ForwardsSearch : Enum::BackwardsSearch);
1699 }
1700 void SessionController::updateMenuIconsAccordingToReverseSearchSetting()
1701 {
1702     if (reverseSearchChecked()) {
1703         _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1704         _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1705     } else {
1706         _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1707         _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1708     }
1709 }
1710 void SessionController::changeSearchMatch()
1711 {
1712     Q_ASSERT(_searchBar);
1713     Q_ASSERT(_searchFilter);
1714 
1715     // reset Selection for new case match
1716     view()->clearMouseSelection();
1717     beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1718 }
1719 void SessionController::showHistoryOptions()
1720 {
1721     auto *dialog = new HistorySizeDialog(QApplication::activeWindow());
1722     dialog->setAttribute(Qt::WA_DeleteOnClose);
1723     dialog->setModal(true);
1724 
1725     const HistoryType &currentHistory = session()->historyType();
1726     if (currentHistory.isEnabled()) {
1727         if (currentHistory.isUnlimited()) {
1728             dialog->setMode(Enum::UnlimitedHistory);
1729         } else {
1730             dialog->setMode(Enum::FixedSizeHistory);
1731             dialog->setLineCount(currentHistory.maximumLineCount());
1732         }
1733     } else {
1734         dialog->setMode(Enum::NoHistory);
1735     }
1736 
1737     connect(dialog, &QDialog::accepted, this, [this, dialog]() {
1738         scrollBackOptionsChanged(dialog->mode(), dialog->lineCount());
1739     });
1740 
1741     dialog->show();
1742 }
1743 void SessionController::sessionResizeRequest(const QSize &size)
1744 {
1745     ////qDebug() << "View resize requested to " << size;
1746     view()->setSize(size.width(), size.height());
1747 }
1748 void SessionController::scrollBackOptionsChanged(int mode, int lines)
1749 {
1750     switch (mode) {
1751     case Enum::NoHistory:
1752         session()->setHistoryType(HistoryTypeNone());
1753         break;
1754     case Enum::FixedSizeHistory:
1755         session()->setHistoryType(CompactHistoryType(lines));
1756         break;
1757     case Enum::UnlimitedHistory:
1758         session()->setHistoryType(HistoryTypeFile());
1759         break;
1760     }
1761 }
1762 
1763 void SessionController::saveHistory()
1764 {
1765     SessionTask *task = new SaveHistoryTask(this);
1766     task->setAutoDelete(true);
1767     task->addSession(session());
1768     task->execute();
1769 }
1770 
1771 void SessionController::clearHistory()
1772 {
1773     session()->clearHistory();
1774     view()->updateImage(); // To reset view scrollbar
1775     view()->repaint();
1776 }
1777 
1778 void SessionController::clearHistoryAndReset()
1779 {
1780     Profile::Ptr profile = SessionManager::instance()->sessionProfile(session());
1781     QByteArray name = profile->defaultEncoding().toUtf8();
1782 
1783     Emulation *emulation = session()->emulation();
1784     emulation->reset(false, true);
1785     session()->refresh();
1786     session()->setCodec(QTextCodec::codecForName(name));
1787     clearHistory();
1788 }
1789 
1790 void SessionController::increaseFontSize()
1791 {
1792     view()->terminalFont()->increaseFontSize();
1793 }
1794 
1795 void SessionController::decreaseFontSize()
1796 {
1797     view()->terminalFont()->decreaseFontSize();
1798 }
1799 
1800 void SessionController::resetFontSize()
1801 {
1802     view()->terminalFont()->resetFontSize();
1803 }
1804 
1805 void SessionController::notifyPrompt()
1806 {
1807     if (session()->isMonitorPrompt()) {
1808         KNotification *notification =
1809             new KNotification(session()->hasFocus() ? QStringLiteral("Prompt") : QStringLiteral("PromptHidden"), KNotification::CloseWhenWindowActivated);
1810         notification->setWindow(view()->windowHandle());
1811 
1812         notification->setText(i18n("The shell prompt is displayed in session '%1'", session()->nameTitle()));
1813         auto action = notification->addDefaultAction(i18n("Show session"));
1814         connect(action, &KNotificationAction::activated, this, [this, notification] {
1815             view()->notificationClicked(notification->xdgActivationToken());
1816         });
1817         notification->sendEvent();
1818         if (_monitorOnce) {
1819             actionCollection()->action(QStringLiteral("monitor-prompt"))->setChecked(false);
1820         }
1821     }
1822 }
1823 void SessionController::monitorOnce(bool once)
1824 {
1825     _monitorOnce = once;
1826 }
1827 void SessionController::monitorPrompt(bool monitor)
1828 {
1829     session()->setMonitorPrompt(monitor);
1830 }
1831 void SessionController::monitorActivity(bool monitor)
1832 {
1833     session()->setMonitorActivity(monitor);
1834 }
1835 void SessionController::monitorSilence(bool monitor)
1836 {
1837     session()->setMonitorSilence(monitor);
1838 }
1839 void SessionController::monitorProcessFinish(bool monitor)
1840 {
1841     _monitorProcessFinish = monitor;
1842 }
1843 void SessionController::updateSessionIcon()
1844 {
1845     // If the default profile icon is being used, don't put it on the tab
1846     // Only show the icon if the user specifically chose one
1847     if (session()->iconName() == QStringLiteral("utilities-terminal")) {
1848         _sessionIconName = QString();
1849     } else {
1850         _sessionIconName = session()->iconName();
1851     }
1852     _sessionIcon = QIcon::fromTheme(_sessionIconName);
1853 
1854     setIcon(_sessionIcon);
1855 }
1856 
1857 void SessionController::updateReadOnlyActionStates()
1858 {
1859     bool readonly = isReadOnly();
1860     QAction *readonlyAction = actionCollection()->action(QStringLiteral("view-readonly"));
1861     Q_ASSERT(readonlyAction != nullptr);
1862     readonlyAction->setIcon(QIcon::fromTheme(readonly ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked")));
1863     readonlyAction->setChecked(readonly);
1864 
1865     auto updateActionState = [this, readonly](const QString &name) {
1866         QAction *action = actionCollection()->action(name);
1867         if (action != nullptr) {
1868             action->setVisible(!readonly);
1869         }
1870     };
1871 
1872     updateActionState(QStringLiteral("edit_paste"));
1873     updateActionState(QStringLiteral("clear-history"));
1874     updateActionState(QStringLiteral("clear-history-and-reset"));
1875     updateActionState(QStringLiteral("edit-current-profile"));
1876     updateActionState(QStringLiteral("switch-profile"));
1877     updateActionState(QStringLiteral("adjust-history"));
1878     updateActionState(QStringLiteral("send-signal"));
1879     updateActionState(QStringLiteral("zmodem-upload"));
1880 
1881     _codecAction->setEnabled(!readonly);
1882 
1883     // Without the timer, when detaching a tab while the message widget is visible,
1884     // the size of the terminal becomes really small...
1885     QTimer::singleShot(0, this, [this, readonly]() {
1886         view()->updateReadOnlyState(readonly);
1887     });
1888 }
1889 
1890 bool SessionController::isReadOnly() const
1891 {
1892     if (!session().isNull()) {
1893         return session()->isReadOnly();
1894     } else {
1895         return false;
1896     }
1897 }
1898 
1899 bool SessionController::isCopyInputActive() const
1900 {
1901     return ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1);
1902 }
1903 
1904 void SessionController::sessionAttributeChanged()
1905 {
1906     if (_sessionIconName != session()->iconName()) {
1907         updateSessionIcon();
1908     }
1909 
1910     QString title = session()->title(Session::DisplayedTitleRole);
1911 
1912     // special handling for the "%w" marker which is replaced with the
1913     // window title set by the shell
1914     title.replace(QLatin1String("%w"), session()->userTitle());
1915     // special handling for the "%#" marker which is replaced with the
1916     // number of the shell
1917     title.replace(QLatin1String("%#"), QString::number(session()->sessionId()));
1918 
1919     if (title.isEmpty()) {
1920         title = session()->title(Session::NameRole);
1921     }
1922 
1923 #ifdef Q_OS_WIN
1924     title = session()->userTitle();
1925 #endif
1926 
1927     setTitle(title);
1928     setColor(session()->color());
1929     Q_EMIT rawTitleChanged();
1930 }
1931 
1932 void SessionController::sessionReadOnlyChanged()
1933 {
1934     updateReadOnlyActionStates();
1935 
1936     // Update all views
1937     const QList<TerminalDisplay *> viewsList = session()->views();
1938     for (TerminalDisplay *terminalDisplay : viewsList) {
1939         if (terminalDisplay != view()) {
1940             terminalDisplay->updateReadOnlyState(isReadOnly());
1941         }
1942         Q_EMIT readOnlyChanged(this);
1943     }
1944 }
1945 
1946 void SessionController::showDisplayContextMenu(const QPoint &position)
1947 {
1948     // needed to make sure the popup menu is available, even if a hosting
1949     // application did not merge our GUI.
1950     if (factory() == nullptr) {
1951         if (clientBuilder() == nullptr) {
1952             // Client builder does not get deleted automatically, we handle this
1953             _clientBuilder.reset(new KXMLGUIBuilder(view()));
1954             setClientBuilder(_clientBuilder.get());
1955         }
1956 
1957         auto factory = new KXMLGUIFactory(clientBuilder(), view());
1958         factory->addClient(this);
1959     }
1960 
1961     QPointer<QMenu> popup = qobject_cast<QMenu *>(factory()->container(QStringLiteral("session-popup-menu"), this));
1962     if (!popup.isNull()) {
1963         updateReadOnlyActionStates();
1964 
1965         auto contentSeparator = new QAction(popup);
1966         contentSeparator->setSeparator(true);
1967 
1968         // We don't actually use this shortcut, but we need to display it for consistency :/
1969         QAction *copy = actionCollection()->action(QStringLiteral("edit_copy_contextmenu"));
1970         copy->setShortcut(Konsole::ACCEL | Qt::Key_C);
1971 
1972         // Adds a "Open Folder With" action
1973         const QUrl currentUrl = url().isLocalFile() ? url() : QUrl::fromLocalFile(QDir::homePath());
1974         KFileItem item(currentUrl);
1975 
1976         const auto old = popup->actions();
1977 
1978         const KFileItemListProperties props({item});
1979         QScopedPointer<KFileItemActions> ac(new KFileItemActions());
1980         ac->setItemListProperties(props);
1981 
1982         ac->insertOpenWithActionsTo(popup->actions().value(4, nullptr), popup, QStringList{qApp->desktopFileName()});
1983 
1984         auto newActions = popup->actions();
1985         for (auto *elm : old) {
1986             newActions.removeAll(elm);
1987         }
1988         // Finish Adding the "Open Folder With" action.
1989 
1990         QList<QAction *> toRemove;
1991         // prepend content-specific actions such as "Open Link", "Copy Email Address" etc
1992         _currentHotSpot = view()->filterActions(position);
1993         if (_currentHotSpot != nullptr) {
1994             popup->insertActions(popup->actions().value(0, nullptr), _currentHotSpot->actions() << contentSeparator);
1995             popup->addAction(contentSeparator);
1996             toRemove = _currentHotSpot->setupMenu(popup.data());
1997 
1998             // The action above can create an action for Open Folder With,
1999             // for the selected folder, but then we have two different
2000             // Open Folder With - with different folders on each.
2001             // Change the text of the second one, that points to the
2002             // current folder.
2003             for (auto *action : newActions) {
2004                 if (action->objectName() == QStringLiteral("openWith_submenu")) {
2005                     action->setText(i18n("Open Current Folder With"));
2006                 }
2007             }
2008             toRemove = toRemove + newActions;
2009         } else {
2010             toRemove = newActions;
2011         }
2012 
2013         // always update this submenu before showing the context menu,
2014         // because the available search services might have changed
2015         // since the context menu is shown last time
2016         updateWebSearchMenu();
2017 
2018         _preventClose = true;
2019 
2020         auto hamburger = static_cast<KHamburgerMenu *>(actionCollection()->action(KStandardAction::name(KStandardAction::HamburgerMenu)));
2021         if (hamburger) {
2022             hamburger->addToMenu(popup);
2023         }
2024 
2025         // they are here.
2026         // qDebug() << popup->actions().indexOf(contentActions[0]) << popup->actions().indexOf(contentActions[1]) << popup->actions()[3];
2027         QAction *chosen = popup->exec(QCursor::pos());
2028 
2029         // check for validity of the pointer to the popup menu
2030         if (!popup.isNull()) {
2031             delete contentSeparator;
2032             // Remove the 'Open with' actions from it.
2033             for (auto *act : toRemove) {
2034                 popup->removeAction(act);
2035             }
2036 
2037             // Remove the Accelerator for the copy shortcut so we don't have two actions with same shortcut.
2038             copy->setShortcut({});
2039         }
2040 
2041         // This should be at the end, to prevent crashes if the session
2042         // is closed from the menu in e.g. konsole kpart
2043         _preventClose = false;
2044         if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) {
2045             chosen->trigger();
2046         }
2047     } else {
2048         qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << session()->title(Session::NameRole)
2049                               << ", no GUI factory available to build the popup.";
2050     }
2051 }
2052 
2053 void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event)
2054 {
2055     QCoreApplication::sendEvent(view(), event);
2056     setSearchStartToWindowCurrentLine();
2057 }
2058 
2059 void SessionController::sessionNotificationsChanged(Session::Notification notification, bool enabled)
2060 {
2061     Q_EMIT notificationChanged(this, notification, enabled);
2062 }
2063 
2064 void SessionController::zmodemDownload()
2065 {
2066     QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz"));
2067     if (zmodem.isEmpty()) {
2068         zmodem = QStandardPaths::findExecutable(QStringLiteral("lrzsz-rz"));
2069     }
2070     if (zmodem.isEmpty()) {
2071         zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz"));
2072     }
2073     if (!zmodem.isEmpty()) {
2074         const QString path = QFileDialog::getExistingDirectory(view(),
2075                                                                i18n("Save ZModem Download to..."),
2076                                                                QDir::homePath(),
2077                                                                QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
2078 
2079         if (!path.isEmpty()) {
2080             session()->startZModem(zmodem, path, QStringList());
2081             return;
2082         }
2083     } else {
2084         KMessageBox::error(view(),
2085                            i18n("<p>A ZModem file transfer attempt has been detected, "
2086                                 "but no suitable ZModem software was found on this system.</p>"
2087                                 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
2088     }
2089     session()->cancelZModem();
2090 }
2091 
2092 void SessionController::zmodemUpload()
2093 {
2094     if (session()->isZModemBusy()) {
2095         KMessageBox::information(view(), i18n("<p>The current session already has a ZModem file transfer in progress.</p>"));
2096         return;
2097     }
2098 
2099     QString zmodem = QStandardPaths::findExecutable(QStringLiteral("sz"));
2100     if (zmodem.isEmpty()) {
2101         zmodem = QStandardPaths::findExecutable(QStringLiteral("lrzsz-sz"));
2102     }
2103     if (zmodem.isEmpty()) {
2104         zmodem = QStandardPaths::findExecutable(QStringLiteral("lsz"));
2105     }
2106     if (zmodem.isEmpty()) {
2107         KMessageBox::error(view(),
2108                            i18n("<p>No suitable ZModem software was found on this system.</p>"
2109                                 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
2110         return;
2111     }
2112 
2113     QStringList files = QFileDialog::getOpenFileNames(view(), i18n("Select Files for ZModem Upload"), QDir::homePath());
2114     if (!files.isEmpty()) {
2115         session()->startZModem(zmodem, QString(), files);
2116     }
2117 }
2118 
2119 bool SessionController::isKonsolePart() const
2120 {
2121     // Check to see if we are being called from Konsole or a KPart
2122     return !(qApp->applicationName() == QLatin1String("konsole"));
2123 }
2124 
2125 QString SessionController::userTitle() const
2126 {
2127     if (!session().isNull()) {
2128         return session()->userTitle();
2129     } else {
2130         return QString();
2131     }
2132 }
2133 
2134 bool SessionController::isValid() const
2135 {
2136     return _sessionDisplayConnection->isValid();
2137 }
2138 
2139 void SessionController::setVisible(QString name, bool visible)
2140 {
2141     /* For certain user profiles, testTerminalInterface crashes
2142         in QAction::setVisible() without this check.
2143     */
2144     if (!actionCollection()->action(name)) {
2145         return;
2146     }
2147     actionCollection()->action(name)->setVisible(visible);
2148 }
2149 
2150 KSelectAction *SessionController::copyInputActions()
2151 {
2152     return _copyInputActions;
2153 }
2154 
2155 #include "moc_SessionController.cpp"