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