1 /*
2 SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
3 SPDX-FileCopyrightText: 2009 Thomas Dreibholz <dreibh@iem.uni-due.de>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 // Own
9 #include "SessionController.h"
10
11 #include "konsoledebug.h"
12 #include "profile/ProfileManager.h"
13 #include "terminalDisplay/TerminalColor.h"
14 #include "terminalDisplay/TerminalFonts.h"
15
16 // Qt
17 #include <QAction>
18 #include <QApplication>
19 #include <QFileDialog>
20 #include <QIcon>
21 #include <QKeyEvent>
22 #include <QList>
23 #include <QMenu>
24 #include <QPainter>
25 #include <QStandardPaths>
26 #include <QUrl>
27
28 // KDE
29 #include <KActionCollection>
30 #include <KActionMenu>
31 #include <KCodecAction>
32 #include <KConfigGroup>
33 #include <KLocalizedString>
34 #include <KMessageBox>
35 #include <KNotification>
36 #include <KSelectAction>
37 #include <KSharedConfig>
38 #include <KShell>
39 #include <KStringHandler>
40 #include <KToggleAction>
41 #include <KUriFilter>
42 #include <KXMLGUIBuilder>
43 #include <KXMLGUIFactory>
44 #include <KXmlGuiWindow>
45
46 #include <KIO/CommandLauncherJob>
47
48 #include <KIO/JobUiDelegate>
49 #include <KIO/OpenUrlJob>
50
51 #include <kconfigwidgets_version.h>
52 #include <kwidgetsaddons_version.h>
53
54 // Konsole
55 #include "CopyInputDialog.h"
56 #include "Emulation.h"
57 #include "HistorySizeDialog.h"
58 #include "RenameTabDialog.h"
59 #include "SaveHistoryTask.h"
60 #include "ScreenWindow.h"
61 #include "SearchHistoryTask.h"
62
63 #include "filterHotSpots/ColorFilter.h"
64 #include "filterHotSpots/EscapeSequenceUrlFilter.h"
65 #include "filterHotSpots/FileFilter.h"
66 #include "filterHotSpots/Filter.h"
67 #include "filterHotSpots/FilterChain.h"
68 #include "filterHotSpots/HotSpot.h"
69 #include "filterHotSpots/RegExpFilter.h"
70 #include "filterHotSpots/UrlFilter.h"
71
72 #include "history/HistoryType.h"
73 #include "history/HistoryTypeFile.h"
74 #include "history/HistoryTypeNone.h"
75 #include "history/compact/CompactHistoryType.h"
76
77 #include "profile/ProfileList.h"
78
79 #include "SessionGroup.h"
80 #include "SessionManager.h"
81
82 #include "widgets/EditProfileDialog.h"
83 #include "widgets/IncrementalSearchBar.h"
84
85 #include "terminalDisplay/TerminalColor.h"
86 #include "terminalDisplay/TerminalDisplay.h"
87
88 // For Unix signal names
89 #include <csignal>
90
91 using namespace Konsole;
92
93 QSet<SessionController *> SessionController::_allControllers;
94 int SessionController::_lastControllerId;
95
SessionController(Session * sessionParam,TerminalDisplay * viewParam,QObject * parent)96 SessionController::SessionController(Session *sessionParam, TerminalDisplay *viewParam, QObject *parent)
97 : ViewProperties(parent)
98 , KXMLGUIClient()
99 , _copyToGroup(nullptr)
100 , _profileList(nullptr)
101 , _sessionIcon(QIcon())
102 , _sessionIconName(QString())
103 , _searchFilter(nullptr)
104 , _urlFilter(nullptr)
105 , _fileFilter(nullptr)
106 , _colorFilter(nullptr)
107 , _copyInputToAllTabsAction(nullptr)
108 , _findAction(nullptr)
109 , _findNextAction(nullptr)
110 , _findPreviousAction(nullptr)
111 , _interactionTimer(nullptr)
112 , _searchStartLine(0)
113 , _prevSearchResultLine(0)
114 , _codecAction(nullptr)
115 , _switchProfileMenu(nullptr)
116 , _webSearchMenu(nullptr)
117 , _listenForScreenWindowUpdates(false)
118 , _preventClose(false)
119 , _selectedText(QString())
120 , _showMenuAction(nullptr)
121 , _bookmarkValidProgramsToClear(QStringList())
122 , _isSearchBarEnabled(false)
123 , _searchBar(viewParam->searchBar())
124 , _monitorProcessFinish(false)
125 , _escapedUrlFilter(nullptr)
126 {
127 Q_ASSERT(sessionParam);
128 Q_ASSERT(viewParam);
129
130 _sessionDisplayConnection = new SessionDisplayConnection(sessionParam, viewParam, this);
131 viewParam->setSessionController(this);
132
133 // handle user interface related to session (menus etc.)
134 if (isKonsolePart()) {
135 setComponentName(QStringLiteral("konsole"), i18n("Konsole"));
136 setXMLFile(QStringLiteral("partui.rc"));
137 setupCommonActions();
138 } else {
139 setXMLFile(QStringLiteral("sessionui.rc"));
140 setupCommonActions();
141 setupExtraActions();
142 }
143
144 connect(this, &SessionController::requestPrint, view(), &TerminalDisplay::printScreen);
145
146 actionCollection()->addAssociatedWidget(viewParam);
147
148 const QList<QAction *> actionsList = actionCollection()->actions();
149 for (QAction *action : actionsList) {
150 action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
151 }
152
153 setIdentifier(++_lastControllerId);
154 sessionAttributeChanged();
155
156 connect(view(), &TerminalDisplay::compositeFocusChanged, this, &SessionController::viewFocusChangeHandler);
157
158 // install filter on the view to highlight URLs and files
159 updateFilterList(SessionManager::instance()->sessionProfile(session()));
160
161 // listen for changes in session, we might need to change the enabled filters
162 connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList);
163
164 // listen for session resize requests
165 connect(session(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest);
166
167 // listen for popup menu requests
168 connect(view(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu);
169
170 // move view to newest output when keystrokes occur
171 connect(view(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput);
172
173 // listen to activity / silence notifications from session
174 connect(session(), &Konsole::Session::notificationsChanged, this, &Konsole::SessionController::sessionNotificationsChanged);
175 // listen to title and icon changes
176 connect(session(), &Konsole::Session::sessionAttributeChanged, this, &Konsole::SessionController::sessionAttributeChanged);
177 connect(session(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged);
178
179 connect(this, &Konsole::SessionController::tabRenamedByUser, session(), &Konsole::Session::tabTitleSetByUser);
180 connect(this, &Konsole::SessionController::tabColoredByUser, session(), &Konsole::Session::tabColorSetByUser);
181
182 connect(session(), &Konsole::Session::currentDirectoryChanged, this, &Konsole::SessionController::currentDirectoryChanged);
183
184 // listen for color changes
185 connect(session(), &Konsole::Session::changeBackgroundColorRequest, view()->terminalColor(), &TerminalColor::setBackgroundColor);
186 connect(session(), &Konsole::Session::changeForegroundColorRequest, view()->terminalColor(), &TerminalColor::setForegroundColor);
187
188 // update the title when the session starts
189 connect(session(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot);
190
191 // listen for output changes to set activity flag
192 connect(session()->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity);
193
194 // listen for detection of ZModem transfer
195 connect(session(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload);
196 connect(session(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload);
197
198 // listen for flow control status changes
199 connect(session(), &Konsole::Session::flowControlEnabledChanged, view(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled);
200 view()->setFlowControlWarningEnabled(session()->flowControlEnabled());
201
202 // take a snapshot of the session state every so often when
203 // user activity occurs
204 //
205 // the timer is owned by the session so that it will be destroyed along
206 // with the session
207 _interactionTimer = new QTimer(session());
208 _interactionTimer->setSingleShot(true);
209 _interactionTimer->setInterval(500);
210 connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
211 connect(view(), &Konsole::TerminalDisplay::compositeFocusChanged, this, [this](bool focused) {
212 if (focused) {
213 interactionHandler();
214 }
215 });
216 connect(view(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::interactionHandler);
217
218 // take a snapshot of the session state periodically in the background
219 auto backgroundTimer = new QTimer(session());
220 backgroundTimer->setSingleShot(false);
221 backgroundTimer->setInterval(2000);
222 connect(backgroundTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
223 backgroundTimer->start();
224
225 // xterm '10;?' request
226 connect(session(), &Konsole::Session::getForegroundColor, this, &Konsole::SessionController::sendForegroundColor);
227 // xterm '11;?' request
228 connect(session(), &Konsole::Session::getBackgroundColor, this, &Konsole::SessionController::sendBackgroundColor);
229
230 _allControllers.insert(this);
231
232 // A list of programs that accept Ctrl+C to clear command line used
233 // before outputting bookmark.
234 _bookmarkValidProgramsToClear =
235 QStringList({QStringLiteral("bash"), QStringLiteral("fish"), QStringLiteral("sh"), QStringLiteral("tcsh"), QStringLiteral("zsh")});
236
237 setupSearchBar();
238 _searchBar->setVisible(_isSearchBarEnabled);
239 }
240
~SessionController()241 SessionController::~SessionController()
242 {
243 _allControllers.remove(this);
244
245 if (factory() != nullptr) {
246 factory()->removeClient(this);
247 }
248 }
trackOutput(QKeyEvent * event)249 void SessionController::trackOutput(QKeyEvent *event)
250 {
251 Q_ASSERT(view()->screenWindow());
252
253 // Qt has broken something, so we can't rely on just checking if certain
254 // keys are passed as modifiers anymore.
255 const int key = event->key();
256
257 /* clang-format off */
258 const bool shouldNotTriggerScroll =
259 key == Qt::Key_Super_L
260 || key == Qt::Key_Super_R
261 || key == Qt::Key_Hyper_L
262 || key == Qt::Key_Hyper_R
263 || key == Qt::Key_Shift
264 || key == Qt::Key_Control
265 || key == Qt::Key_Meta
266 || key == Qt::Key_Alt
267 || key == Qt::Key_AltGr
268 || key == Qt::Key_CapsLock
269 || key == Qt::Key_NumLock
270 || key == Qt::Key_ScrollLock;
271 /* clang-format on */
272
273 // Only jump to the bottom if the user actually typed something in,
274 // not if the user e. g. just pressed a modifier.
275 if (event->text().isEmpty() && ((event->modifiers() != 0u) || shouldNotTriggerScroll)) {
276 return;
277 }
278
279 view()->screenWindow()->setTrackOutput(true);
280 }
281
viewFocusChangeHandler(bool focused)282 void SessionController::viewFocusChangeHandler(bool focused)
283 {
284 if (focused) {
285 // notify the world that the view associated with this session has been focused
286 // used by the view manager to update the title of the MainWindow widget containing the view
287 Q_EMIT viewFocused(this);
288
289 // when the view is focused, set bell events from the associated session to be delivered
290 // by the focused view
291
292 // first, disconnect any other views which are listening for bell signals from the session
293 disconnect(session(), &Konsole::Session::bellRequest, nullptr, nullptr);
294 // second, connect the newly focused view to listen for the session's bell signal
295 connect(session(), &Konsole::Session::bellRequest, view(), &Konsole::TerminalDisplay::bell);
296
297 if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) {
298 // A session with "Copy To All Tabs" has come into focus:
299 // Ensure that newly created sessions are included in _copyToGroup!
300 copyInputToAllTabs();
301 }
302 }
303 }
304
interactionHandler()305 void SessionController::interactionHandler()
306 {
307 _interactionTimer->start();
308 }
309
snapshot()310 void SessionController::snapshot()
311 {
312 Q_ASSERT(!session().isNull());
313
314 QString title = session()->getDynamicTitle();
315 title = title.simplified();
316
317 // Visualize that the session is broadcasting to others
318 if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) {
319 title.append(QLatin1Char('*'));
320 }
321
322 // use the fallback title if needed
323 if (title.isEmpty()) {
324 title = session()->title(Session::NameRole);
325 }
326
327 QColor color = session()->color();
328 // use the fallback color if needed
329 if (!color.isValid()) {
330 color = QColor(QColor::Invalid);
331 }
332
333 // apply new title
334 session()->setTitle(Session::DisplayedTitleRole, title);
335
336 // apply new color
337 session()->setColor(color);
338
339 // check if foreground process ended and notify if this option was requested
340 if (_monitorProcessFinish) {
341 bool isForegroundProcessActive = session()->isForegroundProcessActive();
342 if (!_previousForegroundProcessName.isNull() && !isForegroundProcessActive) {
343 KNotification::event(session()->hasFocus() ? QStringLiteral("ProcessFinished") : QStringLiteral("ProcessFinishedHidden"),
344 i18n("The process '%1' has finished running in session '%2'", _previousForegroundProcessName, session()->nameTitle()),
345 QPixmap(),
346 QApplication::activeWindow(),
347 KNotification::CloseWhenWidgetActivated);
348 }
349 _previousForegroundProcessName = isForegroundProcessActive ? session()->foregroundProcessName() : QString();
350 }
351
352 // do not forget icon
353 updateSessionIcon();
354 }
355
currentDir() const356 QString SessionController::currentDir() const
357 {
358 return session()->currentWorkingDirectory();
359 }
360
url() const361 QUrl SessionController::url() const
362 {
363 return session()->getUrl();
364 }
365
rename()366 void SessionController::rename()
367 {
368 renameSession();
369 }
370
openUrl(const QUrl & url)371 void SessionController::openUrl(const QUrl &url)
372 {
373 // Clear shell's command line
374 if (!session()->isForegroundProcessActive() && _bookmarkValidProgramsToClear.contains(session()->foregroundProcessName())) {
375 session()->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C
376 }
377
378 // handle local paths
379 if (url.isLocalFile()) {
380 QString path = url.toLocalFile();
381 session()->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r'));
382 } else if (url.scheme().isEmpty()) {
383 // QUrl couldn't parse what the user entered into the URL field
384 // so just dump it to the shell
385 // If you change this, change it also in autotests/BookMarkTest.cpp
386 QString command = QUrl::fromPercentEncoding(url.toEncoded());
387 if (!command.isEmpty()) {
388 session()->sendTextToTerminal(command, QLatin1Char('\r'));
389 }
390 } else if (url.scheme() == QLatin1String("ssh")) {
391 QString sshCommand = QStringLiteral("ssh ");
392
393 if (url.port() > -1) {
394 sshCommand += QStringLiteral("-p %1 ").arg(url.port());
395 }
396 if (!url.userName().isEmpty()) {
397 sshCommand += (url.userName() + QLatin1Char('@'));
398 }
399 if (!url.host().isEmpty()) {
400 sshCommand += url.host();
401 }
402 session()->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
403
404 } else if (url.scheme() == QLatin1String("telnet")) {
405 QString telnetCommand = QStringLiteral("telnet ");
406
407 if (!url.userName().isEmpty()) {
408 telnetCommand += QStringLiteral("-l %1 ").arg(url.userName());
409 }
410 if (!url.host().isEmpty()) {
411 telnetCommand += (url.host() + QLatin1Char(' '));
412 }
413 if (url.port() > -1) {
414 telnetCommand += QString::number(url.port());
415 }
416
417 session()->sendTextToTerminal(telnetCommand, QLatin1Char('\r'));
418
419 } else {
420 // TODO Implement handling for other Url types
421
422 KMessageBox::sorry(view()->window(), i18n("Konsole does not know how to open the bookmark: ") + url.toDisplayString());
423
424 qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know"
425 << " how to handle the protocol " << url.scheme();
426 }
427 }
428
setupPrimaryScreenSpecificActions(bool use)429 void SessionController::setupPrimaryScreenSpecificActions(bool use)
430 {
431 KActionCollection *collection = actionCollection();
432 QAction *clearAction = collection->action(QStringLiteral("clear-history"));
433 QAction *resetAction = collection->action(QStringLiteral("clear-history-and-reset"));
434 QAction *selectAllAction = collection->action(QStringLiteral("select-all"));
435 QAction *selectLineAction = collection->action(QStringLiteral("select-line"));
436
437 // these actions are meaningful only when primary screen is used.
438 clearAction->setEnabled(use);
439 resetAction->setEnabled(use);
440 selectAllAction->setEnabled(use);
441 selectLineAction->setEnabled(use);
442 }
443
selectionChanged(const QString & selectedText)444 void SessionController::selectionChanged(const QString &selectedText)
445 {
446 _selectedText = selectedText;
447 updateCopyAction(selectedText);
448 }
449
updateCopyAction(const QString & selectedText)450 void SessionController::updateCopyAction(const QString &selectedText)
451 {
452 QAction *copyAction = actionCollection()->action(QStringLiteral("edit_copy"));
453 QAction *copyContextMenu = actionCollection()->action(QStringLiteral("edit_copy_contextmenu"));
454 // copy action is meaningful only when some text is selected.
455 copyAction->setEnabled(!selectedText.isEmpty());
456 copyContextMenu->setVisible(!selectedText.isEmpty());
457 }
458
updateWebSearchMenu()459 void SessionController::updateWebSearchMenu()
460 {
461 // reset
462 _webSearchMenu->setVisible(false);
463 _webSearchMenu->menu()->clear();
464
465 if (_selectedText.isEmpty()) {
466 return;
467 }
468
469 QString searchText = _selectedText;
470 searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
471
472 if (searchText.isEmpty()) {
473 return;
474 }
475
476 // Is 'Enable Web shortcuts' checked in System Settings?
477 KSharedConfigPtr kuriikwsConfig = KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"));
478 if (!kuriikwsConfig->group("General").readEntry("EnableWebShortcuts", true)) {
479 return;
480 }
481
482 KUriFilterData filterData(searchText);
483 filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);
484
485 if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) {
486 const QStringList searchProviders = filterData.preferredSearchProviders();
487 if (!searchProviders.isEmpty()) {
488 _webSearchMenu->setText(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 16)));
489
490 QAction *action = nullptr;
491
492 for (const QString &searchProvider : searchProviders) {
493 action = new QAction(searchProvider, _webSearchMenu);
494 action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
495 action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
496 connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction);
497 _webSearchMenu->addAction(action);
498 }
499
500 _webSearchMenu->addSeparator();
501
502 action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu);
503 action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
504 connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts);
505 _webSearchMenu->addAction(action);
506
507 _webSearchMenu->setVisible(true);
508 }
509 }
510 }
511
handleWebShortcutAction()512 void SessionController::handleWebShortcutAction()
513 {
514 auto *action = qobject_cast<QAction *>(sender());
515 if (action == nullptr) {
516 return;
517 }
518
519 KUriFilterData filterData(action->data().toString());
520
521 if (KUriFilter::self()->filterUri(filterData, {QStringLiteral("kurisearchfilter")})) {
522 const QUrl url = filterData.uri();
523
524 auto *job = new KIO::OpenUrlJob(url);
525 job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
526 job->start();
527 }
528 }
529
configureWebShortcuts()530 void SessionController::configureWebShortcuts()
531 {
532 auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), {QStringLiteral("webshortcuts")});
533 job->start();
534 }
535
sendSignal(QAction * action)536 void SessionController::sendSignal(QAction *action)
537 {
538 const auto signal = action->data().toInt();
539 session()->sendSignal(signal);
540 }
541
sendForegroundColor(uint terminator)542 void SessionController::sendForegroundColor(uint terminator)
543 {
544 const QColor c = view()->terminalColor()->foregroundColor();
545 session()->reportForegroundColor(c, terminator);
546 }
547
sendBackgroundColor(uint terminator)548 void Konsole::SessionController::sendBackgroundColor(uint terminator)
549 {
550 const QColor c = view()->terminalColor()->backgroundColor();
551 session()->reportBackgroundColor(c, terminator);
552 }
553
toggleReadOnly()554 void SessionController::toggleReadOnly()
555 {
556 auto *action = qobject_cast<QAction *>(sender());
557 if (action != nullptr) {
558 bool readonly = !isReadOnly();
559 session()->setReadOnly(readonly);
560 }
561 }
562
removeSearchFilter()563 void SessionController::removeSearchFilter()
564 {
565 if (_searchFilter == nullptr) {
566 return;
567 }
568
569 view()->filterChain()->removeFilter(_searchFilter);
570 delete _searchFilter;
571 _searchFilter = nullptr;
572 }
573
setupSearchBar()574 void SessionController::setupSearchBar()
575 {
576 connect(_searchBar, &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived);
577 connect(_searchBar, &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed);
578 connect(_searchBar, &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom);
579 connect(_searchBar, &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory);
580 connect(_searchBar, &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory);
581 connect(_searchBar, &Konsole::IncrementalSearchBar::reverseSearchToggled, this, &Konsole::SessionController::updateMenuIconsAccordingToReverseSearchSetting);
582 connect(_searchBar, &Konsole::IncrementalSearchBar::highlightMatchesToggled, this, &Konsole::SessionController::highlightMatches);
583 connect(_searchBar, &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch);
584 connect(_searchBar, &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch);
585
586 updateMenuIconsAccordingToReverseSearchSetting();
587 }
588
setShowMenuAction(QAction * action)589 void SessionController::setShowMenuAction(QAction *action)
590 {
591 _showMenuAction = action;
592 }
593
setupCommonActions()594 void SessionController::setupCommonActions()
595 {
596 KActionCollection *collection = actionCollection();
597
598 // Close Session
599 QAction *action = collection->addAction(QStringLiteral("close-session"), this, &SessionController::closeSession);
600 action->setText(i18n("&Close Session"));
601
602 action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
603 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_W);
604
605 // Open Browser
606 action = collection->addAction(QStringLiteral("open-browser"), this, &SessionController::openBrowser);
607 action->setText(i18n("Open File Manager"));
608 action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
609
610 // Copy and Paste
611 action = KStandardAction::copy(this, &SessionController::copy, collection);
612 #ifdef Q_OS_MACOS
613 // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META)
614 // TODO: check what happens if we leave it to Qt to assign the default?
615 collection->setDefaultShortcut(action, Qt::META | Qt::Key_C);
616 #else
617 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_C);
618 #endif
619 // disabled at first, since nothing has been selected now
620 action->setEnabled(false);
621
622 // We need a different QAction on the context menu because one will be disabled when there's no selection,
623 // other will be hidden.
624 action = collection->addAction(QStringLiteral("edit_copy_contextmenu"));
625 action->setText(i18n("Copy"));
626 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
627 action->setVisible(false);
628 connect(action, &QAction::triggered, this, &SessionController::copy);
629
630 action = KStandardAction::paste(this, &SessionController::paste, collection);
631 QList<QKeySequence> pasteShortcut;
632 #ifdef Q_OS_MACOS
633 pasteShortcut.append(QKeySequence(Qt::META | Qt::Key_V));
634 // No Insert key on Mac keyboards
635 #else
636 pasteShortcut.append(QKeySequence(Konsole::ACCEL | Qt::SHIFT | Qt::Key_V));
637 pasteShortcut.append(QKeySequence(Qt::SHIFT | Qt::Key_Insert));
638 #endif
639 collection->setDefaultShortcuts(action, pasteShortcut);
640
641 action = collection->addAction(QStringLiteral("paste-selection"), this, &SessionController::pasteFromX11Selection);
642 action->setText(i18n("Paste Selection"));
643 #ifdef Q_OS_MACOS
644 collection->setDefaultShortcut(action, Qt::META | Qt::SHIFT | Qt::Key_V);
645 #else
646 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_Insert);
647 #endif
648
649 _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
650 _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
651 _webSearchMenu->setVisible(false);
652 collection->addAction(QStringLiteral("web-search"), _webSearchMenu);
653
654 action = collection->addAction(QStringLiteral("select-all"), this, &SessionController::selectAll);
655 action->setText(i18n("&Select All"));
656 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
657
658 action = collection->addAction(QStringLiteral("select-line"), this, &SessionController::selectLine);
659 action->setText(i18n("Select &Line"));
660
661 action = KStandardAction::saveAs(this, &SessionController::saveHistory, collection);
662 action->setText(i18n("Save Output &As..."));
663 #ifdef Q_OS_MACOS
664 action->setShortcut(QKeySequence(Qt::META | Qt::Key_S));
665 #endif
666
667 action = KStandardAction::print(this, &SessionController::requestPrint, collection);
668 action->setText(i18n("&Print Screen..."));
669 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_P);
670
671 action = collection->addAction(QStringLiteral("adjust-history"), this, &SessionController::showHistoryOptions);
672 action->setText(i18n("Adjust Scrollback..."));
673 action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
674
675 action = collection->addAction(QStringLiteral("clear-history"), this, &SessionController::clearHistory);
676 action->setText(i18n("Clear Scrollback"));
677 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
678
679 action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, &SessionController::clearHistoryAndReset);
680 action->setText(i18n("Clear Scrollback and Reset"));
681 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
682 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::SHIFT | Qt::Key_K);
683
684 // Profile Options
685 action = collection->addAction(QStringLiteral("edit-current-profile"), this, &SessionController::editCurrentProfile);
686 action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
687 setEditProfileActionText(SessionManager::instance()->sessionProfile(session()));
688
689 _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this);
690 collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu);
691 connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu);
692 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 77, 0)
693 _switchProfileMenu->setPopupMode(QToolButton::MenuButtonPopup);
694 #else
695 _switchProfileMenu->setDelayed(false);
696 #endif
697
698 // History
699 _findAction = KStandardAction::find(this, &SessionController::searchBarEvent, collection);
700
701 _findNextAction = KStandardAction::findNext(this, &SessionController::findNextInHistory, collection);
702 _findNextAction->setEnabled(false);
703
704 _findPreviousAction = KStandardAction::findPrev(this, &SessionController::findPreviousInHistory, collection);
705 _findPreviousAction->setEnabled(false);
706
707 #ifdef Q_OS_MACOS
708 collection->setDefaultShortcut(_findAction, Qt::META | Qt::Key_F);
709 collection->setDefaultShortcut(_findNextAction, Qt::META | Qt::Key_G);
710 collection->setDefaultShortcut(_findPreviousAction, Qt::META | Qt::SHIFT | Qt::Key_G);
711 #else
712 collection->setDefaultShortcut(_findAction, Konsole::ACCEL | Qt::SHIFT | Qt::Key_F);
713 collection->setDefaultShortcut(_findNextAction, Qt::Key_F3);
714 collection->setDefaultShortcut(_findPreviousAction, Qt::SHIFT | Qt::Key_F3);
715 #endif
716
717 // Character Encoding
718 _codecAction = new KCodecAction(i18n("Set &Encoding"), this);
719 _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set")));
720 collection->addAction(QStringLiteral("set-encoding"), _codecAction);
721 _codecAction->setCurrentCodec(QString::fromUtf8(session()->codec()));
722 connect(session(), &Konsole::Session::sessionCodecChanged, this, &Konsole::SessionController::updateCodecAction);
723 connect(_codecAction,
724 #if KCONFIGWIDGETS_VERSION >= QT_VERSION_CHECK(5, 78, 0)
725 QOverload<QTextCodec *>::of(&KCodecAction::codecTriggered),
726 this,
727 #else
728 QOverload<QTextCodec *>::of(&KCodecAction::triggered),
729 this,
730 #endif
731 &Konsole::SessionController::changeCodec);
732
733 // Read-only
734 action = collection->addAction(QStringLiteral("view-readonly"), this, &SessionController::toggleReadOnly);
735 action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only"));
736 action->setCheckable(true);
737 updateReadOnlyActionStates();
738 }
739
setupExtraActions()740 void SessionController::setupExtraActions()
741 {
742 KActionCollection *collection = actionCollection();
743
744 // Rename Session
745 QAction *action = collection->addAction(QStringLiteral("rename-session"), this, &SessionController::renameSession);
746 action->setText(i18n("&Configure or Rename Tab..."));
747 action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
748 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::ALT | Qt::Key_S);
749
750 // Copy input to ==> all tabs
751 auto *copyInputToAllTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-all-tabs"));
752 copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
753 copyInputToAllTabsAction->setData(CopyInputToAllTabsMode);
754 // this action is also used in other place, so remember it
755 _copyInputToAllTabsAction = copyInputToAllTabsAction;
756
757 // Copy input to ==> selected tabs
758 auto *copyInputToSelectedTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-selected-tabs"));
759 copyInputToSelectedTabsAction->setText(i18n("&Select Tabs..."));
760 collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL | Qt::SHIFT | Qt::Key_Period);
761 copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode);
762
763 // Copy input to ==> none
764 auto *copyInputToNoneAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-none"));
765 copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
766 collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL | Qt::SHIFT | Qt::Key_Slash);
767 copyInputToNoneAction->setData(CopyInputToNoneMode);
768 copyInputToNoneAction->setChecked(true); // the default state
769
770 // The "Copy Input To" submenu
771 // The above three choices are represented as combo boxes
772 auto *copyInputActions = collection->add<KSelectAction>(QStringLiteral("copy-input-to"));
773 copyInputActions->setText(i18n("Copy Input To"));
774 copyInputActions->addAction(copyInputToAllTabsAction);
775 copyInputActions->addAction(copyInputToSelectedTabsAction);
776 copyInputActions->addAction(copyInputToNoneAction);
777 connect(copyInputActions, QOverload<QAction *>::of(&KSelectAction::triggered), this, &Konsole::SessionController::copyInputActionsTriggered);
778
779 action = collection->addAction(QStringLiteral("zmodem-upload"), this, &SessionController::zmodemUpload);
780 action->setText(i18n("&ZModem Upload..."));
781 action->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
782 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::ALT | Qt::Key_U);
783
784 // Monitor
785 KToggleAction *toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this);
786 collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::SHIFT | Qt::Key_A);
787 action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction);
788 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity);
789 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn")));
790
791 toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this);
792 collection->setDefaultShortcut(toggleAction, Konsole::ACCEL | Qt::SHIFT | Qt::Key_I);
793 action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction);
794 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence);
795 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-copy")));
796
797 toggleAction = new KToggleAction(i18n("Monitor for Process Finishing"), this);
798 action = collection->addAction(QStringLiteral("monitor-process-finish"), toggleAction);
799 connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorProcessFinish);
800 action->setIcon(QIcon::fromTheme(QStringLiteral("tools-media-optical-burn-image")));
801
802 // Text Size
803 action = collection->addAction(QStringLiteral("enlarge-font"), this, &SessionController::increaseFontSize);
804 action->setText(i18n("Enlarge Font"));
805 action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more")));
806 QList<QKeySequence> enlargeFontShortcut;
807 enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL | Qt::Key_Plus));
808 enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL | Qt::Key_Equal));
809 collection->setDefaultShortcuts(action, enlargeFontShortcut);
810
811 action = collection->addAction(QStringLiteral("shrink-font"), this, &SessionController::decreaseFontSize);
812 action->setText(i18n("Shrink Font"));
813 action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less")));
814 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_Minus);
815
816 action = collection->addAction(QStringLiteral("reset-font-size"), this, &SessionController::resetFontSize);
817 action->setText(i18n("Reset Font Size"));
818 collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::ALT | Qt::Key_0);
819
820 // Send signal
821 auto *sendSignalActions = collection->add<KSelectAction>(QStringLiteral("send-signal"));
822 sendSignalActions->setText(i18n("Send Signal"));
823 connect(sendSignalActions, QOverload<QAction *>::of(&KSelectAction::triggered), this, &Konsole::SessionController::sendSignal);
824
825 action = collection->addAction(QStringLiteral("sigstop-signal"));
826 action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)"));
827 action->setData(SIGSTOP);
828 sendSignalActions->addAction(action);
829
830 action = collection->addAction(QStringLiteral("sigcont-signal"));
831 action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)"));
832 action->setData(SIGCONT);
833 sendSignalActions->addAction(action);
834
835 action = collection->addAction(QStringLiteral("sighup-signal"));
836 action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)"));
837 action->setData(SIGHUP);
838 sendSignalActions->addAction(action);
839
840 action = collection->addAction(QStringLiteral("sigint-signal"));
841 action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)"));
842 action->setData(SIGINT);
843 sendSignalActions->addAction(action);
844
845 action = collection->addAction(QStringLiteral("sigterm-signal"));
846 action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)"));
847 action->setData(SIGTERM);
848 sendSignalActions->addAction(action);
849
850 action = collection->addAction(QStringLiteral("sigkill-signal"));
851 action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)"));
852 action->setData(SIGKILL);
853 sendSignalActions->addAction(action);
854
855 action = collection->addAction(QStringLiteral("sigusr1-signal"));
856 action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)"));
857 action->setData(SIGUSR1);
858 sendSignalActions->addAction(action);
859
860 action = collection->addAction(QStringLiteral("sigusr2-signal"));
861 action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)"));
862 action->setData(SIGUSR2);
863 sendSignalActions->addAction(action);
864 }
865
switchProfile(const Profile::Ptr & profile)866 void SessionController::switchProfile(const Profile::Ptr &profile)
867 {
868 SessionManager::instance()->setSessionProfile(session(), profile);
869 _switchProfileMenu->setIcon(QIcon::fromTheme(profile->icon()));
870 updateFilterList(profile);
871 setEditProfileActionText(profile);
872 }
873
setEditProfileActionText(const Profile::Ptr & profile)874 void SessionController::setEditProfileActionText(const Profile::Ptr &profile)
875 {
876 QAction *action = actionCollection()->action(QStringLiteral("edit-current-profile"));
877 if (profile->isFallback()) {
878 action->setText(i18n("Create New Profile..."));
879 } else {
880 action->setText(i18n("Edit Current Profile..."));
881 }
882 }
883
prepareSwitchProfileMenu()884 void SessionController::prepareSwitchProfileMenu()
885 {
886 if (_switchProfileMenu->menu()->isEmpty()) {
887 _profileList = new ProfileList(false, this);
888 connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile);
889 }
890
891 _switchProfileMenu->menu()->clear();
892 _switchProfileMenu->menu()->addActions(_profileList->actions());
893 }
updateCodecAction(QTextCodec * codec)894 void SessionController::updateCodecAction(QTextCodec *codec)
895 {
896 _codecAction->setCurrentCodec(codec);
897 }
898
changeCodec(QTextCodec * codec)899 void SessionController::changeCodec(QTextCodec *codec)
900 {
901 session()->setCodec(codec);
902 }
903
editCurrentProfile()904 void SessionController::editCurrentProfile()
905 {
906 auto *dialog = new EditProfileDialog(QApplication::activeWindow());
907 dialog->setAttribute(Qt::WA_DeleteOnClose);
908 dialog->setModal(true);
909
910 auto profile = SessionManager::instance()->sessionProfile(session());
911 auto state = EditProfileDialog::ExistingProfile;
912 // Don't edit the Fallback profile, instead create a new one
913 if (profile->isFallback()) {
914 auto newProfile = Profile::Ptr(new Profile(profile));
915 newProfile->clone(profile, true);
916 const QString uniqueName = ProfileManager::instance()->generateUniqueName();
917 newProfile->setProperty(Profile::Name, uniqueName);
918 newProfile->setProperty(Profile::UntranslatedName, uniqueName);
919 profile = newProfile;
920 SessionManager::instance()->setSessionProfile(session(), profile);
921 state = EditProfileDialog::NewProfile;
922
923 connect(dialog, &QDialog::accepted, this, [this, profile]() {
924 setEditProfileActionText(profile);
925 });
926 }
927
928 dialog->setProfile(profile, state);
929
930 dialog->show();
931 }
932
renameSession()933 void SessionController::renameSession()
934 {
935 const QString sessionLocalTabTitleFormat = session()->tabTitleFormat(Session::LocalTabTitle);
936 const QString sessionRemoteTabTitleFormat = session()->tabTitleFormat(Session::RemoteTabTitle);
937 const QColor sessionTabColor = session()->color();
938
939 auto *dialog = new RenameTabDialog(QApplication::activeWindow());
940 dialog->setAttribute(Qt::WA_DeleteOnClose);
941 dialog->setModal(true);
942 dialog->setTabTitleText(sessionLocalTabTitleFormat);
943 dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat);
944 dialog->setColor(sessionTabColor);
945
946 if (session()->isRemote()) {
947 dialog->focusRemoteTabTitleText();
948 } else {
949 dialog->focusTabTitleText();
950 }
951
952 connect(dialog, &QDialog::accepted, this, [=]() {
953 const QString tabTitle = dialog->tabTitleText();
954 const QString remoteTabTitle = dialog->remoteTabTitleText();
955 const QColor tabColor = dialog->color();
956
957 if (tabTitle != sessionLocalTabTitleFormat) {
958 session()->setTabTitleFormat(Session::LocalTabTitle, tabTitle);
959 Q_EMIT tabRenamedByUser(true);
960 // trigger an update of the tab text
961 snapshot();
962 }
963
964 if (remoteTabTitle != sessionRemoteTabTitleFormat) {
965 session()->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle);
966 Q_EMIT tabRenamedByUser(true);
967 snapshot();
968 }
969
970 if (tabColor != sessionTabColor) {
971 session()->setColor(tabColor);
972 Q_EMIT tabColoredByUser(true);
973 snapshot();
974 }
975 });
976
977 dialog->show();
978 }
979
980 // This is called upon Menu->Close Sesssion and right-click on tab->Close Tab
confirmClose() const981 bool SessionController::confirmClose() const
982 {
983 if (session()->isForegroundProcessActive()) {
984 QString title = session()->foregroundProcessName();
985
986 // hard coded for now. In future make it possible for the user to specify which programs
987 // are ignored when considering whether to display a confirmation
988 QStringList ignoreList;
989 ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1);
990 if (ignoreList.contains(title)) {
991 return true;
992 }
993
994 QString question;
995 if (title.isEmpty()) {
996 question = i18n(
997 "A program is currently running in this session."
998 " Are you sure you want to close it?");
999 } else {
1000 question = i18n(
1001 "The program '%1' is currently running in this session."
1002 " Are you sure you want to close it?",
1003 title);
1004 }
1005
1006 int result = KMessageBox::warningYesNo(view()->window(),
1007 question,
1008 i18n("Confirm Close"),
1009 KStandardGuiItem::yes(),
1010 KStandardGuiItem::no(),
1011 QStringLiteral("CloseSingleTab"));
1012 return result == KMessageBox::Yes;
1013 }
1014 return true;
1015 }
confirmForceClose() const1016 bool SessionController::confirmForceClose() const
1017 {
1018 if (session()->isRunning()) {
1019 QString title = session()->program();
1020
1021 // hard coded for now. In future make it possible for the user to specify which programs
1022 // are ignored when considering whether to display a confirmation
1023 QStringList ignoreList;
1024 ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1);
1025 if (ignoreList.contains(title)) {
1026 return true;
1027 }
1028
1029 QString question;
1030 if (title.isEmpty()) {
1031 question = i18n(
1032 "A program in this session would not die."
1033 " Are you sure you want to kill it by force?");
1034 } else {
1035 question = i18n(
1036 "The program '%1' is in this session would not die."
1037 " Are you sure you want to kill it by force?",
1038 title);
1039 }
1040
1041 int result = KMessageBox::warningYesNo(view()->window(), question, i18n("Confirm Close"));
1042 return result == KMessageBox::Yes;
1043 }
1044 return true;
1045 }
closeSession()1046 void SessionController::closeSession()
1047 {
1048 if (_preventClose) {
1049 return;
1050 }
1051
1052 if (!confirmClose()) {
1053 return;
1054 }
1055
1056 if (!session()->closeInNormalWay()) {
1057 if (!confirmForceClose()) {
1058 return;
1059 }
1060
1061 if (!session()->closeInForceWay()) {
1062 qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way.";
1063 return;
1064 }
1065 }
1066
1067 if (factory() != nullptr) {
1068 factory()->removeClient(this);
1069 }
1070 }
1071
1072 // Trying to open a remote Url may produce unexpected results.
1073 // Therefore, if a remote url, open the user's home path.
1074 // TODO consider: 1) disable menu upon remote session
1075 // 2) transform url to get the desired result (ssh -> sftp, etc)
openBrowser()1076 void SessionController::openBrowser()
1077 {
1078 const QUrl currentUrl = url().isLocalFile() ? url() : QUrl::fromLocalFile(QDir::homePath());
1079
1080 auto *job = new KIO::OpenUrlJob(currentUrl);
1081 job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
1082 job->start();
1083 }
1084
copy()1085 void SessionController::copy()
1086 {
1087 view()->copyToClipboard();
1088 }
1089
paste()1090 void SessionController::paste()
1091 {
1092 view()->pasteFromClipboard();
1093 }
pasteFromX11Selection()1094 void SessionController::pasteFromX11Selection()
1095 {
1096 view()->pasteFromX11Selection();
1097 }
selectAll()1098 void SessionController::selectAll()
1099 {
1100 view()->selectAll();
1101 }
selectLine()1102 void SessionController::selectLine()
1103 {
1104 view()->selectCurrentLine();
1105 }
findWindow(const QObject * object)1106 static const KXmlGuiWindow *findWindow(const QObject *object)
1107 {
1108 // Walk up the QObject hierarchy to find a KXmlGuiWindow.
1109 while (object != nullptr) {
1110 const auto *window = qobject_cast<const KXmlGuiWindow *>(object);
1111 if (window != nullptr) {
1112 return (window);
1113 }
1114 object = object->parent();
1115 }
1116 return (nullptr);
1117 }
1118
hasTerminalDisplayInSameWindow(const Session * session,const KXmlGuiWindow * window)1119 static bool hasTerminalDisplayInSameWindow(const Session *session, const KXmlGuiWindow *window)
1120 {
1121 // Iterate all TerminalDisplays of this Session ...
1122 const QList<TerminalDisplay *> views = session->views();
1123 for (const TerminalDisplay *terminalDisplay : views) {
1124 // ... and check whether a TerminalDisplay has the same
1125 // window as given in the parameter
1126 if (window == findWindow(terminalDisplay)) {
1127 return (true);
1128 }
1129 }
1130 return (false);
1131 }
1132
copyInputActionsTriggered(QAction * action)1133 void SessionController::copyInputActionsTriggered(QAction *action)
1134 {
1135 const auto mode = action->data().toInt();
1136
1137 switch (mode) {
1138 case CopyInputToAllTabsMode:
1139 copyInputToAllTabs();
1140 break;
1141 case CopyInputToSelectedTabsMode:
1142 copyInputToSelectedTabs();
1143 break;
1144 case CopyInputToNoneMode:
1145 copyInputToNone();
1146 break;
1147 default:
1148 Q_ASSERT(false);
1149 }
1150 }
1151
copyInputToAllTabs()1152 void SessionController::copyInputToAllTabs()
1153 {
1154 if (_copyToGroup == nullptr) {
1155 _copyToGroup = new SessionGroup(this);
1156 }
1157
1158 // Find our window ...
1159 const KXmlGuiWindow *myWindow = findWindow(view());
1160
1161 const QList<Session *> sessionsList = SessionManager::instance()->sessions();
1162 QSet<Session *> group(sessionsList.begin(), sessionsList.end());
1163 for (auto session : group) {
1164 // First, ensure that the session is removed
1165 // (necessary to avoid duplicates on addSession()!)
1166 _copyToGroup->removeSession(session);
1167
1168 // Add current session if it is displayed our window
1169 if (hasTerminalDisplayInSameWindow(session, myWindow)) {
1170 _copyToGroup->addSession(session);
1171 }
1172 }
1173 _copyToGroup->setMasterStatus(session(), true);
1174 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1175
1176 snapshot();
1177 Q_EMIT copyInputChanged(this);
1178 }
1179
copyInputToSelectedTabs()1180 void SessionController::copyInputToSelectedTabs()
1181 {
1182 if (_copyToGroup == nullptr) {
1183 _copyToGroup = new SessionGroup(this);
1184 _copyToGroup->addSession(session());
1185 _copyToGroup->setMasterStatus(session(), true);
1186 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1187 }
1188
1189 auto *dialog = new CopyInputDialog(view());
1190 dialog->setAttribute(Qt::WA_DeleteOnClose);
1191 dialog->setModal(true);
1192 dialog->setMasterSession(session());
1193
1194 const QList<Session *> sessionsList = _copyToGroup->sessions();
1195 QSet<Session *> currentGroup(sessionsList.begin(), sessionsList.end());
1196
1197 currentGroup.remove(session());
1198
1199 dialog->setChosenSessions(currentGroup);
1200
1201 connect(dialog, &QDialog::accepted, this, [=]() {
1202 QSet<Session *> newGroup = dialog->chosenSessions();
1203 newGroup.remove(session());
1204
1205 const QSet<Session *> completeGroup = newGroup | currentGroup;
1206 for (Session *session : completeGroup) {
1207 if (newGroup.contains(session) && !currentGroup.contains(session)) {
1208 _copyToGroup->addSession(session);
1209 } else if (!newGroup.contains(session) && currentGroup.contains(session)) {
1210 _copyToGroup->removeSession(session);
1211 }
1212 }
1213
1214 _copyToGroup->setMasterStatus(session(), true);
1215 _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
1216 snapshot();
1217 Q_EMIT copyInputChanged(this);
1218 });
1219
1220 dialog->show();
1221 }
1222
copyInputToNone()1223 void SessionController::copyInputToNone()
1224 {
1225 if (_copyToGroup == nullptr) { // No 'Copy To' is active
1226 return;
1227 }
1228
1229 // Once Qt5.14+ is the mininum, change to use range constructors
1230 const QList<Session *> groupList = SessionManager::instance()->sessions();
1231 QSet<Session *> group(groupList.begin(), groupList.end());
1232
1233 for (auto iterator : group) {
1234 Session *s = iterator;
1235
1236 if (s != session()) {
1237 _copyToGroup->removeSession(iterator);
1238 }
1239 }
1240 delete _copyToGroup;
1241 _copyToGroup = nullptr;
1242 snapshot();
1243 Q_EMIT copyInputChanged(this);
1244 }
1245
searchClosed()1246 void SessionController::searchClosed()
1247 {
1248 _isSearchBarEnabled = false;
1249 searchHistory(false);
1250 }
1251
updateFilterList(const Profile::Ptr & profile)1252 void SessionController::updateFilterList(const Profile::Ptr &profile)
1253 {
1254 if (profile != SessionManager::instance()->sessionProfile(session())) {
1255 return;
1256 }
1257
1258 auto *filterChain = view()->filterChain();
1259
1260 const QString currentWordCharacters = profile->wordCharacters();
1261 static QString _wordChars = currentWordCharacters;
1262
1263 if (profile->underlineFilesEnabled()) {
1264 if (_fileFilter == nullptr) { // Initialize
1265 _fileFilter = new FileFilter(session(), currentWordCharacters);
1266 filterChain->addFilter(_fileFilter);
1267 } else {
1268 // If wordCharacters changed, we need to change the static regex
1269 // pattern in _fileFilter
1270 if (_wordChars != currentWordCharacters) {
1271 _wordChars = currentWordCharacters;
1272 _fileFilter->updateRegex(currentWordCharacters);
1273 }
1274 }
1275 } else if (_fileFilter != nullptr) { // It became disabled, clean up
1276 filterChain->removeFilter(_fileFilter);
1277 delete _fileFilter;
1278 _fileFilter = nullptr;
1279 }
1280
1281 if (profile->underlineLinksEnabled()) {
1282 if (_urlFilter == nullptr) { // Initialize
1283 _urlFilter = new UrlFilter();
1284 filterChain->addFilter(_urlFilter);
1285 }
1286 } else if (_urlFilter != nullptr) { // It became disabled, clean up
1287 filterChain->removeFilter(_urlFilter);
1288 delete _urlFilter;
1289 _urlFilter = nullptr;
1290 }
1291
1292 if (profile->allowEscapedLinks()) {
1293 if (_escapedUrlFilter == nullptr) { // Initialize
1294 _escapedUrlFilter = new EscapeSequenceUrlFilter(session(), view());
1295 filterChain->addFilter(_escapedUrlFilter);
1296 }
1297 } else if (_escapedUrlFilter != nullptr) { // It became disabled, clean up
1298 filterChain->removeFilter(_escapedUrlFilter);
1299 delete _escapedUrlFilter;
1300 _escapedUrlFilter = nullptr;
1301 }
1302
1303 const bool allowColorFilters = profile->colorFilterEnabled();
1304 if (!allowColorFilters && (_colorFilter != nullptr)) {
1305 filterChain->removeFilter(_colorFilter);
1306 delete _colorFilter;
1307 _colorFilter = nullptr;
1308 } else if (allowColorFilters && (_colorFilter == nullptr)) {
1309 _colorFilter = new ColorFilter();
1310 filterChain->addFilter(_colorFilter);
1311 }
1312 }
1313
setSearchStartToWindowCurrentLine()1314 void SessionController::setSearchStartToWindowCurrentLine()
1315 {
1316 setSearchStartTo(-1);
1317 }
1318
setSearchStartTo(int line)1319 void SessionController::setSearchStartTo(int line)
1320 {
1321 _searchStartLine = line;
1322 _prevSearchResultLine = line;
1323 }
1324
listenForScreenWindowUpdates()1325 void SessionController::listenForScreenWindowUpdates()
1326 {
1327 if (_listenForScreenWindowUpdates) {
1328 return;
1329 }
1330
1331 connect(view()->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter);
1332 connect(view()->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter);
1333 connect(view()->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, view(), QOverload<>::of(&Konsole::TerminalDisplay::update));
1334
1335 _listenForScreenWindowUpdates = true;
1336 }
1337
updateSearchFilter()1338 void SessionController::updateSearchFilter()
1339 {
1340 if ((_searchFilter != nullptr) && (!_searchBar.isNull())) {
1341 view()->processFilters();
1342 }
1343 }
1344
searchBarEvent()1345 void SessionController::searchBarEvent()
1346 {
1347 QString selectedText = view()->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace);
1348 if (!selectedText.isEmpty()) {
1349 _searchBar->setSearchText(selectedText);
1350 }
1351
1352 if (_searchBar->isVisible()) {
1353 _searchBar->focusLineEdit();
1354 } else {
1355 searchHistory(true);
1356 _isSearchBarEnabled = true;
1357 }
1358 }
1359
enableSearchBar(bool showSearchBar)1360 void SessionController::enableSearchBar(bool showSearchBar)
1361 {
1362 if (_searchBar.isNull()) {
1363 return;
1364 }
1365
1366 if (showSearchBar && !_searchBar->isVisible()) {
1367 setSearchStartToWindowCurrentLine();
1368 }
1369
1370 _searchBar->setVisible(showSearchBar);
1371 if (showSearchBar) {
1372 connect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged);
1373 connect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory);
1374 connect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory);
1375 } else {
1376 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged);
1377 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory);
1378 disconnect(_searchBar, &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory);
1379 if ((!view().isNull()) && (view()->screenWindow() != nullptr)) {
1380 view()->screenWindow()->setCurrentResultLine(-1);
1381 }
1382 }
1383 }
1384
reverseSearchChecked() const1385 bool SessionController::reverseSearchChecked() const
1386 {
1387 Q_ASSERT(_searchBar);
1388
1389 QBitArray options = _searchBar->optionsChecked();
1390 return options.at(IncrementalSearchBar::ReverseSearch);
1391 }
1392
regexpFromSearchBarOptions() const1393 QRegularExpression SessionController::regexpFromSearchBarOptions() const
1394 {
1395 QBitArray options = _searchBar->optionsChecked();
1396
1397 QString text(_searchBar->searchText());
1398
1399 QRegularExpression regExp;
1400 if (options.at(IncrementalSearchBar::RegExp)) {
1401 regExp.setPattern(text);
1402 } else {
1403 regExp.setPattern(QRegularExpression::escape(text));
1404 }
1405
1406 if (!options.at(IncrementalSearchBar::MatchCase)) {
1407 regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
1408 }
1409
1410 return regExp;
1411 }
1412
1413 // searchHistory() may be called either as a result of clicking a menu item or
1414 // as a result of changing the search bar widget
searchHistory(bool showSearchBar)1415 void SessionController::searchHistory(bool showSearchBar)
1416 {
1417 enableSearchBar(showSearchBar);
1418
1419 if (!_searchBar.isNull()) {
1420 if (showSearchBar) {
1421 removeSearchFilter();
1422
1423 listenForScreenWindowUpdates();
1424
1425 _searchFilter = new RegExpFilter();
1426 _searchFilter->setRegExp(regexpFromSearchBarOptions());
1427 view()->filterChain()->addFilter(_searchFilter);
1428 view()->processFilters();
1429
1430 setFindNextPrevEnabled(true);
1431 } else {
1432 setFindNextPrevEnabled(false);
1433
1434 removeSearchFilter();
1435
1436 view()->setFocus(Qt::ActiveWindowFocusReason);
1437 }
1438 }
1439 }
1440
setFindNextPrevEnabled(bool enabled)1441 void SessionController::setFindNextPrevEnabled(bool enabled)
1442 {
1443 _findNextAction->setEnabled(enabled);
1444 _findPreviousAction->setEnabled(enabled);
1445 }
searchTextChanged(const QString & text)1446 void SessionController::searchTextChanged(const QString &text)
1447 {
1448 Q_ASSERT(view()->screenWindow());
1449
1450 if (_searchText == text) {
1451 return;
1452 }
1453
1454 _searchText = text;
1455
1456 if (text.isEmpty()) {
1457 view()->screenWindow()->clearSelection();
1458 view()->screenWindow()->scrollTo(_searchStartLine);
1459 }
1460
1461 // update search. this is called even when the text is
1462 // empty to clear the view's filters
1463 beginSearch(text, reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1464 }
searchCompleted(bool success)1465 void SessionController::searchCompleted(bool success)
1466 {
1467 _prevSearchResultLine = view()->screenWindow()->currentResultLine();
1468
1469 if (!_searchBar.isNull()) {
1470 _searchBar->setFoundMatch(success);
1471 }
1472 }
1473
beginSearch(const QString & text,Enum::SearchDirection direction)1474 void SessionController::beginSearch(const QString &text, Enum::SearchDirection direction)
1475 {
1476 Q_ASSERT(_searchBar);
1477 Q_ASSERT(_searchFilter);
1478
1479 QRegularExpression regExp = regexpFromSearchBarOptions();
1480 _searchFilter->setRegExp(regExp);
1481
1482 if (_searchStartLine < 0 || _searchStartLine > view()->screenWindow()->lineCount()) {
1483 if (direction == Enum::ForwardsSearch) {
1484 setSearchStartTo(view()->screenWindow()->currentLine());
1485 } else {
1486 setSearchStartTo(view()->screenWindow()->currentLine() + view()->screenWindow()->windowLines());
1487 }
1488 }
1489
1490 if (!regExp.pattern().isEmpty()) {
1491 view()->screenWindow()->setCurrentResultLine(-1);
1492 auto task = new SearchHistoryTask(this);
1493
1494 connect(task, &Konsole::SearchHistoryTask::completed, this, &Konsole::SessionController::searchCompleted);
1495
1496 task->setRegExp(regExp);
1497 task->setSearchDirection(direction);
1498 task->setAutoDelete(true);
1499 task->setStartLine(_searchStartLine);
1500 task->addScreenWindow(session(), view()->screenWindow());
1501 task->execute();
1502 } else if (text.isEmpty()) {
1503 searchCompleted(false);
1504 }
1505
1506 view()->processFilters();
1507 }
highlightMatches(bool highlight)1508 void SessionController::highlightMatches(bool highlight)
1509 {
1510 if (highlight) {
1511 view()->filterChain()->addFilter(_searchFilter);
1512 view()->processFilters();
1513 } else {
1514 view()->filterChain()->removeFilter(_searchFilter);
1515 }
1516
1517 view()->update();
1518 }
1519
searchFrom()1520 void SessionController::searchFrom()
1521 {
1522 Q_ASSERT(_searchBar);
1523 Q_ASSERT(_searchFilter);
1524
1525 if (reverseSearchChecked()) {
1526 setSearchStartTo(view()->screenWindow()->lineCount());
1527 } else {
1528 setSearchStartTo(0);
1529 }
1530
1531 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1532 }
findNextInHistory()1533 void SessionController::findNextInHistory()
1534 {
1535 Q_ASSERT(_searchBar);
1536 Q_ASSERT(_searchFilter);
1537
1538 setSearchStartTo(_prevSearchResultLine);
1539
1540 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1541 }
findPreviousInHistory()1542 void SessionController::findPreviousInHistory()
1543 {
1544 Q_ASSERT(_searchBar);
1545 Q_ASSERT(_searchFilter);
1546
1547 setSearchStartTo(_prevSearchResultLine);
1548
1549 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::ForwardsSearch : Enum::BackwardsSearch);
1550 }
updateMenuIconsAccordingToReverseSearchSetting()1551 void SessionController::updateMenuIconsAccordingToReverseSearchSetting()
1552 {
1553 if (reverseSearchChecked()) {
1554 _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1555 _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1556 } else {
1557 _findNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
1558 _findPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up")));
1559 }
1560 }
changeSearchMatch()1561 void SessionController::changeSearchMatch()
1562 {
1563 Q_ASSERT(_searchBar);
1564 Q_ASSERT(_searchFilter);
1565
1566 // reset Selection for new case match
1567 view()->screenWindow()->clearSelection();
1568 beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch);
1569 }
showHistoryOptions()1570 void SessionController::showHistoryOptions()
1571 {
1572 auto *dialog = new HistorySizeDialog(QApplication::activeWindow());
1573 dialog->setAttribute(Qt::WA_DeleteOnClose);
1574 dialog->setModal(true);
1575
1576 const HistoryType ¤tHistory = session()->historyType();
1577 if (currentHistory.isEnabled()) {
1578 if (currentHistory.isUnlimited()) {
1579 dialog->setMode(Enum::UnlimitedHistory);
1580 } else {
1581 dialog->setMode(Enum::FixedSizeHistory);
1582 dialog->setLineCount(currentHistory.maximumLineCount());
1583 }
1584 } else {
1585 dialog->setMode(Enum::NoHistory);
1586 }
1587
1588 connect(dialog, &QDialog::accepted, this, [this, dialog]() {
1589 scrollBackOptionsChanged(dialog->mode(), dialog->lineCount());
1590 });
1591
1592 dialog->show();
1593 }
sessionResizeRequest(const QSize & size)1594 void SessionController::sessionResizeRequest(const QSize &size)
1595 {
1596 ////qDebug() << "View resize requested to " << size;
1597 view()->setSize(size.width(), size.height());
1598 }
scrollBackOptionsChanged(int mode,int lines)1599 void SessionController::scrollBackOptionsChanged(int mode, int lines)
1600 {
1601 switch (mode) {
1602 case Enum::NoHistory:
1603 session()->setHistoryType(HistoryTypeNone());
1604 break;
1605 case Enum::FixedSizeHistory:
1606 session()->setHistoryType(CompactHistoryType(lines));
1607 break;
1608 case Enum::UnlimitedHistory:
1609 session()->setHistoryType(HistoryTypeFile());
1610 break;
1611 }
1612 }
1613
saveHistory()1614 void SessionController::saveHistory()
1615 {
1616 SessionTask *task = new SaveHistoryTask(this);
1617 task->setAutoDelete(true);
1618 task->addSession(session());
1619 task->execute();
1620 }
1621
clearHistory()1622 void SessionController::clearHistory()
1623 {
1624 session()->clearHistory();
1625 view()->updateImage(); // To reset view scrollbar
1626 view()->repaint();
1627 }
1628
clearHistoryAndReset()1629 void SessionController::clearHistoryAndReset()
1630 {
1631 Profile::Ptr profile = SessionManager::instance()->sessionProfile(session());
1632 QByteArray name = profile->defaultEncoding().toUtf8();
1633
1634 Emulation *emulation = session()->emulation();
1635 emulation->reset();
1636 session()->refresh();
1637 session()->setCodec(QTextCodec::codecForName(name));
1638 clearHistory();
1639 }
1640
increaseFontSize()1641 void SessionController::increaseFontSize()
1642 {
1643 view()->terminalFont()->increaseFontSize();
1644 }
1645
decreaseFontSize()1646 void SessionController::decreaseFontSize()
1647 {
1648 view()->terminalFont()->decreaseFontSize();
1649 }
1650
resetFontSize()1651 void SessionController::resetFontSize()
1652 {
1653 view()->terminalFont()->resetFontSize();
1654 }
1655
monitorActivity(bool monitor)1656 void SessionController::monitorActivity(bool monitor)
1657 {
1658 session()->setMonitorActivity(monitor);
1659 }
monitorSilence(bool monitor)1660 void SessionController::monitorSilence(bool monitor)
1661 {
1662 session()->setMonitorSilence(monitor);
1663 }
monitorProcessFinish(bool monitor)1664 void SessionController::monitorProcessFinish(bool monitor)
1665 {
1666 _monitorProcessFinish = monitor;
1667 }
updateSessionIcon()1668 void SessionController::updateSessionIcon()
1669 {
1670 // If the default profile icon is being used, don't put it on the tab
1671 // Only show the icon if the user specifically chose one
1672 if (session()->iconName() == QStringLiteral("utilities-terminal")) {
1673 _sessionIconName = QString();
1674 } else {
1675 _sessionIconName = session()->iconName();
1676 }
1677 _sessionIcon = QIcon::fromTheme(_sessionIconName);
1678
1679 setIcon(_sessionIcon);
1680 }
1681
updateReadOnlyActionStates()1682 void SessionController::updateReadOnlyActionStates()
1683 {
1684 bool readonly = isReadOnly();
1685 QAction *readonlyAction = actionCollection()->action(QStringLiteral("view-readonly"));
1686 Q_ASSERT(readonlyAction != nullptr);
1687 readonlyAction->setIcon(QIcon::fromTheme(readonly ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked")));
1688 readonlyAction->setChecked(readonly);
1689
1690 auto updateActionState = [this, readonly](const QString &name) {
1691 QAction *action = actionCollection()->action(name);
1692 if (action != nullptr) {
1693 action->setVisible(!readonly);
1694 }
1695 };
1696
1697 updateActionState(QStringLiteral("edit_paste"));
1698 updateActionState(QStringLiteral("clear-history"));
1699 updateActionState(QStringLiteral("clear-history-and-reset"));
1700 updateActionState(QStringLiteral("edit-current-profile"));
1701 updateActionState(QStringLiteral("switch-profile"));
1702 updateActionState(QStringLiteral("adjust-history"));
1703 updateActionState(QStringLiteral("send-signal"));
1704 updateActionState(QStringLiteral("zmodem-upload"));
1705
1706 _codecAction->setEnabled(!readonly);
1707
1708 // Without the timer, when detaching a tab while the message widget is visible,
1709 // the size of the terminal becomes really small...
1710 QTimer::singleShot(0, this, [this, readonly]() {
1711 view()->updateReadOnlyState(readonly);
1712 });
1713 }
1714
isReadOnly() const1715 bool SessionController::isReadOnly() const
1716 {
1717 if (!session().isNull()) {
1718 return session()->isReadOnly();
1719 } else {
1720 return false;
1721 }
1722 }
1723
isCopyInputActive() const1724 bool SessionController::isCopyInputActive() const
1725 {
1726 return ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1);
1727 }
1728
sessionAttributeChanged()1729 void SessionController::sessionAttributeChanged()
1730 {
1731 if (_sessionIconName != session()->iconName()) {
1732 updateSessionIcon();
1733 }
1734
1735 QString title = session()->title(Session::DisplayedTitleRole);
1736
1737 // special handling for the "%w" marker which is replaced with the
1738 // window title set by the shell
1739 title.replace(QLatin1String("%w"), session()->userTitle());
1740 // special handling for the "%#" marker which is replaced with the
1741 // number of the shell
1742 title.replace(QLatin1String("%#"), QString::number(session()->sessionId()));
1743
1744 if (title.isEmpty()) {
1745 title = session()->title(Session::NameRole);
1746 }
1747
1748 setTitle(title);
1749 setColor(session()->color());
1750 Q_EMIT rawTitleChanged();
1751 }
1752
sessionReadOnlyChanged()1753 void SessionController::sessionReadOnlyChanged()
1754 {
1755 updateReadOnlyActionStates();
1756
1757 // Update all views
1758 const QList<TerminalDisplay *> viewsList = session()->views();
1759 for (TerminalDisplay *terminalDisplay : viewsList) {
1760 if (terminalDisplay != view()) {
1761 terminalDisplay->updateReadOnlyState(isReadOnly());
1762 }
1763 Q_EMIT readOnlyChanged(this);
1764 }
1765 }
1766
showDisplayContextMenu(const QPoint & position)1767 void SessionController::showDisplayContextMenu(const QPoint &position)
1768 {
1769 // needed to make sure the popup menu is available, even if a hosting
1770 // application did not merge our GUI.
1771 if (factory() == nullptr) {
1772 if (clientBuilder() == nullptr) {
1773 // Client builder does not get deleted automatically, we handle this
1774 _clientBuilder.reset(new KXMLGUIBuilder(view()));
1775 setClientBuilder(_clientBuilder.get());
1776 }
1777
1778 auto factory = new KXMLGUIFactory(clientBuilder(), view());
1779 factory->addClient(this);
1780 }
1781
1782 QPointer<QMenu> popup = qobject_cast<QMenu *>(factory()->container(QStringLiteral("session-popup-menu"), this));
1783 if (!popup.isNull()) {
1784 updateReadOnlyActionStates();
1785
1786 auto contentSeparator = new QAction(popup);
1787 contentSeparator->setSeparator(true);
1788
1789 // We don't actually use this shortcut, but we need to display it for consistency :/
1790 QAction *copy = actionCollection()->action(QStringLiteral("edit_copy_contextmenu"));
1791 #ifdef Q_OS_MACOS
1792 copy->setShortcut(Qt::META | Qt::Key_C);
1793 #else
1794 copy->setShortcut(Konsole::ACCEL | Qt::SHIFT | Qt::Key_C);
1795 #endif
1796
1797 QList<QAction *> toRemove;
1798 // prepend content-specific actions such as "Open Link", "Copy Email Address" etc
1799 QSharedPointer<HotSpot> hotSpot = view()->filterActions(position);
1800 if (hotSpot != nullptr) {
1801 popup->insertActions(popup->actions().value(0, nullptr), hotSpot->actions() << contentSeparator);
1802 popup->addAction(contentSeparator);
1803 toRemove = hotSpot->setupMenu(popup.data());
1804 }
1805
1806 // always update this submenu before showing the context menu,
1807 // because the available search services might have changed
1808 // since the context menu is shown last time
1809 updateWebSearchMenu();
1810
1811 _preventClose = true;
1812
1813 if (_showMenuAction != nullptr) {
1814 if (_showMenuAction->isChecked()) {
1815 popup->removeAction(_showMenuAction);
1816 } else {
1817 popup->insertAction(_switchProfileMenu, _showMenuAction);
1818 }
1819 }
1820
1821 // they are here.
1822 // qDebug() << popup->actions().indexOf(contentActions[0]) << popup->actions().indexOf(contentActions[1]) << popup->actions()[3];
1823 QAction *chosen = popup->exec(QCursor::pos());
1824
1825 // check for validity of the pointer to the popup menu
1826 if (!popup.isNull()) {
1827 delete contentSeparator;
1828 // Remove the 'Open with' actions from it.
1829 for (auto *act : toRemove) {
1830 popup->removeAction(act);
1831 }
1832
1833 // Remove the Accelerator for the copy shortcut so we don't have two actions with same shortcut.
1834 copy->setShortcut({});
1835 }
1836
1837 // This should be at the end, to prevent crashes if the session
1838 // is closed from the menu in e.g. konsole kpart
1839 _preventClose = false;
1840 if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) {
1841 chosen->trigger();
1842 }
1843 } else {
1844 qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << session()->title(Session::NameRole)
1845 << ", no GUI factory available to build the popup.";
1846 }
1847 }
1848
movementKeyFromSearchBarReceived(QKeyEvent * event)1849 void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event)
1850 {
1851 QCoreApplication::sendEvent(view(), event);
1852 setSearchStartToWindowCurrentLine();
1853 }
1854
sessionNotificationsChanged(Session::Notification notification,bool enabled)1855 void SessionController::sessionNotificationsChanged(Session::Notification notification, bool enabled)
1856 {
1857 Q_EMIT notificationChanged(this, notification, enabled);
1858 }
1859
zmodemDownload()1860 void SessionController::zmodemDownload()
1861 {
1862 QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz"));
1863 if (zmodem.isEmpty()) {
1864 zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz"));
1865 }
1866 if (!zmodem.isEmpty()) {
1867 const QString path = QFileDialog::getExistingDirectory(view(),
1868 i18n("Save ZModem Download to..."),
1869 QDir::homePath(),
1870 QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
1871
1872 if (!path.isEmpty()) {
1873 session()->startZModem(zmodem, path, QStringList());
1874 return;
1875 }
1876 } else {
1877 KMessageBox::error(view(),
1878 i18n("<p>A ZModem file transfer attempt has been detected, "
1879 "but no suitable ZModem software was found on this system.</p>"
1880 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
1881 }
1882 session()->cancelZModem();
1883 }
1884
zmodemUpload()1885 void SessionController::zmodemUpload()
1886 {
1887 if (session()->isZModemBusy()) {
1888 KMessageBox::sorry(view(), i18n("<p>The current session already has a ZModem file transfer in progress.</p>"));
1889 return;
1890 }
1891
1892 QString zmodem = QStandardPaths::findExecutable(QStringLiteral("sz"));
1893 if (zmodem.isEmpty()) {
1894 zmodem = QStandardPaths::findExecutable(QStringLiteral("lsz"));
1895 }
1896 if (zmodem.isEmpty()) {
1897 KMessageBox::sorry(view(),
1898 i18n("<p>No suitable ZModem software was found on this system.</p>"
1899 "<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>"));
1900 return;
1901 }
1902
1903 QStringList files = QFileDialog::getOpenFileNames(view(), i18n("Select Files for ZModem Upload"), QDir::homePath());
1904 if (!files.isEmpty()) {
1905 session()->startZModem(zmodem, QString(), files);
1906 }
1907 }
1908
isKonsolePart() const1909 bool SessionController::isKonsolePart() const
1910 {
1911 // Check to see if we are being called from Konsole or a KPart
1912 return !(qApp->applicationName() == QLatin1String("konsole"));
1913 }
1914
userTitle() const1915 QString SessionController::userTitle() const
1916 {
1917 if (!session().isNull()) {
1918 return session()->userTitle();
1919 } else {
1920 return QString();
1921 }
1922 }
1923
isValid() const1924 bool SessionController::isValid() const
1925 {
1926 return _sessionDisplayConnection->isValid();
1927 }
1928