File indexing completed on 2024-05-05 10:14:32
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->setCheckable(true); 0804 0805 // Read-only 0806 action = collection->addAction(QStringLiteral("view-readonly"), this); 0807 connect(action, &QAction::toggled, this, [this, action]() { 0808 toggleReadOnly(action); 0809 }); 0810 action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only")); 0811 action->setCheckable(true); 0812 updateReadOnlyActionStates(); 0813 } 0814 0815 void SessionController::setupExtraActions() 0816 { 0817 KActionCollection *collection = actionCollection(); 0818 0819 // Rename Session 0820 QAction *action = collection->addAction(QStringLiteral("rename-session"), this, &SessionController::renameSession); 0821 action->setText(i18n("&Configure or Rename Tab...")); 0822 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); 0823 collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_S); 0824 0825 // Copy input to ==> all tabs 0826 auto *copyInputToAllTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-all-tabs")); 0827 copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window")); 0828 copyInputToAllTabsAction->setData(CopyInputToAllTabsMode); 0829 // this action is also used in other place, so remember it 0830 _copyInputToAllTabsAction = copyInputToAllTabsAction; 0831 0832 // Copy input to ==> selected tabs 0833 auto *copyInputToSelectedTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-selected-tabs")); 0834 copyInputToSelectedTabsAction->setText(i18n("&Select Tabs...")); 0835 collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL | Qt::Key_Period); 0836 copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode); 0837 0838 // Copy input to ==> none 0839 auto *copyInputToNoneAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-none")); 0840 copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None")); 0841 collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL | Qt::Key_Slash); 0842 copyInputToNoneAction->setData(CopyInputToNoneMode); 0843 copyInputToNoneAction->setChecked(true); // the default state 0844 0845 // The "Copy Input To" submenu 0846 // The above three choices are represented as combo boxes 0847 auto *copyInputActions = collection->add<KSelectAction>(QStringLiteral("copy-input-to")); 0848 copyInputActions->setText(i18n("Copy Input To")); 0849 copyInputActions->addAction(copyInputToAllTabsAction); 0850 copyInputActions->addAction(copyInputToSelectedTabsAction); 0851 copyInputActions->addAction(copyInputToNoneAction); 0852 connect(copyInputActions, &KSelectAction::actionTriggered, this, &Konsole::SessionController::copyInputActionsTriggered); 0853 _copyInputActions = copyInputActions; 0854 0855 action = collection->addAction(QStringLiteral("zmodem-upload"), this, &SessionController::zmodemUpload); 0856 action->setText(i18n("&ZModem Upload...")); 0857 action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); 0858 collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_U); 0859 0860 // Monitor 0861 KToggleAction *toggleAction = new KToggleAction(i18n("One-shot monitors"), this); 0862 action = collection->addAction(QStringLiteral("monitor-once"), toggleAction); 0863 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorOnce); 0864 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn"))); 0865 0866 toggleAction = new KToggleAction(i18n("Monitor for &Prompt"), this); 0867 collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_R); 0868 action = collection->addAction(QStringLiteral("monitor-prompt"), toggleAction); 0869 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorPrompt); 0870 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn"))); 0871 action->setVisible(false); 0872 0873 toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this); 0874 collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_A); 0875 action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction); 0876 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity); 0877 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn"))); 0878 0879 toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this); 0880 collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::Key_I); 0881 action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction); 0882 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence); 0883 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-copy"))); 0884 0885 toggleAction = new KToggleAction(i18n("Monitor for Process Finishing"), this); 0886 action = collection->addAction(QStringLiteral("monitor-process-finish"), toggleAction); 0887 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorProcessFinish); 0888 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn-image"))); 0889 0890 // Text Size 0891 action = collection->addAction(QStringLiteral("enlarge-font"), this, &SessionController::increaseFontSize); 0892 action->setText(i18n("Enlarge Font")); 0893 action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more"))); 0894 QList<QKeySequence> enlargeFontShortcut; 0895 enlargeFontShortcut.append(QKeySequence(Qt::CTRL | Qt::Key_Plus)); 0896 enlargeFontShortcut.append(QKeySequence(Qt::CTRL | Qt::Key_Equal)); 0897 collection->setDefaultShortcuts(action, enlargeFontShortcut); 0898 0899 action = collection->addAction(QStringLiteral("shrink-font"), this, &SessionController::decreaseFontSize); 0900 action->setText(i18n("Shrink Font")); 0901 action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less"))); 0902 collection->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_Minus)); 0903 0904 action = collection->addAction(QStringLiteral("reset-font-size"), this, &SessionController::resetFontSize); 0905 action->setText(i18n("Reset Font Size")); 0906 collection->setDefaultShortcut(action, Qt::CTRL | Qt::ALT | Qt::Key_0); 0907 0908 #ifndef Q_OS_WIN 0909 // Send signal 0910 auto *sendSignalActions = collection->add<KSelectAction>(QStringLiteral("send-signal")); 0911 sendSignalActions->setText(i18n("Send Signal")); 0912 connect(sendSignalActions, &KSelectAction::actionTriggered, this, &Konsole::SessionController::sendSignal); 0913 0914 action = collection->addAction(QStringLiteral("sigstop-signal")); 0915 action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)")); 0916 action->setData(SIGSTOP); 0917 sendSignalActions->addAction(action); 0918 0919 action = collection->addAction(QStringLiteral("sigcont-signal")); 0920 action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)")); 0921 action->setData(SIGCONT); 0922 sendSignalActions->addAction(action); 0923 0924 action = collection->addAction(QStringLiteral("sighup-signal")); 0925 action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)")); 0926 action->setData(SIGHUP); 0927 sendSignalActions->addAction(action); 0928 0929 action = collection->addAction(QStringLiteral("sigint-signal")); 0930 action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)")); 0931 action->setData(SIGINT); 0932 sendSignalActions->addAction(action); 0933 0934 action = collection->addAction(QStringLiteral("sigterm-signal")); 0935 action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)")); 0936 action->setData(SIGTERM); 0937 sendSignalActions->addAction(action); 0938 0939 action = collection->addAction(QStringLiteral("sigkill-signal")); 0940 action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)")); 0941 action->setData(SIGKILL); 0942 sendSignalActions->addAction(action); 0943 0944 action = collection->addAction(QStringLiteral("sigusr1-signal")); 0945 action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)")); 0946 action->setData(SIGUSR1); 0947 sendSignalActions->addAction(action); 0948 0949 action = collection->addAction(QStringLiteral("sigusr2-signal")); 0950 action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)")); 0951 action->setData(SIGUSR2); 0952 sendSignalActions->addAction(action); 0953 #endif 0954 } 0955 0956 void SessionController::switchProfile(const Profile::Ptr &profile) 0957 { 0958 SessionManager::instance()->setSessionProfile(session(), profile); 0959 _switchProfileMenu->setIcon(QIcon::fromTheme(profile->icon())); 0960 updateFilterList(profile); 0961 setEditProfileActionText(profile); 0962 } 0963 0964 void SessionController::setEditProfileActionText(const Profile::Ptr &profile) 0965 { 0966 QAction *action = actionCollection()->action(QStringLiteral("edit-current-profile")); 0967 if (!profile->isEditable()) { 0968 action->setText(i18n("Create New Profile...")); 0969 } else { 0970 action->setText(i18n("Edit Current Profile...")); 0971 } 0972 } 0973 0974 void SessionController::prepareSwitchProfileMenu() 0975 { 0976 if (_switchProfileMenu->menu()->isEmpty()) { 0977 _profileList = new ProfileList(false, this); 0978 connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile); 0979 } 0980 0981 _switchProfileMenu->menu()->clear(); 0982 _switchProfileMenu->menu()->addActions(_profileList->actions()); 0983 } 0984 void SessionController::updateCodecAction(QTextCodec *codec) 0985 { 0986 _codecAction->setCurrentCodec(QString::fromUtf8(codec->name())); 0987 } 0988 0989 void SessionController::changeCodec(QTextCodec *codec) 0990 { 0991 session()->setCodec(codec); 0992 } 0993 0994 void SessionController::editCurrentProfile() 0995 { 0996 auto *dialog = new EditProfileDialog(QApplication::activeWindow()); 0997 dialog->setAttribute(Qt::WA_DeleteOnClose); 0998 dialog->setModal(true); 0999 1000 auto profile = SessionManager::instance()->sessionProfile(session()); 1001 auto state = EditProfileDialog::ExistingProfile; 1002 // Don't edit uneditable profiles, instead create a new one 1003 if (!profile->isEditable()) { 1004 auto newProfile = Profile::Ptr(new Profile(profile)); 1005 newProfile->clone(profile, true); 1006 const QString uniqueName = ProfileManager::instance()->generateUniqueName(); 1007 newProfile->setProperty(Profile::Name, uniqueName); 1008 newProfile->setProperty(Profile::UntranslatedName, uniqueName); 1009 profile = newProfile; 1010 SessionManager::instance()->setSessionProfile(session(), profile); 1011 state = EditProfileDialog::NewProfile; 1012 1013 connect(dialog, &QDialog::accepted, this, [this, profile]() { 1014 setEditProfileActionText(profile); 1015 }); 1016 } 1017 1018 dialog->setProfile(profile, state); 1019 1020 dialog->show(); 1021 } 1022 1023 void SessionController::renameSession() 1024 { 1025 const QString sessionLocalTabTitleFormat = session()->tabTitleFormat(Session::LocalTabTitle); 1026 const QString sessionRemoteTabTitleFormat = session()->tabTitleFormat(Session::RemoteTabTitle); 1027 const QColor sessionTabColor = session()->color(); 1028 1029 auto *dialog = new RenameTabDialog(QApplication::activeWindow()); 1030 dialog->setAttribute(Qt::WA_DeleteOnClose); 1031 dialog->setModal(true); 1032 dialog->setTabTitleText(sessionLocalTabTitleFormat); 1033 dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat); 1034 dialog->setColor(sessionTabColor); 1035 1036 if (session()->isRemote()) { 1037 dialog->focusRemoteTabTitleText(); 1038 } else { 1039 dialog->focusTabTitleText(); 1040 } 1041 1042 connect(dialog, &QDialog::accepted, this, [=]() { 1043 const QString tabTitle = dialog->tabTitleText(); 1044 const QString remoteTabTitle = dialog->remoteTabTitleText(); 1045 const QColor tabColor = dialog->color(); 1046 1047 if (tabTitle != sessionLocalTabTitleFormat) { 1048 session()->setTabTitleFormat(Session::LocalTabTitle, tabTitle); 1049 Q_EMIT tabRenamedByUser(true); 1050 // trigger an update of the tab text 1051 snapshot(); 1052 } 1053 1054 if (remoteTabTitle != sessionRemoteTabTitleFormat) { 1055 session()->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle); 1056 Q_EMIT tabRenamedByUser(true); 1057 snapshot(); 1058 } 1059 1060 if (tabColor != sessionTabColor) { 1061 session()->setColor(tabColor); 1062 Q_EMIT tabColoredByUser(true); 1063 snapshot(); 1064 } 1065 }); 1066 1067 dialog->show(); 1068 } 1069 1070 // This is called upon Menu->Close Session and right-click on tab->Close Tab 1071 bool SessionController::confirmClose() const 1072 { 1073 if (session()->isForegroundProcessActive()) { 1074 QString title = session()->foregroundProcessName(); 1075 1076 // hard coded for now. In future make it possible for the user to specify which programs 1077 // are ignored when considering whether to display a confirmation 1078 QStringList ignoreList; 1079 ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); 1080 if (ignoreList.contains(title)) { 1081 return true; 1082 } 1083 1084 QString question; 1085 if (title.isEmpty()) { 1086 question = i18n( 1087 "A program is currently running in this session." 1088 " Are you sure you want to close it?"); 1089 } else { 1090 question = i18n( 1091 "The program '%1' is currently running in this session." 1092 " Are you sure you want to close it?", 1093 title); 1094 } 1095 1096 int result = KMessageBox::warningTwoActions(view()->window(), 1097 question, 1098 i18n("Confirm Close"), 1099 KGuiItem(i18nc("@action:button", "Close Program"), QStringLiteral("application-exit")), 1100 KStandardGuiItem::cancel(), 1101 QStringLiteral("CloseSingleTab")); 1102 return result == KMessageBox::PrimaryAction; 1103 } 1104 return true; 1105 } 1106 bool SessionController::confirmForceClose() const 1107 { 1108 if (session()->isRunning()) { 1109 QString title = session()->program(); 1110 1111 // hard coded for now. In future make it possible for the user to specify which programs 1112 // are ignored when considering whether to display a confirmation 1113 QStringList ignoreList; 1114 ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); 1115 if (ignoreList.contains(title)) { 1116 return true; 1117 } 1118 1119 QString question; 1120 if (title.isEmpty()) { 1121 question = i18n( 1122 "A program in this session would not die." 1123 " Are you sure you want to kill it by force?"); 1124 } else { 1125 question = i18n( 1126 "The program '%1' is in this session would not die." 1127 " Are you sure you want to kill it by force?", 1128 title); 1129 } 1130 1131 int result = KMessageBox::warningTwoActions(view()->window(), 1132 question, 1133 i18n("Confirm Close"), 1134 KGuiItem(i18nc("@action:button", "Kill Program"), QStringLiteral("application-exit")), 1135 KStandardGuiItem::cancel()); 1136 return result == KMessageBox::PrimaryAction; 1137 } 1138 return true; 1139 } 1140 void SessionController::closeSession() 1141 { 1142 if (_preventClose) { 1143 return; 1144 } 1145 1146 if (!confirmClose()) { 1147 return; 1148 } 1149 1150 if (!session()->closeInNormalWay()) { 1151 if (!confirmForceClose()) { 1152 return; 1153 } 1154 1155 if (!session()->closeInForceWay()) { 1156 qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way."; 1157 return; 1158 } 1159 } 1160 1161 if (factory() != nullptr) { 1162 factory()->removeClient(this); 1163 } 1164 } 1165 1166 // Trying to open a remote Url may produce unexpected results. 1167 // Therefore, if a remote url, open the user's home path. 1168 // TODO consider: 1) disable menu upon remote session 1169 // 2) transform url to get the desired result (ssh -> sftp, etc) 1170 void SessionController::openBrowser() 1171 { 1172 // if we requested the browser on a file, we can't use OpenUrlJob 1173 // because it does not open the file in a browser, it opens another program 1174 // based on it's mime type. 1175 // so force open dolphin with it selected. 1176 // TODO: and for people that have other default file browsers such as 1177 // konqueror and krusader? 1178 1179 if (_currentHotSpot && _currentHotSpot->type() == HotSpot::File) { 1180 auto *fileHotSpot = qobject_cast<FileFilterHotSpot *>(_currentHotSpot.get()); 1181 assert(fileHotSpot); 1182 auto *job = new KIO::OpenFileManagerWindowJob(); 1183 job->setHighlightUrls({fileHotSpot->fileItem().url()}); 1184 job->start(); 1185 } else { 1186 const QUrl currentUrl = url().isLocalFile() ? url() : QUrl::fromLocalFile(QDir::homePath()); 1187 auto *job = new KIO::OpenUrlJob(currentUrl); 1188 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow())); 1189 job->start(); 1190 } 1191 } 1192 1193 void SessionController::copy() 1194 { 1195 view()->copyToClipboard(); 1196 } 1197 1198 void SessionController::copyInput() 1199 { 1200 view()->copyToClipboard(Screen::ExcludePrompt | Screen::ExcludeOutput); 1201 } 1202 1203 void SessionController::copyOutput() 1204 { 1205 view()->copyToClipboard(Screen::ExcludePrompt | Screen::ExcludeInput); 1206 } 1207 1208 void SessionController::copyInputOutput() 1209 { 1210 view()->copyToClipboard(Screen::ExcludePrompt); 1211 } 1212 1213 void SessionController::paste() 1214 { 1215 view()->pasteFromClipboard(); 1216 } 1217 void SessionController::pasteFromX11Selection() 1218 { 1219 view()->pasteFromX11Selection(); 1220 } 1221 void SessionController::selectAll() 1222 { 1223 view()->selectAll(); 1224 } 1225 void SessionController::selectMode() 1226 { 1227 if (!session().isNull()) { 1228 bool Mode = session()->getSelectMode(); 1229 setSelectMode(!Mode); 1230 } 1231 } 1232 void SessionController::setSelectMode(bool mode) 1233 { 1234 if (!session().isNull()) { 1235 session()->setSelectMode(mode); 1236 view()->setSelectMode(mode); 1237 } 1238 } 1239 1240 void SessionController::selectLine() 1241 { 1242 view()->selectCurrentLine(); 1243 } 1244 static const KXmlGuiWindow *findWindow(const QObject *object) 1245 { 1246 // Walk up the QObject hierarchy to find a KXmlGuiWindow. 1247 while (object != nullptr) { 1248 const auto *window = qobject_cast<const KXmlGuiWindow *>(object); 1249 if (window != nullptr) { 1250 return (window); 1251 } 1252 object = object->parent(); 1253 } 1254 return (nullptr); 1255 } 1256 1257 static bool hasTerminalDisplayInSameWindow(const Session *session, const KXmlGuiWindow *window) 1258 { 1259 // Iterate all TerminalDisplays of this Session ... 1260 const QList<TerminalDisplay *> views = session->views(); 1261 for (const TerminalDisplay *terminalDisplay : views) { 1262 // ... and check whether a TerminalDisplay has the same 1263 // window as given in the parameter 1264 if (window == findWindow(terminalDisplay)) { 1265 return (true); 1266 } 1267 } 1268 return (false); 1269 } 1270 1271 void SessionController::copyInputActionsTriggered(QAction *action) 1272 { 1273 const auto mode = action->data().toInt(); 1274 1275 switch (mode) { 1276 case CopyInputToAllTabsMode: 1277 copyInputToAllTabs(); 1278 break; 1279 case CopyInputToSelectedTabsMode: 1280 copyInputToSelectedTabs(); 1281 break; 1282 case CopyInputToNoneMode: 1283 copyInputToNone(); 1284 break; 1285 default: 1286 Q_ASSERT(false); 1287 } 1288 } 1289 1290 void SessionController::copyInputToAllTabs() 1291 { 1292 if (_copyToGroup == nullptr) { 1293 _copyToGroup = new SessionGroup(this); 1294 } 1295 1296 // Find our window ... 1297 const KXmlGuiWindow *myWindow = findWindow(view()); 1298 1299 const QList<Session *> sessionsList = SessionManager::instance()->sessions(); 1300 QSet<Session *> group(sessionsList.begin(), sessionsList.end()); 1301 for (auto session : group) { 1302 // First, ensure that the session is removed 1303 // (necessary to avoid duplicates on addSession()!) 1304 _copyToGroup->removeSession(session); 1305 1306 // Add current session if it is displayed our window 1307 if (hasTerminalDisplayInSameWindow(session, myWindow)) { 1308 _copyToGroup->addSession(session); 1309 } 1310 } 1311 _copyToGroup->setMasterStatus(session(), true); 1312 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); 1313 1314 snapshot(); 1315 Q_EMIT copyInputChanged(this); 1316 } 1317 1318 void SessionController::copyInputToSelectedTabs(QList<Session *> *sessions) 1319 { 1320 if (_copyToGroup == nullptr) { 1321 _copyToGroup = new SessionGroup(this); 1322 _copyToGroup->addSession(session()); 1323 _copyToGroup->setMasterStatus(session(), true); 1324 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); 1325 } 1326 1327 const QList<Session *> sessionsList = _copyToGroup->sessions(); 1328 QSet<Session *> currentGroup(sessionsList.begin(), sessionsList.end()); 1329 1330 currentGroup.remove(session()); 1331 1332 auto update = [this](QSet<Session *> newGroup, QSet<Session *> currentGroup) { 1333 const QSet<Session *> completeGroup = newGroup | currentGroup; 1334 for (Session *session : completeGroup) { 1335 if (newGroup.contains(session) && !currentGroup.contains(session)) { 1336 _copyToGroup->addSession(session); 1337 } else if (!newGroup.contains(session) && currentGroup.contains(session)) { 1338 _copyToGroup->removeSession(session); 1339 } 1340 } 1341 1342 _copyToGroup->setMasterStatus(session(), true); 1343 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); 1344 snapshot(); 1345 Q_EMIT copyInputChanged(this); 1346 }; 1347 1348 if (sessions != nullptr) { 1349 QSet<Session *> newGroup(sessions->begin(), sessions->end()); 1350 newGroup.remove(session()); 1351 update(newGroup, currentGroup); 1352 } else { 1353 auto *dialog = new CopyInputDialog(view()); 1354 dialog->setAttribute(Qt::WA_DeleteOnClose); 1355 dialog->setModal(true); 1356 dialog->setMasterSession(session()); 1357 dialog->setChosenSessions(currentGroup); 1358 1359 connect(dialog, &QDialog::accepted, this, [=]() { 1360 QSet<Session *> newGroup = dialog->chosenSessions(); 1361 newGroup.remove(session()); 1362 update(newGroup, currentGroup); 1363 }); 1364 1365 dialog->show(); 1366 } 1367 } 1368 1369 void SessionController::copyInputToNone() 1370 { 1371 if (_copyToGroup == nullptr) { // No 'Copy To' is active 1372 return; 1373 } 1374 1375 // Once Qt5.14+ is the minimum, change to use range constructors 1376 const QList<Session *> groupList = SessionManager::instance()->sessions(); 1377 QSet<Session *> group(groupList.begin(), groupList.end()); 1378 1379 for (auto iterator : group) { 1380 Session *s = iterator; 1381 1382 if (s != session()) { 1383 _copyToGroup->removeSession(iterator); 1384 } 1385 } 1386 delete _copyToGroup; 1387 _copyToGroup = nullptr; 1388 snapshot(); 1389 Q_EMIT copyInputChanged(this); 1390 } 1391 1392 void SessionController::searchClosed() 1393 { 1394 _isSearchBarEnabled = false; 1395 searchHistory(false); 1396 } 1397 1398 void SessionController::updateFilterList(const Profile::Ptr &profile) 1399 { 1400 if (profile != SessionManager::instance()->sessionProfile(session())) { 1401 return; 1402 } 1403 1404 auto *filterChain = view()->filterChain(); 1405 1406 const QString currentWordCharacters = profile->wordCharacters(); 1407 static QString _wordChars = currentWordCharacters; 1408 1409 if (profile->underlineFilesEnabled()) { 1410 if (_fileFilter == nullptr) { // Initialize 1411 _fileFilter = new FileFilter(session(), currentWordCharacters); 1412 filterChain->addFilter(_fileFilter); 1413 } else { 1414 // If wordCharacters changed, we need to change the static regex 1415 // pattern in _fileFilter 1416 if (_wordChars != currentWordCharacters) { 1417 _wordChars = currentWordCharacters; 1418 _fileFilter->updateRegex(currentWordCharacters); 1419 } 1420 } 1421 } else if (_fileFilter != nullptr) { // It became disabled, clean up 1422 filterChain->removeFilter(_fileFilter); 1423 delete _fileFilter; 1424 _fileFilter = nullptr; 1425 } 1426 1427 if (profile->underlineLinksEnabled()) { 1428 if (_urlFilter == nullptr) { // Initialize 1429 _urlFilter = new UrlFilter(); 1430 filterChain->addFilter(_urlFilter); 1431 } 1432 } else if (_urlFilter != nullptr) { // It became disabled, clean up 1433 filterChain->removeFilter(_urlFilter); 1434 delete _urlFilter; 1435 _urlFilter = nullptr; 1436 } 1437 1438 if (profile->allowEscapedLinks()) { 1439 if (_escapedUrlFilter == nullptr) { // Initialize 1440 _escapedUrlFilter = new EscapeSequenceUrlFilter(session(), view()); 1441 filterChain->addFilter(_escapedUrlFilter); 1442 } 1443 } else if (_escapedUrlFilter != nullptr) { // It became disabled, clean up 1444 filterChain->removeFilter(_escapedUrlFilter); 1445 delete _escapedUrlFilter; 1446 _escapedUrlFilter = nullptr; 1447 } 1448 1449 const bool allowColorFilters = profile->colorFilterEnabled(); 1450 if (!allowColorFilters && (_colorFilter != nullptr)) { 1451 filterChain->removeFilter(_colorFilter); 1452 delete _colorFilter; 1453 _colorFilter = nullptr; 1454 } else if (allowColorFilters && (_colorFilter == nullptr)) { 1455 _colorFilter = new ColorFilter(); 1456 filterChain->addFilter(_colorFilter); 1457 } 1458 } 1459 1460 void SessionController::setSearchStartToWindowCurrentLine() 1461 { 1462 setSearchStartTo(-1); 1463 } 1464 1465 void SessionController::setSearchStartTo(int line) 1466 { 1467 _searchStartLine = line; 1468 _prevSearchResultLine = line; 1469 } 1470 1471 void SessionController::listenForScreenWindowUpdates() 1472 { 1473 if (_listenForScreenWindowUpdates) { 1474 return; 1475 } 1476 1477 connect(view()->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter); 1478 connect(view()->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter); 1479 connect(view()->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, view(), QOverload<>::of(&Konsole::TerminalDisplay::update)); 1480 1481 _listenForScreenWindowUpdates = true; 1482 } 1483 1484 void SessionController::updateSearchFilter() 1485 { 1486 if ((_searchFilter != nullptr) && (!_searchBar.isNull())) { 1487 view()->processFilters(); 1488 } 1489 } 1490 1491 void SessionController::searchBarEvent() 1492 { 1493 QString selectedText = view()->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace); 1494 if (!selectedText.isEmpty()) { 1495 _searchBar->setSearchText(selectedText); 1496 } 1497 1498 if (_searchBar->isVisible()) { 1499 _searchBar->focusLineEdit(); 1500 } else { 1501 searchHistory(true); 1502 searchTextChanged(_searchBar->searchText()); 1503 _isSearchBarEnabled = true; 1504 } 1505 } 1506 1507 void SessionController::enableSearchBar(bool showSearchBar) 1508 { 1509 if (_searchBar.isNull()) { 1510 return; 1511 } 1512 1513 if (showSearchBar && !_searchBar->isVisible()) { 1514 setSearchStartToWindowCurrentLine(); 1515 } 1516 1517 _searchBar->setVisible(showSearchBar); 1518 if (showSearchBar) { 1519 connect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); 1520 connect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); 1521 connect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); 1522 } else { 1523 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); 1524 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); 1525 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); 1526 if ((!view().isNull()) && (view()->screenWindow() != nullptr)) { 1527 view()->screenWindow()->setCurrentResultLine(-1); 1528 } 1529 } 1530 } 1531 1532 bool SessionController::reverseSearchChecked() const 1533 { 1534 Q_ASSERT(_searchBar); 1535 1536 QBitArray options = _searchBar->optionsChecked(); 1537 return options.at(IncrementalSearchBar::ReverseSearch); 1538 } 1539 1540 QRegularExpression SessionController::regexpFromSearchBarOptions() const 1541 { 1542 QBitArray options = _searchBar->optionsChecked(); 1543 1544 QString text(_searchBar->searchText()); 1545 1546 QRegularExpression regExp; 1547 if (options.at(IncrementalSearchBar::RegExp)) { 1548 regExp.setPattern(text); 1549 } else { 1550 regExp.setPattern(QRegularExpression::escape(text)); 1551 } 1552 1553 if (!options.at(IncrementalSearchBar::MatchCase)) { 1554 regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); 1555 } 1556 1557 return regExp; 1558 } 1559 1560 // searchHistory() may be called either as a result of clicking a menu item or 1561 // as a result of changing the search bar widget 1562 void SessionController::searchHistory(bool showSearchBar) 1563 { 1564 enableSearchBar(showSearchBar); 1565 1566 if (!_searchBar.isNull()) { 1567 if (showSearchBar) { 1568 removeSearchFilter(); 1569 1570 listenForScreenWindowUpdates(); 1571 1572 _searchFilter = new RegExpFilter(); 1573 _searchFilter->setRegExp(regexpFromSearchBarOptions()); 1574 view()->filterChain()->addFilter(_searchFilter); 1575 view()->processFilters(); 1576 1577 setFindNextPrevEnabled(true); 1578 } else { 1579 setFindNextPrevEnabled(false); 1580 1581 removeSearchFilter(); 1582 1583 view()->setFocus(Qt::ActiveWindowFocusReason); 1584 } 1585 } 1586 } 1587 1588 void SessionController::setFindNextPrevEnabled(bool enabled) 1589 { 1590 _findNextAction->setEnabled(enabled); 1591 _findPreviousAction->setEnabled(enabled); 1592 } 1593 1594 void SessionController::searchTextChanged(const QString &text) 1595 { 1596 Q_ASSERT(view()->screenWindow()); 1597 1598 if (_searchText == text && _isSearchBarEnabled) { 1599 return; 1600 } 1601 1602 _searchText = text; 1603 1604 if (text.isEmpty()) { 1605 view()->clearMouseSelection(); 1606 view()->screenWindow()->scrollTo(_searchStartLine); 1607 } 1608 1609 // update search. this is called even when the text is 1610 // empty to clear the view's filters 1611 beginSearch(text, reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); 1612 } 1613 void SessionController::searchCompleted(bool success) 1614 { 1615 _prevSearchResultLine = view()->screenWindow()->currentResultLine(); 1616 1617 if (!_searchBar.isNull()) { 1618 _searchBar->setFoundMatch(success); 1619 } 1620 } 1621 1622 void SessionController::beginSearch(const QString &text, Enum::SearchDirection direction) 1623 { 1624 Q_ASSERT(_searchBar); 1625 Q_ASSERT(_searchFilter); 1626 1627 QRegularExpression regExp = regexpFromSearchBarOptions(); 1628 _searchFilter->setRegExp(regExp); 1629 1630 if (_searchStartLine < 0 || _searchStartLine > view()->screenWindow()->lineCount()) { 1631 if (direction == Enum::ForwardsSearch) { 1632 setSearchStartTo(view()->screenWindow()->currentLine()); 1633 } else { 1634 setSearchStartTo(view()->screenWindow()->currentLine() + view()->screenWindow()->windowLines()); 1635 } 1636 } 1637 1638 if (!regExp.pattern().isEmpty()) { 1639 view()->screenWindow()->setCurrentResultLine(-1); 1640 auto task = new SearchHistoryTask(this); 1641 1642 connect(task, &Konsole::SearchHistoryTask::completed, this, &Konsole::SessionController::searchCompleted); 1643 1644 task->setRegExp(regExp); 1645 task->setSearchDirection(direction); 1646 task->setAutoDelete(true); 1647 task->setStartLine(_searchStartLine); 1648 task->addScreenWindow(session(), view()->screenWindow()); 1649 task->execute(); 1650 } else if (text.isEmpty()) { 1651 searchCompleted(false); 1652 } 1653 1654 view()->processFilters(); 1655 } 1656 void SessionController::highlightMatches(bool highlight) 1657 { 1658 if (highlight) { 1659 view()->filterChain()->addFilter(_searchFilter); 1660 view()->processFilters(); 1661 } else { 1662 view()->filterChain()->removeFilter(_searchFilter); 1663 } 1664 1665 view()->update(); 1666 } 1667 1668 void SessionController::searchFrom() 1669 { 1670 Q_ASSERT(_searchBar); 1671 Q_ASSERT(_searchFilter); 1672 1673 if (reverseSearchChecked()) { 1674 setSearchStartTo(view()->screenWindow()->lineCount()); 1675 } else { 1676 setSearchStartTo(0); 1677 } 1678 1679 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); 1680 } 1681 void SessionController::findNextInHistory() 1682 { 1683 Q_ASSERT(_searchBar); 1684 Q_ASSERT(_searchFilter); 1685 1686 setSearchStartTo(_prevSearchResultLine); 1687 1688 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); 1689 } 1690 void SessionController::findPreviousInHistory() 1691 { 1692 Q_ASSERT(_searchBar); 1693 Q_ASSERT(_searchFilter); 1694 1695 setSearchStartTo(_prevSearchResultLine); 1696 1697 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::ForwardsSearch : Enum::BackwardsSearch); 1698 } 1699 void SessionController::updateMenuIconsAccordingToReverseSearchSetting() 1700 { 1701 if (reverseSearchChecked()) { 1702 _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 1703 _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 1704 } else { 1705 _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); 1706 _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); 1707 } 1708 } 1709 void SessionController::changeSearchMatch() 1710 { 1711 Q_ASSERT(_searchBar); 1712 Q_ASSERT(_searchFilter); 1713 1714 // reset Selection for new case match 1715 view()->clearMouseSelection(); 1716 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); 1717 } 1718 void SessionController::showHistoryOptions() 1719 { 1720 auto *dialog = new HistorySizeDialog(QApplication::activeWindow()); 1721 dialog->setAttribute(Qt::WA_DeleteOnClose); 1722 dialog->setModal(true); 1723 1724 const HistoryType ¤tHistory = session()->historyType(); 1725 if (currentHistory.isEnabled()) { 1726 if (currentHistory.isUnlimited()) { 1727 dialog->setMode(Enum::UnlimitedHistory); 1728 } else { 1729 dialog->setMode(Enum::FixedSizeHistory); 1730 dialog->setLineCount(currentHistory.maximumLineCount()); 1731 } 1732 } else { 1733 dialog->setMode(Enum::NoHistory); 1734 } 1735 1736 connect(dialog, &QDialog::accepted, this, [this, dialog]() { 1737 scrollBackOptionsChanged(dialog->mode(), dialog->lineCount()); 1738 }); 1739 1740 dialog->show(); 1741 } 1742 void SessionController::sessionResizeRequest(const QSize &size) 1743 { 1744 ////qDebug() << "View resize requested to " << size; 1745 view()->setSize(size.width(), size.height()); 1746 } 1747 void SessionController::scrollBackOptionsChanged(int mode, int lines) 1748 { 1749 switch (mode) { 1750 case Enum::NoHistory: 1751 session()->setHistoryType(HistoryTypeNone()); 1752 break; 1753 case Enum::FixedSizeHistory: 1754 session()->setHistoryType(CompactHistoryType(lines)); 1755 break; 1756 case Enum::UnlimitedHistory: 1757 session()->setHistoryType(HistoryTypeFile()); 1758 break; 1759 } 1760 } 1761 1762 void SessionController::saveHistory() 1763 { 1764 SessionTask *task = new SaveHistoryTask(this); 1765 task->setAutoDelete(true); 1766 task->addSession(session()); 1767 task->execute(); 1768 } 1769 1770 void SessionController::clearHistory() 1771 { 1772 session()->clearHistory(); 1773 view()->updateImage(); // To reset view scrollbar 1774 view()->repaint(); 1775 } 1776 1777 void SessionController::clearHistoryAndReset() 1778 { 1779 Profile::Ptr profile = SessionManager::instance()->sessionProfile(session()); 1780 QByteArray name = profile->defaultEncoding().toUtf8(); 1781 1782 Emulation *emulation = session()->emulation(); 1783 emulation->reset(false, true); 1784 session()->refresh(); 1785 session()->setCodec(QTextCodec::codecForName(name)); 1786 clearHistory(); 1787 } 1788 1789 void SessionController::increaseFontSize() 1790 { 1791 view()->terminalFont()->increaseFontSize(); 1792 } 1793 1794 void SessionController::decreaseFontSize() 1795 { 1796 view()->terminalFont()->decreaseFontSize(); 1797 } 1798 1799 void SessionController::resetFontSize() 1800 { 1801 view()->terminalFont()->resetFontSize(); 1802 } 1803 1804 void SessionController::notifyPrompt() 1805 { 1806 if (session()->isMonitorPrompt()) { 1807 KNotification *notification = 1808 new KNotification(session()->hasFocus() ? QStringLiteral("Prompt") : QStringLiteral("PromptHidden"), KNotification::CloseWhenWindowActivated); 1809 notification->setWindow(view()->windowHandle()); 1810 1811 notification->setText(i18n("The shell prompt is displayed in session '%1'", session()->nameTitle())); 1812 auto action = notification->addDefaultAction(i18n("Show session")); 1813 connect(action, &KNotificationAction::activated, this, [this, notification] { 1814 view()->notificationClicked(notification->xdgActivationToken()); 1815 }); 1816 notification->sendEvent(); 1817 if (_monitorOnce) { 1818 actionCollection()->action(QStringLiteral("monitor-prompt"))->setChecked(false); 1819 } 1820 } 1821 } 1822 void SessionController::monitorOnce(bool once) 1823 { 1824 _monitorOnce = once; 1825 } 1826 void SessionController::monitorPrompt(bool monitor) 1827 { 1828 session()->setMonitorPrompt(monitor); 1829 } 1830 void SessionController::monitorActivity(bool monitor) 1831 { 1832 session()->setMonitorActivity(monitor); 1833 } 1834 void SessionController::monitorSilence(bool monitor) 1835 { 1836 session()->setMonitorSilence(monitor); 1837 } 1838 void SessionController::monitorProcessFinish(bool monitor) 1839 { 1840 _monitorProcessFinish = monitor; 1841 } 1842 void SessionController::updateSessionIcon() 1843 { 1844 // If the default profile icon is being used, don't put it on the tab 1845 // Only show the icon if the user specifically chose one 1846 if (session()->iconName() == QStringLiteral("utilities-terminal")) { 1847 _sessionIconName = QString(); 1848 } else { 1849 _sessionIconName = session()->iconName(); 1850 } 1851 _sessionIcon = QIcon::fromTheme(_sessionIconName); 1852 1853 setIcon(_sessionIcon); 1854 } 1855 1856 void SessionController::updateReadOnlyActionStates() 1857 { 1858 bool readonly = isReadOnly(); 1859 QAction *readonlyAction = actionCollection()->action(QStringLiteral("view-readonly")); 1860 Q_ASSERT(readonlyAction != nullptr); 1861 readonlyAction->setIcon(QIcon::fromTheme(readonly ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked"))); 1862 readonlyAction->setChecked(readonly); 1863 1864 auto updateActionState = [this, readonly](const QString &name) { 1865 QAction *action = actionCollection()->action(name); 1866 if (action != nullptr) { 1867 action->setVisible(!readonly); 1868 } 1869 }; 1870 1871 updateActionState(QStringLiteral("edit_paste")); 1872 updateActionState(QStringLiteral("clear-history")); 1873 updateActionState(QStringLiteral("clear-history-and-reset")); 1874 updateActionState(QStringLiteral("edit-current-profile")); 1875 updateActionState(QStringLiteral("switch-profile")); 1876 updateActionState(QStringLiteral("adjust-history")); 1877 updateActionState(QStringLiteral("send-signal")); 1878 updateActionState(QStringLiteral("zmodem-upload")); 1879 1880 _codecAction->setEnabled(!readonly); 1881 1882 // Without the timer, when detaching a tab while the message widget is visible, 1883 // the size of the terminal becomes really small... 1884 QTimer::singleShot(0, this, [this, readonly]() { 1885 view()->updateReadOnlyState(readonly); 1886 }); 1887 } 1888 1889 bool SessionController::isReadOnly() const 1890 { 1891 if (!session().isNull()) { 1892 return session()->isReadOnly(); 1893 } else { 1894 return false; 1895 } 1896 } 1897 1898 bool SessionController::isCopyInputActive() const 1899 { 1900 return ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1); 1901 } 1902 1903 void SessionController::sessionAttributeChanged() 1904 { 1905 if (_sessionIconName != session()->iconName()) { 1906 updateSessionIcon(); 1907 } 1908 1909 QString title = session()->title(Session::DisplayedTitleRole); 1910 1911 // special handling for the "%w" marker which is replaced with the 1912 // window title set by the shell 1913 title.replace(QLatin1String("%w"), session()->userTitle()); 1914 // special handling for the "%#" marker which is replaced with the 1915 // number of the shell 1916 title.replace(QLatin1String("%#"), QString::number(session()->sessionId())); 1917 1918 if (title.isEmpty()) { 1919 title = session()->title(Session::NameRole); 1920 } 1921 1922 #ifdef Q_OS_WIN 1923 title = session()->userTitle(); 1924 #endif 1925 1926 setTitle(title); 1927 setColor(session()->color()); 1928 Q_EMIT rawTitleChanged(); 1929 } 1930 1931 void SessionController::sessionReadOnlyChanged() 1932 { 1933 updateReadOnlyActionStates(); 1934 1935 // Update all views 1936 const QList<TerminalDisplay *> viewsList = session()->views(); 1937 for (TerminalDisplay *terminalDisplay : viewsList) { 1938 if (terminalDisplay != view()) { 1939 terminalDisplay->updateReadOnlyState(isReadOnly()); 1940 } 1941 Q_EMIT readOnlyChanged(this); 1942 } 1943 } 1944 1945 void SessionController::showDisplayContextMenu(const QPoint &position) 1946 { 1947 // needed to make sure the popup menu is available, even if a hosting 1948 // application did not merge our GUI. 1949 if (factory() == nullptr) { 1950 if (clientBuilder() == nullptr) { 1951 // Client builder does not get deleted automatically, we handle this 1952 _clientBuilder.reset(new KXMLGUIBuilder(view())); 1953 setClientBuilder(_clientBuilder.get()); 1954 } 1955 1956 auto factory = new KXMLGUIFactory(clientBuilder(), view()); 1957 factory->addClient(this); 1958 } 1959 1960 QPointer<QMenu> popup = qobject_cast<QMenu *>(factory()->container(QStringLiteral("session-popup-menu"), this)); 1961 if (!popup.isNull()) { 1962 updateReadOnlyActionStates(); 1963 1964 auto contentSeparator = new QAction(popup); 1965 contentSeparator->setSeparator(true); 1966 1967 // We don't actually use this shortcut, but we need to display it for consistency :/ 1968 QAction *copy = actionCollection()->action(QStringLiteral("edit_copy_contextmenu")); 1969 copy->setShortcut(Konsole::ACCEL | Qt::Key_C); 1970 1971 // Adds a "Open Folder With" action 1972 const QUrl currentUrl = url().isLocalFile() ? url() : QUrl::fromLocalFile(QDir::homePath()); 1973 KFileItem item(currentUrl); 1974 1975 const auto old = popup->actions(); 1976 1977 const KFileItemListProperties props({item}); 1978 QScopedPointer<KFileItemActions> ac(new KFileItemActions()); 1979 ac->setItemListProperties(props); 1980 1981 ac->insertOpenWithActionsTo(popup->actions().value(4, nullptr), popup, QStringList{qApp->desktopFileName()}); 1982 1983 auto newActions = popup->actions(); 1984 for (auto *elm : old) { 1985 newActions.removeAll(elm); 1986 } 1987 // Finish Adding the "Open Folder With" action. 1988 1989 QList<QAction *> toRemove; 1990 // prepend content-specific actions such as "Open Link", "Copy Email Address" etc 1991 _currentHotSpot = view()->filterActions(position); 1992 if (_currentHotSpot != nullptr) { 1993 popup->insertActions(popup->actions().value(0, nullptr), _currentHotSpot->actions() << contentSeparator); 1994 popup->addAction(contentSeparator); 1995 toRemove = _currentHotSpot->setupMenu(popup.data()); 1996 1997 // The action above can create an action for Open Folder With, 1998 // for the selected folder, but then we have two different 1999 // Open Folder With - with different folders on each. 2000 // Change the text of the second one, that points to the 2001 // current folder. 2002 for (auto *action : newActions) { 2003 if (action->objectName() == QStringLiteral("openWith_submenu")) { 2004 action->setText(i18n("Open Current Folder With")); 2005 } 2006 } 2007 toRemove = toRemove + newActions; 2008 } else { 2009 toRemove = newActions; 2010 } 2011 2012 // always update this submenu before showing the context menu, 2013 // because the available search services might have changed 2014 // since the context menu is shown last time 2015 updateWebSearchMenu(); 2016 2017 _preventClose = true; 2018 2019 auto hamburger = static_cast<KHamburgerMenu *>(actionCollection()->action(KStandardAction::name(KStandardAction::HamburgerMenu))); 2020 if (hamburger) { 2021 hamburger->addToMenu(popup); 2022 } 2023 2024 // they are here. 2025 // qDebug() << popup->actions().indexOf(contentActions[0]) << popup->actions().indexOf(contentActions[1]) << popup->actions()[3]; 2026 QAction *chosen = popup->exec(QCursor::pos()); 2027 2028 // check for validity of the pointer to the popup menu 2029 if (!popup.isNull()) { 2030 delete contentSeparator; 2031 // Remove the 'Open with' actions from it. 2032 for (auto *act : toRemove) { 2033 popup->removeAction(act); 2034 } 2035 2036 // Remove the Accelerator for the copy shortcut so we don't have two actions with same shortcut. 2037 copy->setShortcut({}); 2038 } 2039 2040 // This should be at the end, to prevent crashes if the session 2041 // is closed from the menu in e.g. konsole kpart 2042 _preventClose = false; 2043 if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) { 2044 chosen->trigger(); 2045 } 2046 } else { 2047 qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << session()->title(Session::NameRole) 2048 << ", no GUI factory available to build the popup."; 2049 } 2050 } 2051 2052 void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event) 2053 { 2054 QCoreApplication::sendEvent(view(), event); 2055 setSearchStartToWindowCurrentLine(); 2056 } 2057 2058 void SessionController::sessionNotificationsChanged(Session::Notification notification, bool enabled) 2059 { 2060 Q_EMIT notificationChanged(this, notification, enabled); 2061 } 2062 2063 void SessionController::zmodemDownload() 2064 { 2065 QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz")); 2066 if (zmodem.isEmpty()) { 2067 zmodem = QStandardPaths::findExecutable(QStringLiteral("lrzsz-rz")); 2068 } 2069 if (zmodem.isEmpty()) { 2070 zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz")); 2071 } 2072 if (!zmodem.isEmpty()) { 2073 const QString path = QFileDialog::getExistingDirectory(view(), 2074 i18n("Save ZModem Download to..."), 2075 QDir::homePath(), 2076 QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); 2077 2078 if (!path.isEmpty()) { 2079 session()->startZModem(zmodem, path, QStringList()); 2080 return; 2081 } 2082 } else { 2083 KMessageBox::error(view(), 2084 i18n("<p>A ZModem file transfer attempt has been detected, " 2085 "but no suitable ZModem software was found on this system.</p>" 2086 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>")); 2087 } 2088 session()->cancelZModem(); 2089 } 2090 2091 void SessionController::zmodemUpload() 2092 { 2093 if (session()->isZModemBusy()) { 2094 KMessageBox::information(view(), i18n("<p>The current session already has a ZModem file transfer in progress.</p>")); 2095 return; 2096 } 2097 2098 QString zmodem = QStandardPaths::findExecutable(QStringLiteral("sz")); 2099 if (zmodem.isEmpty()) { 2100 zmodem = QStandardPaths::findExecutable(QStringLiteral("lrzsz-sz")); 2101 } 2102 if (zmodem.isEmpty()) { 2103 zmodem = QStandardPaths::findExecutable(QStringLiteral("lsz")); 2104 } 2105 if (zmodem.isEmpty()) { 2106 KMessageBox::error(view(), 2107 i18n("<p>No suitable ZModem software was found on this system.</p>" 2108 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>")); 2109 return; 2110 } 2111 2112 QStringList files = QFileDialog::getOpenFileNames(view(), i18n("Select Files for ZModem Upload"), QDir::homePath()); 2113 if (!files.isEmpty()) { 2114 session()->startZModem(zmodem, QString(), files); 2115 } 2116 } 2117 2118 bool SessionController::isKonsolePart() const 2119 { 2120 // Check to see if we are being called from Konsole or a KPart 2121 return !(qApp->applicationName() == QLatin1String("konsole")); 2122 } 2123 2124 QString SessionController::userTitle() const 2125 { 2126 if (!session().isNull()) { 2127 return session()->userTitle(); 2128 } else { 2129 return QString(); 2130 } 2131 } 2132 2133 bool SessionController::isValid() const 2134 { 2135 return _sessionDisplayConnection->isValid(); 2136 } 2137 2138 void SessionController::setVisible(QString name, bool visible) 2139 { 2140 /* For certain user profiles, testTerminalInterface crashes 2141 in QAction::setVisible() without this check. 2142 */ 2143 if (!actionCollection()->action(name)) { 2144 return; 2145 } 2146 actionCollection()->action(name)->setVisible(visible); 2147 } 2148 2149 KSelectAction *SessionController::copyInputActions() 2150 { 2151 return _copyInputActions; 2152 } 2153 2154 #include "moc_SessionController.cpp"