SessionController.cpp 72.7 KB
Newer Older
1
/*
2
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
3
    Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/

// Own
22
#include "session/SessionController.h"
23

24
#include "profile/ProfileManager.h"
25
#include "konsoledebug.h"
26

27
// Qt
28
#include <QApplication>
29
#include <QAction>
patrick pereira's avatar
patrick pereira committed
30
#include <QList>
31
#include <QMenu>
32
#include <QKeyEvent>
33
34
#include <QPrinter>
#include <QPrintDialog>
35
#include <QFileDialog>
36
#include <QPainter>
37
#include <QStandardPaths>
38
39
#include <QUrl>
#include <QIcon>
40

41
// KDE
42
43
#include <KActionMenu>
#include <KActionCollection>
44
#include <KLocalizedString>
45
#include <KMessageBox>
46
#include <KRun>
47
#include <KShell>
Jekyll Wu's avatar
Jekyll Wu committed
48
#include <KToolInvocation>
Robert Knight's avatar
   
Robert Knight committed
49
#include <KToggleAction>
50
#include <KSelectAction>
51
#include <KXmlGuiWindow>
52
#include <KXMLGUIFactory>
53
#include <KXMLGUIBuilder>
Jekyll Wu's avatar
Jekyll Wu committed
54
55
#include <KUriFilter>
#include <KStringHandler>
Alex Richardson's avatar
Alex Richardson committed
56
#include <KSharedConfig>
57
#include <KConfigGroup>
58
#include <KCodecAction>
59
#include <KNotification>
60

61
// Konsole
62
#include "CopyInputDialog.h"
63
#include "Emulation.h"
64
65
66
67
68
69
70
#include "Enumeration.h"
#include "HistorySizeDialog.h"
#include "PrintOptions.h"
#include "RenameTabDialog.h"
#include "SaveHistoryTask.h"
#include "ScreenWindow.h"
#include "SearchHistoryTask.h"
71
72
73
74
75
76
#include "filterHotSpots/FileFilter.h"
#include "filterHotSpots/Filter.h"
#include "filterHotSpots/FilterChain.h"
#include "filterHotSpots/HotSpot.h"
#include "filterHotSpots/RegExpFilter.h"
#include "filterHotSpots/UrlFilter.h"
77
78
#include "history/HistoryType.h"
#include "history/HistoryTypeFile.h"
79
#include "history/HistoryTypeNone.h"
80
#include "history/compact/CompactHistoryType.h"
81
#include "profile/ProfileList.h"
82
83
#include "session/Session.h"
#include "session/SessionGroup.h"
84
#include "session/SessionManager.h"
85
#include "widgets/EditProfileDialog.h"
86
87
#include "widgets/IncrementalSearchBar.h"
#include "widgets/TerminalDisplay.h"
88

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

92
using namespace Konsole;
93

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

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

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

152
    setIdentifier(++_lastControllerId);
153
    sessionAttributeChanged();
154

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

230
    _allControllers.insert(this);
231
232
233

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

242
243
    setupSearchBar();
    _searchBar->setVisible(_isSearchBarEnabled);
244
245
246
}

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

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

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    // 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;


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

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

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
299
        disconnect(_sessionDisplayConnection->session(), &Konsole::Session::bellRequest, nullptr, nullptr);
300
        // second, connect the newly focused view to listen for the session's bell signal
301
        connect(_sessionDisplayConnection->session(), &Konsole::Session::bellRequest, _sessionDisplayConnection->view(), &Konsole::TerminalDisplay::bell);
302
303
304
305
306
307
308
309
310

        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();
        }
    }
}

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

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

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

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

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

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

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

342
    // apply new color
343
    _sessionDisplayConnection->session()->setColor(color);
344

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

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

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

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

372
373
void SessionController::rename()
{
374
    renameSession();
375
376
}

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

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

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

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

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

424
        _sessionDisplayConnection->session()->sendTextToTerminal(telnetCommand, QLatin1Char('\r'));
425

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

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

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
438
void SessionController::setupPrimaryScreenSpecificActions(bool use)
439
{
440
    KActionCollection* collection = actionCollection();
441
442
443
444
    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"));
445
446
447
448

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

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

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

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

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

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

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

485
486
487
488
489
490
    // 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
491
492
493
494
495
496
497
498
    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
499
            QAction* action = nullptr;
Jekyll Wu's avatar
Jekyll Wu committed
500

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

            _webSearchMenu->addSeparator();

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

            _webSearchMenu->setVisible(true);
        }
    }
}

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

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

530
    if (KUriFilter::self()->filterUri(filterData, { QStringLiteral("kurisearchfilter") })) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
531
        const QUrl& url = filterData.uri();
532
        new KRun(url, QApplication::activeWindow());
Jekyll Wu's avatar
Jekyll Wu committed
533
534
535
536
537
    }
}

void SessionController::configureWebShortcuts()
{
538
    KToolInvocation::kdeinitExec(QStringLiteral("kcmshell5"), { QStringLiteral("webshortcuts") });
539
540
}

541
542
void SessionController::sendSignal(QAction* action)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
543
    const auto signal = action->data().toInt();
544
    _sessionDisplayConnection->session()->sendSignal(signal);
545
546
}

547
548
void SessionController::sendForegroundColor()
{
549
550
    const QColor c = _sessionDisplayConnection->view()->getForegroundColor();
    _sessionDisplayConnection->session()->reportForegroundColor(c);
551
552
}

553
554
void SessionController::sendBackgroundColor()
{
555
556
    const QColor c = _sessionDisplayConnection->view()->getBackgroundColor();
    _sessionDisplayConnection->session()->reportBackgroundColor(c);
557
558
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
559
560
void SessionController::toggleReadOnly()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
561
    auto *action = qobject_cast<QAction*>(sender());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
562
563
    if (action != nullptr) {
        bool readonly = !isReadOnly();
564
        _sessionDisplayConnection->session()->setReadOnly(readonly);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
565
566
567
    }
}

568
569
void SessionController::removeSearchFilter()
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
570
    if (_searchFilter == nullptr) {
571
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
572
    }
573

574
    _sessionDisplayConnection->view()->filterChain()->removeFilter(_searchFilter);
575
    delete _searchFilter;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
576
    _searchFilter = nullptr;
577
578
}

579
void SessionController::setupSearchBar()
580
{
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
581
582
583
584
585
586
587
588
    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);
589
590
}

591
592
void SessionController::setShowMenuAction(QAction* action)
{
593
    _showMenuAction = action;
594
595
}

596
void SessionController::setupCommonActions()
597
{
598
    KActionCollection* collection = actionCollection();
599

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

Michal Humpula's avatar
Michal Humpula committed
604
    action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close")));
605
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W);
606
607

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

612
    // Copy and Paste
613
    action = KStandardAction::copy(this, &SessionController::copy, collection);
614
#ifdef Q_OS_MACOS
615
616
617
618
619
620
    // 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
621
622
    // disabled at first, since nothing has been selected now
    action->setEnabled(false);
623

624
625
626
627
628
629
630
631
    // 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);

632
    action = KStandardAction::paste(this, &SessionController::paste, collection);
Alex Richardson's avatar
Alex Richardson committed
633
    QList<QKeySequence> pasteShortcut;
634
#ifdef Q_OS_MACOS
635
636
637
638
    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
639
    pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert));
640
#endif
641
    collection->setDefaultShortcuts(action, pasteShortcut);
642

643
    action = collection->addAction(QStringLiteral("paste-selection"), this, &SessionController::pasteFromX11Selection);
644
    action->setText(i18n("Paste Selection"));
645
#ifdef Q_OS_MACOS
646
647
648
649
    collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V);
#else
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert);
#endif
650

651
    _webSearchMenu = new KActionMenu(i18n("Web Search"), this);
Michal Humpula's avatar
Michal Humpula committed
652
    _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")));
Jekyll Wu's avatar
Jekyll Wu committed
653
    _webSearchMenu->setVisible(false);
654
    collection->addAction(QStringLiteral("web-search"), _webSearchMenu);
Jekyll Wu's avatar
Jekyll Wu committed
655
656


657
    action = collection->addAction(QStringLiteral("select-all"), this, &SessionController::selectAll);
658
    action->setText(i18n("&Select All"));
Michal Humpula's avatar
Michal Humpula committed
659
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all")));
660

661
    action = collection->addAction(QStringLiteral("select-line"), this, &SessionController::selectLine);
662
663
    action->setText(i18n("Select &Line"));

664
    action = KStandardAction::saveAs(this, &SessionController::saveHistory, collection);
665
    action->setText(i18n("Save Output &As..."));
666
#ifdef Q_OS_MACOS
667
668
    action->setShortcut(QKeySequence(Qt::META + Qt::Key_S));
#endif
669

670
    action = KStandardAction::print(this, &SessionController::print_screen, collection);
671
    action->setText(i18n("&Print Screen..."));
672
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P);
673

674
    action = collection->addAction(QStringLiteral("adjust-history"), this, &SessionController::showHistoryOptions);
675
    action->setText(i18n("Adjust Scrollback..."));
Michal Humpula's avatar
Michal Humpula committed
676
    action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
677

678
    action = collection->addAction(QStringLiteral("clear-history"), this, &SessionController::clearHistory);
679
    action->setText(i18n("Clear Scrollback"));
Michal Humpula's avatar
Michal Humpula committed
680
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
681

682
    action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, &SessionController::clearHistoryAndReset);
683
    action->setText(i18n("Clear Scrollback and Reset"));
Michal Humpula's avatar
Michal Humpula committed
684
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
685
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K);
686
687

    // Profile Options
688
    action = collection->addAction(QStringLiteral("edit-current-profile"), this, &SessionController::editCurrentProfile);
689
    action->setText(i18n("Edit Current Profile..."));
Michal Humpula's avatar
Michal Humpula committed
690
    action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
691

692
    _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this);
693
    collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu);
694
    connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu);
695

696
    // History
697
    _findAction = KStandardAction::find(this, &SessionController::searchBarEvent, collection);
698

699
    _findNextAction = KStandardAction::findNext(this, &SessionController::findNextInHistory, collection);
700
701
    _findNextAction->setEnabled(false);

702
    _findPreviousAction = KStandardAction::findPrev(this, &SessionController::findPreviousInHistory, collection);
703
    _findPreviousAction->setEnabled(false);
704

705
706
707
708
709
710
711
712
713
714
#ifdef Q_OS_MACOS
    collection->setDefaultShortcut(_findAction, Qt::META + Qt::Key_F);
    collection->setDefaultShortcut(_findNextAction, Qt::META + Qt::Key_G);
    collection->setDefaultShortcut(_findPreviousAction, Qt::META + Qt::SHIFT + Qt::Key_G);
#else
    collection->setDefaultShortcut(_findAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_F);
    collection->setDefaultShortcut(_findNextAction, Qt::Key_F3);
    collection->setDefaultShortcut(_findPreviousAction, Qt::SHIFT + Qt::Key_F3);
#endif

715
716
    // Character Encoding
    _codecAction = new KCodecAction(i18n("Set &Encoding"), this);
Michal Humpula's avatar
Michal Humpula committed
717
    _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set")));
718
    collection->addAction(QStringLiteral("set-encoding"), _codecAction);
719
720
    _codecAction->setCurrentCodec(QString::fromUtf8(_sessionDisplayConnection->session()->codec()));
    connect(_sessionDisplayConnection->session(), &Konsole::Session::sessionCodecChanged, this, &Konsole::SessionController::updateCodecAction);
721
722
723
    connect(_codecAction,
            QOverload<QTextCodec*>::of(&KCodecAction::triggered), this,
            &Konsole::SessionController::changeCodec);
724
725

    // Read-only
726
    action = collection->addAction(QStringLiteral("view-readonly"), this, &SessionController::toggleReadOnly);
727
728
729
    action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only"));
    action->setCheckable(true);
    updateReadOnlyActionStates();
730
731
732
733
734
735
}

void SessionController::setupExtraActions()
{
    KActionCollection* collection = actionCollection();

736
    // Rename Session
737
    QAction* action = collection->addAction(QStringLiteral("rename-session"), this, &SessionController::renameSession);
738
    action->setText(i18n("&Current Tab Settings..."));
Michal Humpula's avatar
Michal Humpula committed
739
    action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
740
    collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S);
741

742
    // Copy input to ==> all tabs
Kurt Hindenburg's avatar
Kurt Hindenburg committed
743
    auto* copyInputToAllTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-all-tabs"));
744
745
746
747
748
749
    copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window"));
    copyInputToAllTabsAction->setData(CopyInputToAllTabsMode);
    // this action is also used in other place, so remember it
    _copyInputToAllTabsAction = copyInputToAllTabsAction;

    // Copy input to ==> selected tabs
Kurt Hindenburg's avatar
Kurt Hindenburg committed
750
    auto* copyInputToSelectedTabsAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-selected-tabs"));
751
    copyInputToSelectedTabsAction->setText(i18n("&Select Tabs..."));
752
    collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Period);
753
754
755
    copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode);

    // Copy input to ==> none
Kurt Hindenburg's avatar
Kurt Hindenburg committed
756
    auto* copyInputToNoneAction = collection->add<KToggleAction>(QStringLiteral("copy-input-to-none"));
757
    copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None"));
758
    collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Slash);
759
760
761
762
763
    copyInputToNoneAction->setData(CopyInputToNoneMode);
    copyInputToNoneAction->setChecked(true); // the default state

    // The "Copy Input To" submenu
    // The above three choices are represented as combo boxes
Kurt Hindenburg's avatar
Kurt Hindenburg committed
764