SessionController.cpp 72.8 KB
Newer Older
1
/*
2 3
    SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
    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 "profile/ProfileManager.h"
12
#include "konsoledebug.h"
13

14
// Qt
15
#include <QApplication>
16
#include <QAction>
patrick pereira's avatar
patrick pereira committed
17
#include <QList>
18
#include <QMenu>
19
#include <QKeyEvent>
20
#include <QFileDialog>
21
#include <QPainter>
22
#include <QStandardPaths>
23 24
#include <QUrl>
#include <QIcon>
25

26
// KDE
27 28
#include <KActionMenu>
#include <KActionCollection>
29
#include <KLocalizedString>
30
#include <KMessageBox>
31
#include <KShell>
Robert Knight's avatar
 
Robert Knight committed
32
#include <KToggleAction>
33
#include <KSelectAction>
34
#include <KXmlGuiWindow>
35
#include <KXMLGUIFactory>
36
#include <KXMLGUIBuilder>
Jekyll Wu's avatar
Jekyll Wu committed
37 38
#include <KUriFilter>
#include <KStringHandler>
Alex Richardson's avatar
Alex Richardson committed
39
#include <KSharedConfig>
40
#include <KConfigGroup>
41
#include <KCodecAction>
42
#include <KNotification>
43

44
#include <kio_version.h>
Kurt Hindenburg's avatar
Kurt Hindenburg committed
45 46 47 48 49 50
#if KIO_VERSION > QT_VERSION_CHECK(5, 68, 0)
#include <KIO/CommandLauncherJob>
#else
#include <KToolInvocation>
#endif

51 52 53 54 55 56 57
#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
#include <KRun>
#else
#include <KIO/JobUiDelegate>
#include <KIO/OpenUrlJob>
#endif

58
// Konsole
59
#include "CopyInputDialog.h"
60
#include "Emulation.h"
61 62 63 64 65
#include "HistorySizeDialog.h"
#include "RenameTabDialog.h"
#include "SaveHistoryTask.h"
#include "ScreenWindow.h"
#include "SearchHistoryTask.h"
66 67

#include "filterHotSpots/EscapeSequenceUrlFilter.h"
68 69 70 71 72 73
#include "filterHotSpots/FileFilter.h"
#include "filterHotSpots/Filter.h"
#include "filterHotSpots/FilterChain.h"
#include "filterHotSpots/HotSpot.h"
#include "filterHotSpots/RegExpFilter.h"
#include "filterHotSpots/UrlFilter.h"
74

75 76
#include "history/HistoryType.h"
#include "history/HistoryTypeFile.h"
77
#include "history/HistoryTypeNone.h"
78
#include "history/compact/CompactHistoryType.h"
79

80
#include "profile/ProfileList.h"
81

82 83
#include "SessionGroup.h"
#include "SessionManager.h"
84

85
#include "widgets/EditProfileDialog.h"
86
#include "widgets/IncrementalSearchBar.h"
87 88

#include "terminalDisplay/TerminalDisplay.h"
89

90
// For Unix signal names
Kurt Hindenburg's avatar
Kurt Hindenburg committed
91
#include <csignal>
92

93
using namespace Konsole;
94

95
QSet<SessionController*> SessionController::_allControllers;
96
int SessionController::_lastControllerId;
Robert Knight's avatar
 
Robert Knight committed
97

98
SessionController::SessionController(Session* session, TerminalDisplay* view, QObject* parent)
Robert Knight's avatar
 
Robert Knight committed
99
    : ViewProperties(parent)
100
    , KXMLGUIClient()
Kurt Hindenburg's avatar
Kurt Hindenburg committed
101
    , _copyToGroup(nullptr)
102
    , _profileList(nullptr)
103 104
    , _sessionIcon(QIcon())
    , _sessionIconName(QString())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
105 106 107 108 109 110 111
    , _searchFilter(nullptr)
    , _urlFilter(nullptr)
    , _fileFilter(nullptr)
    , _copyInputToAllTabsAction(nullptr)
    , _findAction(nullptr)
    , _findNextAction(nullptr)
    , _findPreviousAction(nullptr)
112
    , _interactionTimer(nullptr)
Harald Hvaal's avatar
Harald Hvaal committed
113 114
    , _searchStartLine(0)
    , _prevSearchResultLine(0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
115 116 117
    , _codecAction(nullptr)
    , _switchProfileMenu(nullptr)
    , _webSearchMenu(nullptr)
118 119
    , _listenForScreenWindowUpdates(false)
    , _preventClose(false)
120
    , _selectedText(QString())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
121
    , _showMenuAction(nullptr)
122
    , _bookmarkValidProgramsToClear(QStringList())
123
    , _isSearchBarEnabled(false)
124
    , _editProfileDialog(nullptr)
125
    , _searchBar(view->searchBar())
126
    , _monitorProcessFinish(false)
127
    , _escapedUrlFilter(nullptr)
128
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
129 130
    Q_ASSERT(session);
    Q_ASSERT(view);
131

132 133
    _sessionDisplayConnection = new SessionDisplayConnection(session, view, this);

134
    // handle user interface related to session (menus etc.)
135
    if (isKonsolePart()) {
136
        setComponentName(QStringLiteral("konsole"), i18n("Konsole"));
137
        setXMLFile(QStringLiteral("partui.rc"));
138 139
        setupCommonActions();
    } else {
140
        setXMLFile(QStringLiteral("sessionui.rc"));
141 142 143
        setupCommonActions();
        setupExtraActions();
    }
144

145 146
    connect(this, &SessionController::requestPrint, _sessionDisplayConnection->view(), &TerminalDisplay::printScreen);

147
    actionCollection()->addAssociatedWidget(view);
148 149 150

    const QList<QAction *> actionsList = actionCollection()->actions();
    for (QAction *action : actionsList) {
Jekyll Wu's avatar
Jekyll Wu committed
151 152
        action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
    }
153

154
    setIdentifier(++_lastControllerId);
155
    sessionAttributeChanged();
156

157 158
    connect(_sessionDisplayConnection->view(), &TerminalDisplay::compositeFocusChanged, this, &SessionController::viewFocusChangeHandler);
    _sessionDisplayConnection->view()->setSessionController(this);
Robert Knight's avatar
 
Robert Knight committed
159

160
    // install filter on the view to highlight URLs and files
161
    updateFilterList(SessionManager::instance()->sessionProfile(_sessionDisplayConnection->session()));
162

163 164
    // listen for changes in session, we might need to change the enabled filters
    connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList);
165

166
    // listen for session resize requests
167
    connect(_sessionDisplayConnection->session(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest);
168

169
    // listen for popup menu requests
170
    connect(_sessionDisplayConnection->view(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu);
171

172
    // move view to newest output when keystrokes occur
173
    connect(_sessionDisplayConnection->view(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput);
174

Robert Knight's avatar
 
Robert Knight committed
175
    // listen to activity / silence notifications from session
176
    connect(_sessionDisplayConnection->session(), &Konsole::Session::notificationsChanged, this, &Konsole::SessionController::sessionNotificationsChanged);
177
    // listen to title and icon changes
178 179
    connect(_sessionDisplayConnection->session(), &Konsole::Session::sessionAttributeChanged, this, &Konsole::SessionController::sessionAttributeChanged);
    connect(_sessionDisplayConnection->session(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged);
180

181 182
    connect(this, &Konsole::SessionController::tabRenamedByUser, _sessionDisplayConnection->session(), &Konsole::Session::tabTitleSetByUser);
    connect(this, &Konsole::SessionController::tabColoredByUser, _sessionDisplayConnection->session(), &Konsole::Session::tabColorSetByUser);
183

184
    connect(_sessionDisplayConnection->session() , &Konsole::Session::currentDirectoryChanged , this , &Konsole::SessionController::currentDirectoryChanged);
185

186
    // listen for color changes
187 188
    connect(_sessionDisplayConnection->session(), &Konsole::Session::changeBackgroundColorRequest, _sessionDisplayConnection->view(), &Konsole::TerminalDisplay::setBackgroundColor);
    connect(_sessionDisplayConnection->session(), &Konsole::Session::changeForegroundColorRequest, _sessionDisplayConnection->view(), &Konsole::TerminalDisplay::setForegroundColor);
189

190
    // update the title when the session starts
191
    connect(_sessionDisplayConnection->session(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot);
192

193
    // listen for output changes to set activity flag
194
    connect(_sessionDisplayConnection->session()->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity);
195 196

    // listen for detection of ZModem transfer
197 198
    connect(_sessionDisplayConnection->session(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload);
    connect(_sessionDisplayConnection->session(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload);
199

Robert Knight's avatar
 
Robert Knight committed
200
    // listen for flow control status changes
201 202
    connect(_sessionDisplayConnection->session(), &Konsole::Session::flowControlEnabledChanged, _sessionDisplayConnection->view(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled);
    _sessionDisplayConnection->view()->setFlowControlWarningEnabled(_sessionDisplayConnection->session()->flowControlEnabled());
Robert Knight's avatar
 
Robert Knight committed
203

204 205
    // take a snapshot of the session state every so often when
    // user activity occurs
206 207 208
    //
    // the timer is owned by the session so that it will be destroyed along
    // with the session
209
    _interactionTimer = new QTimer(_sessionDisplayConnection->session());
210
    _interactionTimer->setSingleShot(true);
211
    _interactionTimer->setInterval(500);
212
    connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
213
    connect(_sessionDisplayConnection->view(), &Konsole::TerminalDisplay::compositeFocusChanged,
214
            this, [this](bool focused) { if (focused) { interactionHandler(); }});
215
    connect(_sessionDisplayConnection->view(), &Konsole::TerminalDisplay::keyPressedSignal,
216
            this, &Konsole::SessionController::interactionHandler);
217

218
    // take a snapshot of the session state periodically in the background
219
    auto backgroundTimer = new QTimer(_sessionDisplayConnection->session());
220 221
    backgroundTimer->setSingleShot(false);
    backgroundTimer->setInterval(2000);
222
    connect(backgroundTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot);
223 224
    backgroundTimer->start();

225
    // xterm '10;?' request
226
    connect(_sessionDisplayConnection->session(), &Konsole::Session::getForegroundColor,
227
            this, &Konsole::SessionController::sendForegroundColor);
228
    // xterm '11;?' request
229
    connect(_sessionDisplayConnection->session(), &Konsole::Session::getBackgroundColor,
230 231
            this, &Konsole::SessionController::sendBackgroundColor);

232
    _allControllers.insert(this);
233 234 235

    // A list of programs that accept Ctrl+C to clear command line used
    // before outputting bookmark.
236 237 238 239 240 241 242 243
    _bookmarkValidProgramsToClear = QStringList({
        QStringLiteral("bash"),
        QStringLiteral("fish"),
        QStringLiteral("sh"),
        QStringLiteral("tcsh"),
        QStringLiteral("zsh")
    });

244 245
    setupSearchBar();
    _searchBar->setVisible(_isSearchBarEnabled);
246 247 248
}

SessionController::~SessionController()
249
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
250
    _allControllers.remove(this);
251 252

    if (!_editProfileDialog.isNull()) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
253
        _editProfileDialog->deleteLater();
254
    }
255
    if(factory() != nullptr) {
256 257
        factory()->removeClient(this);
    }
258
}
259
void SessionController::trackOutput(QKeyEvent* event)
260
{
261
    Q_ASSERT(_sessionDisplayConnection->view()->screenWindow());
262

263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    // Qt has broken something, so we can't rely on just checking if certain
    // keys are passed as modifiers anymore.
    const int key = event->key();
    const bool shouldNotTriggerScroll =
        key == Qt::Key_Super_L      ||
        key == Qt::Key_Super_R      ||
        key == Qt::Key_Hyper_L      ||
        key == Qt::Key_Hyper_R      ||
        key == Qt::Key_Shift        ||
        key == Qt::Key_Control      ||
        key == Qt::Key_Meta         ||
        key == Qt::Key_Alt          ||
        key == Qt::Key_AltGr        ||
        key == Qt::Key_CapsLock     ||
        key == Qt::Key_NumLock      ||
        key == Qt::Key_ScrollLock;


281 282
    // Only jump to the bottom if the user actually typed something in,
    // not if the user e. g. just pressed a modifier.
283
    if (event->text().isEmpty() && ((event->modifiers() != 0u) || shouldNotTriggerScroll)) {
284
        return;
285
    }
286

287
    _sessionDisplayConnection->view()->screenWindow()->setTrackOutput(true);
288
}
289 290 291 292 293 294 295 296 297 298 299 300

void SessionController::viewFocusChangeHandler(bool focused)
{
    if (focused) {
        // notify the world that the view associated with this session has been focused
        // used by the view manager to update the title of the MainWindow widget containing the view
        emit viewFocused(this);

        // when the view is focused, set bell events from the associated session to be delivered
        // by the focused view

        // first, disconnect any other views which are listening for bell signals from the session
301
        disconnect(_sessionDisplayConnection->session(), &Konsole::Session::bellRequest, nullptr, nullptr);
302
        // second, connect the newly focused view to listen for the session's bell signal
303
        connect(_sessionDisplayConnection->session(), &Konsole::Session::bellRequest, _sessionDisplayConnection->view(), &Konsole::TerminalDisplay::bell);
304 305 306 307 308 309 310 311 312

        if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) {
            // A session with "Copy To All Tabs" has come into focus:
            // Ensure that newly created sessions are included in _copyToGroup!
            copyInputToAllTabs();
        }
    }
}

313 314 315 316 317
void SessionController::interactionHandler()
{
    _interactionTimer->start();
}

318 319
void SessionController::snapshot()
{
320
    Q_ASSERT(!_sessionDisplayConnection->session().isNull());
321

322
    QString title = _sessionDisplayConnection->session()->getDynamicTitle();
Robert Knight's avatar
 
Robert Knight committed
323
    title         = title.simplified();
324

325
    // Visualize that the session is broadcasting to others
326
    if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) {
327
        title.append(QLatin1Char('*'));
328
    }
Jekyll Wu's avatar
Jekyll Wu committed
329 330 331

    // use the fallback title if needed
    if (title.isEmpty()) {
332
        title = _sessionDisplayConnection->session()->title(Session::NameRole);
Jekyll Wu's avatar
Jekyll Wu committed
333
    }
334

335
    QColor color = _sessionDisplayConnection->session()->color();
336 337 338 339
    // use the fallback color if needed
    if (!color.isValid()) {
        color = QColor(QColor::Invalid);
    }
340

341
    // apply new title
342
    _sessionDisplayConnection->session()->setTitle(Session::DisplayedTitleRole, title);
Jekyll Wu's avatar
Jekyll Wu committed
343

344
    // apply new color
345
    _sessionDisplayConnection->session()->setColor(color);
346

347 348
    // check if foreground process ended and notify if this option was requested
    if (_monitorProcessFinish) {
349
        bool isForegroundProcessActive = _sessionDisplayConnection->session()->isForegroundProcessActive();
350
        if (!_previousForegroundProcessName.isNull() && !isForegroundProcessActive) {
351 352
            KNotification::event(_sessionDisplayConnection->session()->hasFocus() ? QStringLiteral("ProcessFinished") : QStringLiteral("ProcessFinishedHidden"),
                                 i18n("The process '%1' has finished running in session '%2'", _previousForegroundProcessName, _sessionDisplayConnection->session()->nameTitle()),
353 354 355 356
                                 QPixmap(),
                                 QApplication::activeWindow(),
                                 KNotification::CloseWhenWidgetActivated);
        }
357
        _previousForegroundProcessName = isForegroundProcessActive ? _sessionDisplayConnection->session()->foregroundProcessName() : QString();
358 359
    }

Jekyll Wu's avatar
Jekyll Wu committed
360 361
    // do not forget icon
    updateSessionIcon();
362 363
}

364 365
QString SessionController::currentDir() const
{
366
    return _sessionDisplayConnection->session()->currentWorkingDirectory();
367 368
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
369
QUrl SessionController::url() const
370
{
371
    return _sessionDisplayConnection->session()->getUrl();
372 373
}

374 375
void SessionController::rename()
{
376
    renameSession();
377 378
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
379
void SessionController::openUrl(const QUrl& url)
380
{
381
    // Clear shell's command line
382 383 384
    if (!_sessionDisplayConnection->session()->isForegroundProcessActive()
            && _bookmarkValidProgramsToClear.contains(_sessionDisplayConnection->session()->foregroundProcessName())) {
        _sessionDisplayConnection->session()->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C
385 386
    }

387
    // handle local paths
Kurt Hindenburg's avatar
Kurt Hindenburg committed
388
    if (url.isLocalFile()) {
389
        QString path = url.toLocalFile();
390
        _sessionDisplayConnection->session()->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r'));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
391 392
    } else if (url.scheme().isEmpty()) {
        // QUrl couldn't parse what the user entered into the URL field
393
        // so just dump it to the shell
394 395
        // If you change this, change it also in autotests/BookMarkTest.cpp
        QString command = QUrl::fromPercentEncoding(url.toEncoded());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
396
        if (!command.isEmpty()) {
397
            _sessionDisplayConnection->session()->sendTextToTerminal(command, QLatin1Char('\r'));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
398
        }
399 400
    } else if (url.scheme() == QLatin1String("ssh")) {
        QString sshCommand = QStringLiteral("ssh ");
401 402

        if (url.port() > -1) {
403
            sshCommand += QStringLiteral("-p %1 ").arg(url.port());
404
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
405
        if (!url.userName().isEmpty()) {
406
            sshCommand += (url.userName() + QLatin1Char('@'));
407
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
408
        if (!url.host().isEmpty()) {
409 410
            sshCommand += url.host();
        }
411
        _sessionDisplayConnection->session()->sendTextToTerminal(sshCommand, QLatin1Char('\r'));
412

413 414
    } else if (url.scheme() == QLatin1String("telnet")) {
        QString telnetCommand = QStringLiteral("telnet ");
415

Kurt Hindenburg's avatar
Kurt Hindenburg committed
416
        if (!url.userName().isEmpty()) {
417
            telnetCommand += QStringLiteral("-l %1 ").arg(url.userName());
418
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
419
        if (!url.host().isEmpty()) {
420
            telnetCommand += (url.host() + QLatin1Char(' '));
421 422
        }
        if (url.port() > -1) {
423
            telnetCommand += QString::number(url.port());
424 425
        }

426
        _sessionDisplayConnection->session()->sendTextToTerminal(telnetCommand, QLatin1Char('\r'));
427

Kurt Hindenburg's avatar
Kurt Hindenburg committed
428
    } else {
429
        //TODO Implement handling for other Url types
430

431
        KMessageBox::sorry(_sessionDisplayConnection->view()->window(),
432
                           i18n("Konsole does not know how to open the bookmark: ") +
Kurt Hindenburg's avatar
Kurt Hindenburg committed
433
                           url.toDisplayString());
434

435
        qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know"
Kurt Hindenburg's avatar
Kurt Hindenburg committed
436
                   << " how to handle the protocol " << url.scheme();
437 438 439
    }
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
440
void SessionController::setupPrimaryScreenSpecificActions(bool use)
441
{
442
    KActionCollection* collection = actionCollection();
443 444 445 446
    QAction* clearAction = collection->action(QStringLiteral("clear-history"));
    QAction* resetAction = collection->action(QStringLiteral("clear-history-and-reset"));
    QAction* selectAllAction = collection->action(QStringLiteral("select-all"));
    QAction* selectLineAction = collection->action(QStringLiteral("select-line"));
447 448 449 450

    // these actions are meaningful only when primary screen is used.
    clearAction->setEnabled(use);
    resetAction->setEnabled(use);
451
    selectAllAction->setEnabled(use);
452
    selectLineAction->setEnabled(use);
453 454
}

Jekyll Wu's avatar
Jekyll Wu committed
455
void SessionController::selectionChanged(const QString& selectedText)
456
{
Jekyll Wu's avatar
Jekyll Wu committed
457 458 459
    _selectedText = selectedText;
    updateCopyAction(selectedText);
}
460

Jekyll Wu's avatar
Jekyll Wu committed
461 462
void SessionController::updateCopyAction(const QString& selectedText)
{
463
    QAction* copyAction = actionCollection()->action(QStringLiteral("edit_copy"));
464
    QAction* copyContextMenu = actionCollection()->action(QStringLiteral("edit_copy_contextmenu"));
Jekyll Wu's avatar
Jekyll Wu committed
465 466
    // copy action is meaningful only when some text is selected.
    copyAction->setEnabled(!selectedText.isEmpty());
467
    copyContextMenu->setVisible(!selectedText.isEmpty());
Jekyll Wu's avatar
Jekyll Wu committed
468 469 470 471 472 473 474 475
}

void SessionController::updateWebSearchMenu()
{
    // reset
    _webSearchMenu->setVisible(false);
    _webSearchMenu->menu()->clear();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
476
    if (_selectedText.isEmpty()) {
477
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
478
    }
Jekyll Wu's avatar
Jekyll Wu committed
479 480

    QString searchText = _selectedText;
481
    searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
Jekyll Wu's avatar
Jekyll Wu committed
482

Kurt Hindenburg's avatar
Kurt Hindenburg committed
483
    if (searchText.isEmpty()) {
Jekyll Wu's avatar
Jekyll Wu committed
484
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
485
    }
Jekyll Wu's avatar
Jekyll Wu committed
486

487 488 489 490 491 492
    // Is 'Enable Web shortcuts' checked in System Settings?
    KSharedConfigPtr kuriikwsConfig = KSharedConfig::openConfig(QStringLiteral("kuriikwsfilterrc"));
    if (!kuriikwsConfig->group("General").readEntry("EnableWebShortcuts", true)) {
        return;
    }

Jekyll Wu's avatar
Jekyll Wu committed
493 494 495 496 497 498 499 500
    KUriFilterData filterData(searchText);
    filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);

    if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) {
        const QStringList searchProviders = filterData.preferredSearchProviders();
        if (!searchProviders.isEmpty()) {
            _webSearchMenu->setText(i18n("Search for '%1' with",  KStringHandler::rsqueeze(searchText, 16)));

Kurt Hindenburg's avatar
Kurt Hindenburg committed
501
            QAction* action = nullptr;
Jekyll Wu's avatar
Jekyll Wu committed
502

503
            for (const QString &searchProvider : searchProviders) {
504
                action = new QAction(searchProvider, _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
505
                action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider)));
Jekyll Wu's avatar
Jekyll Wu committed
506
                action->setData(filterData.queryForPreferredSearchProvider(searchProvider));
507
                connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction);
Jekyll Wu's avatar
Jekyll Wu committed
508 509 510 511 512
                _webSearchMenu->addAction(action);
            }

            _webSearchMenu->addSeparator();

513
            action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu);
Michal Humpula's avatar
Michal Humpula committed
514
            action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
515
            connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts);
Jekyll Wu's avatar
Jekyll Wu committed
516 517 518 519 520 521 522 523 524
            _webSearchMenu->addAction(action);

            _webSearchMenu->setVisible(true);
        }
    }
}

void SessionController::handleWebShortcutAction()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
525
    auto * action = qobject_cast<QAction*>(sender());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
526
    if (action == nullptr) {
Jekyll Wu's avatar
Jekyll Wu committed
527
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
528
    }
Jekyll Wu's avatar
Jekyll Wu committed
529 530 531

    KUriFilterData filterData(action->data().toString());

532
    if (KUriFilter::self()->filterUri(filterData, { QStringLiteral("kurisearchfilter") })) {
533 534 535
        const QUrl url = filterData.uri();

#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
536
        new KRun(url, QApplication::activeWindow());
537 538 539 540 541
#else
        auto *job = new KIO::OpenUrlJob(url);
        job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
        job->start();
#endif
Jekyll Wu's avatar
Jekyll Wu committed
542 543 544 545 546
    }
}

void SessionController::configureWebShortcuts()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
547
#if KIO_VERSION > QT_VERSION_CHECK(5, 68, 0)
548 549
    auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), { QStringLiteral("webshortcuts") });
    job->start();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
550 551 552
#else
    KToolInvocation::kdeinitExec(QStringLiteral("kcmshell5"), { QStringLiteral("webshortcuts") });
#endif
553 554
}

555 556
void SessionController::sendSignal(QAction* action)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
557
    const auto signal = action->data().toInt();
558
    _sessionDisplayConnection->session()->sendSignal(signal);
559 560
}

561
void SessionController::sendForegroundColor(uint terminator)
562
{
563
    const QColor c = _sessionDisplayConnection->view()->getForegroundColor();
564
    _sessionDisplayConnection->session()->reportForegroundColor(c, terminator);
565 566
}

567
void Konsole::SessionController::sendBackgroundColor(uint terminator)
568
{
569
    const QColor c = _sessionDisplayConnection->view()->getBackgroundColor();
570
    _sessionDisplayConnection->session()->reportBackgroundColor(c, terminator);
571 572
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
573 574
void SessionController::toggleReadOnly()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
575
    auto *action = qobject_cast<QAction*>(sender());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
576 577
    if (action != nullptr) {
        bool readonly = !isReadOnly();
578
        _sessionDisplayConnection->session()->setReadOnly(readonly);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579 580 581
    }
}

582 583
void SessionController::removeSearchFilter()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
584
    if (_searchFilter == nullptr) {
585
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
    }
587

588
    _sessionDisplayConnection->view()->filterChain()->removeFilter(_searchFilter);
589
    delete _searchFilter;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
590
    _searchFilter = nullptr;
591 592
}

593
void SessionController::setupSearchBar()
594
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
595 596 597 598 599 600 601 602
    connect(_searchBar, &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived);
    connect(_searchBar, &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed);
    connect(_searchBar, &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom);
    connect(_searchBar, &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory);
    connect(_searchBar, &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory);
    connect(_searchBar, &Konsole::IncrementalSearchBar::highlightMatchesToggled , this , &Konsole::SessionController::highlightMatches);
    connect(_searchBar, &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch);
    connect(_searchBar, &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch);
603 604
}

605 606
void SessionController::setShowMenuAction(QAction* action)
{
607
    _showMenuAction = action;
608 609
}

610
void SessionController::setupCommonActions()
611
{
612
    KActionCollection* collection = actionCollection();
613

614
    // Close Session
615
    QAction* action = collection->addAction(QStringLiteral("close-session"), this, &SessionController::closeSession);
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
616
    action->setText(i18n("&Close Session"));
617

Michal Humpula's avatar
Michal Humpula committed
618
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
619
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W);
620 621

    // Open Browser
622
    action = collection->addAction(QStringLiteral("open-browser"), this, &SessionController::openBrowser);
623
    action->setText(i18n("Open File Manager"));
Michal Humpula's avatar
Michal Humpula committed
624
    action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager")));
625

626
    // Copy and Paste
627
    action = KStandardAction::copy(this, &SessionController::copy, collection);
628
#ifdef Q_OS_MACOS
629 630 631 632 633 634
    // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META)
    // TODO: check what happens if we leave it to Qt to assign the default?
    collection->setDefaultShortcut(action, Qt::META + Qt::Key_C);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_C);
#endif
635 636
    // disabled at first, since nothing has been selected now
    action->setEnabled(false);
637

638 639 640 641 642 643 644 645
    // We need a different QAction on the context menu because one will be disabled when there's no selection,
    // other will be hidden.
    action = collection->addAction(QStringLiteral("edit_copy_contextmenu"));
    action->setText(i18n("Copy"));
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
    action->setVisible(false);
    connect(action, &QAction::triggered, this, &SessionController::copy);

646
    action = KStandardAction::paste(this, &SessionController::paste, collection);
Alex Richardson's avatar
Alex Richardson committed
647
    QList<QKeySequence> pasteShortcut;
648
#ifdef Q_OS_MACOS
649 650 651 652
    pasteShortcut.append(QKeySequence(Qt::META + Qt::Key_V));
    // No Insert key on Mac keyboards
#else
    pasteShortcut.append(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_V));
Alex Richardson's avatar
Alex Richardson committed
653
    pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
654
#endif
655
    collection->setDefaultShortcuts(action, pasteShortcut);
656

657
    action = collection->addAction(QStringLiteral("paste-selection"), this, &SessionController::pasteFromX11Selection);
658
    action->setText(i18n("Paste Selection"));
659
#ifdef Q_OS_MACOS
660 661 662 663
    collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert);
#endif
664

665
    _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
Michal Humpula's avatar
Michal Humpula committed
666
    _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
Jekyll Wu's avatar
Jekyll Wu committed
667
    _webSearchMenu->setVisible(false);
668
    collection->addAction(QStringLiteral("web-search"), _webSearchMenu);