TerminalDisplay.cpp 100 KB
Newer Older
1
/*
2
3
    SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
4

5
    SPDX-License-Identifier: GPL-2.0-or-later
6
*/
7

8
// Own
9
#include "terminalDisplay/TerminalDisplay.h"
10
#include "KonsoleSettings.h"
11

12
// Config
13
#include "config-konsole.h"
14

Robert Knight's avatar
Robert Knight committed
15
// Qt
16
#include <QApplication>
17
18
19
20
#include <QClipboard>
#include <QKeyEvent>
#include <QEvent>
#include <QFileInfo>
21
#include <QVBoxLayout>
22
23
#include <QAction>
#include <QLabel>
24
#include <QMimeData>
25
26
#include <QPainter>
#include <QPixmap>
27
#include <QStyle>
28
#include <QTimer>
Alex Richardson's avatar
Alex Richardson committed
29
#include <QDrag>
30
#include <QDesktopServices>
31
#include <QAccessible>
32
#include <QElapsedTimer>
33

Robert Knight's avatar
Robert Knight committed
34
// KDE
35
#include <KShell>
Robert Knight's avatar
   
Robert Knight committed
36
#include <KColorScheme>
Robert Knight's avatar
Robert Knight committed
37
#include <KCursor>
38
#include <KLocalizedString>
Robert Knight's avatar
Robert Knight committed
39
#include <KNotification>
40
41
#include <KIO/DropJob>
#include <KJobWidgets>
42
#include <KMessageBox>
43
#include <KMessageWidget>
44
#include <KIO/StatJob>
Robert Knight's avatar
Robert Knight committed
45
46

// Konsole
47
#include "extras/CompositeWidgetFocusWatcher.h"
48
#include "extras/AutoScrollHandler.h"
49
50
51
52
53

#include "filterHotSpots/Filter.h"
#include "filterHotSpots/TerminalImageFilterChain.h"
#include "filterHotSpots/HotSpot.h"
#include "filterHotSpots/FileFilterHotspot.h"
54
55
#include "filterHotSpots/EscapeSequenceUrlFilter.h"
#include "filterHotSpots/EscapeSequenceUrlFilterHotSpot.h"
56

57
#include "konsoledebug.h"
58
#include "../decoders/PlainTextDecoder.h"
59
#include "Screen.h"
60
#include "../characters/ExtendedCharTable.h"
61
#include "../widgets/TerminalDisplayAccessible.h"
62
#include "session/SessionController.h"
63
64
#include "session/SessionManager.h"
#include "session/Session.h"
65
#include "WindowSystemInfo.h"
66
#include "widgets/IncrementalSearchBar.h"
67
#include "profile/Profile.h"
68
#include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this.
69
#include "../characters/LineBlockCharacters.h"
70
#include "PrintOptions.h"
71
#include "../widgets/KonsolePrintManager.h"
72
#include "EscapeSequenceUrlExtractor.h"
73

74
75
76
#include "TerminalPainter.h"
#include "TerminalScrollBar.h"
#include "TerminalColor.h"
77
#include "TerminalFonts.h"
78

79
80
using namespace Konsole;

81
inline int TerminalDisplay::loc(int x, int y) const {
82
83
84
85
86
87
88
    if (y < 0 || y > _lines) {
        qDebug() << "Y: " << y << "Lines" << _lines;
    }
    if (x < 0 || x > _columns ) {
        qDebug() << "X" << x << "Columns" << _columns;
    }

89
90
91
92
93
94
95
96
    Q_ASSERT(y >= 0 && y < _lines);
    Q_ASSERT(x >= 0 && x < _columns);
    x = qBound(0, x, _columns - 1);
    y = qBound(0, y, _lines - 1);

    return y * _columns + x;
}

97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                Colors                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)

   Code        0       1       2       3       4       5       6       7
   ----------- ------- ------- ------- ------- ------- ------- ------- -------
   ANSI  (bgr) Black   Red     Green   Yellow  Blue    Magenta Cyan    White
   IBMPC (rgb) Black   Blue    Green   Cyan    Red     Magenta Yellow  White
*/

111
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
112
{
113
    // disconnect existing screen window if any
114
    if (!_screenWindow.isNull()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
115
        disconnect(_screenWindow , nullptr , this , nullptr);
116
117
    }

118
    _screenWindow = window;
119

120
    if (!_screenWindow.isNull()) {
121
122
123
        connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateLineProperties);
        connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateImage);
        connect(_screenWindow.data() , &Konsole::ScreenWindow::currentResultLineChanged , this , &Konsole::TerminalDisplay::updateImage);
124
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
125
126
            _filterUpdateRequired = true;
        });
127
        connect(_screenWindow.data(), &Konsole::ScreenWindow::screenAboutToChange, this, [this]() {
128
129
130
            _iPntSel = QPoint(-1, -1);
            _pntSel = QPoint(-1, -1);
            _tripleSelBegin = QPoint(-1, -1);
131
        });
132
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
133
134
            _filterUpdateRequired = true;
        });
135
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, []() {
136
137
            QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
        });
Jekyll Wu's avatar
Jekyll Wu committed
138
        _screenWindow->setWindowLines(_lines);
139
140
141

        auto profile = SessionManager::instance()->sessionProfile(_sessionController->session());
        _screenWindow->screen()->urlExtractor()->setAllowedLinkSchema(profile->escapedLinksSchema());
142
        _screenWindow->screen()->setReflowLines(profile->property<bool>(Profile::ReflowLines));
143
    }
144
145
}

146
147
148
149
150
151
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Accessibility                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

152
153
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
154
155

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
156
/**
157
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
158
159
160
161
162
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
163
    if (auto *display = qobject_cast<TerminalDisplay*>(object)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
164
        return new TerminalDisplayAccessible(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
165
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
166
    return nullptr;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
167
}
Alex Richardson's avatar
Alex Richardson committed
168
169

#endif
170
171
}

172
173
174
175
176
177
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

178
TerminalDisplay::TerminalDisplay(QWidget* parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
179
    : QWidget(parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
180
    , _screenWindow(nullptr)
Jekyll Wu's avatar
Jekyll Wu committed
181
    , _bellMasked(false)
182
    , _verticalLayout(new QVBoxLayout(this))
Kurt Hindenburg's avatar
Kurt Hindenburg committed
183
184
    , _lines(1)
    , _columns(1)
185
186
    , _prevCharacterLine(-1)
    , _prevCharacterColumn(-1)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
187
188
    , _usedLines(1)
    , _usedColumns(1)
189
    , _contentRect(QRect())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
190
    , _image(nullptr)
191
192
    , _imageSize(0)
    , _lineProperties(QVector<LineProperty>())
193
    , _randomSeed(0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
194
195
196
    , _resizing(false)
    , _showTerminalSizeHint(true)
    , _bidiEnabled(false)
197
    , _usesMouseTracking(false)
198
    , _bracketedPasteMode(false)
199
200
201
    , _iPntSel(QPoint(-1, -1))
    , _pntSel(QPoint(-1, -1))
    , _tripleSelBegin(QPoint(-1, -1))
Kurt Hindenburg's avatar
Kurt Hindenburg committed
202
203
204
    , _actSel(0)
    , _wordSelectionMode(false)
    , _lineSelectionMode(false)
205
    , _preserveLineBreaks(true)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
206
    , _columnSelectionMode(false)
207
    , _autoCopySelectedText(false)
208
    , _copyTextAsHTML(true)
209
    , _middleClickPasteMode(Enum::PasteFromX11Selection)
210
    , _wordCharacters(QStringLiteral(":@-./_~"))
211
    , _bellMode(Enum::NotifyBell)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
212
213
214
215
216
    , _allowBlinkingText(true)
    , _allowBlinkingCursor(false)
    , _textBlinking(false)
    , _cursorBlinking(false)
    , _hasTextBlinker(false)
217
    , _openLinksByDirectClick(false)
218
    , _ctrlRequiredForDrag(true)
219
    , _dropUrlsAsText(false)
220
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
221
    , _possibleTripleClick(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
222
223
    , _resizeWidget(nullptr)
    , _resizeTimer(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
224
    , _flowControlWarningEnabled(false)
225
    , _outputSuspendedMessageWidget(nullptr)
226
227
    , _size(QSize())
    , _wallpaper(nullptr)
228
    , _filterChain(new TerminalImageFilterChain(this))
229
    , _filterUpdateRequired(true)
230
    , _cursorShape(Enum::BlockCursor)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
231
    , _sessionController(nullptr)
232
    , _trimLeadingSpaces(false)
233
    , _trimTrailingSpaces(false)
234
    , _mouseWheelZoom(false)
235
236
    , _margin(1)
    , _centerContents(false)
237
    , _readOnlyMessageWidget(nullptr)
238
    , _readOnly(false)
239
    , _dimWhenInactive(false)
240
    , _scrollWheelState(ScrollState())
241
    , _searchBar(new IncrementalSearchBar(this))
242
    , _headerBar(new TerminalHeaderBar(this))
243
    , _searchResultRect(QRect())
244
    , _drawOverlay(false)
245
    , _scrollBar(nullptr)
246
    , _terminalColor(nullptr)
247
    , _printManager(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
248
249
250
251
252
{
    // terminal applications are not designed with Right-To-Left in mind,
    // so the layout is forced to Left-To-Right
    setLayoutDirection(Qt::LeftToRight);

253
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
254
255

    // create scroll bar for scrolling output up and down
256
    _scrollBar = new TerminalScrollBar(this);
257
    _scrollBar->setAutoFillBackground(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
258
    // set the scroll bar's slider to occupy the whole area of the scroll bar initially
259
    _scrollBar->setScroll(0, 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
260
    _scrollBar->setCursor(Qt::ArrowCursor);
261
    _headerBar->setCursor(Qt::ArrowCursor);
262
    connect(_headerBar, &TerminalHeaderBar::requestToggleExpansion, this, &Konsole::TerminalDisplay::requestToggleExpansion);
263
    connect(_headerBar, &TerminalHeaderBar::requestMoveToNewTab, this, [this]{requestMoveToNewTab(this);});
Laurent Montel's avatar
Laurent Montel committed
264
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
265

266
267
268
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
269
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
270
271
272
273

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
274
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
275
276
277
278
279

    // hide mouse cursor on keystroke or idle
    KCursor::setAutoHideCursor(this, true);
    setMouseTracking(true);

280
    setUsesMouseTracking(false);
281
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
282
283
284
285
286
287
288
289
290
291
292
293
294
295

    // Enable drag and drop support
    setAcceptDrops(true);
    _dragInfo.state = diNone;

    setFocusPolicy(Qt::WheelFocus);

    // enable input method support
    setAttribute(Qt::WA_InputMethodEnabled, true);

    // this is an important optimization, it tells Qt
    // that TerminalDisplay will handle repainting its entire area.
    setAttribute(Qt::WA_OpaquePaintEvent);

296
    // Add the stretch item once, the KMessageWidgets are inserted at index 0.
297
    _verticalLayout->addWidget(_headerBar);
298
299
    _verticalLayout->addStretch();
    _verticalLayout->setSpacing(0);
300
    _verticalLayout->setContentsMargins(0, 0, 0, 0);
301
    setLayout(_verticalLayout);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
302
    new AutoScrollHandler(this);
303
304

    // Keep this last
305
    CompositeWidgetFocusWatcher *focusWatcher = new CompositeWidgetFocusWatcher(this);
306
307
308
309
310
311
312
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, [this](bool focused) {_hasCompositeFocus = focused;});
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, &TerminalDisplay::compositeFocusChanged);
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            _headerBar, &TerminalHeaderBar::setFocusIndicatorState);

313
#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
314
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
315
#endif
316
317

    connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TerminalDisplay::setupHeaderVisibility);
318

319
320
321
    _terminalColor = new TerminalColor(this);
    connect(_terminalColor, &TerminalColor::onPalette, _scrollBar, &TerminalScrollBar::setPalette);

322
323
    _terminalFont = new TerminalFont(this);

324
    _terminalPainter = new TerminalPainter(this);
325
326
327
328
329
330
331
    connect(this, &TerminalDisplay::drawContents, _terminalPainter, &TerminalPainter::drawContents);
    connect(this, &TerminalDisplay::drawCurrentResultRect, _terminalPainter, &TerminalPainter::drawCurrentResultRect);
    connect(this, &TerminalDisplay::highlightScrolledLines, _terminalPainter, &TerminalPainter::highlightScrolledLines);
    connect(this, &TerminalDisplay::highlightScrolledLinesRegion, _terminalPainter, &TerminalPainter::highlightScrolledLinesRegion);
    connect(this, &TerminalDisplay::drawBackground, _terminalPainter, &TerminalPainter::drawBackground);
    connect(this, &TerminalDisplay::drawCharacters, _terminalPainter, &TerminalPainter::drawCharacters);
    connect(this, &TerminalDisplay::drawInputMethodPreeditString, _terminalPainter, &TerminalPainter::drawInputMethodPreeditString);
332

333
334
    auto ldrawBackground = [this](QPainter &painter,
            const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting) {
335
        emit drawBackground(painter, rect, backgroundColor, useOpacitySetting);
336
    };
337
    auto ldrawContents = [this](QPainter &paint, const QRect &rect, bool friendly) {
338
        emit drawContents(_image, paint, rect, friendly, _imageSize, _bidiEnabled, _lineProperties);
339
340
    };
    auto lgetBackgroundColor = [this]() {
341
        return _terminalColor->backgroundColor();
342
343
    };
    _printManager = new KonsolePrintManager(ldrawBackground, ldrawContents, lgetBackgroundColor);
344
345
}

346
TerminalDisplay::~TerminalDisplay()
347
{
348
349
350
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

351
352
    delete _readOnlyMessageWidget;
    delete _outputSuspendedMessageWidget;
353
354
    delete[] _image;
    delete _filterChain;
355
356
357

    _readOnlyMessageWidget = nullptr;
    _outputSuspendedMessageWidget = nullptr;
358
359

    delete _terminalPainter;
360
    delete _terminalColor;
361
    delete _printManager;
362
363
}

364
365
366
367
368
369
void TerminalDisplay::setupHeaderVisibility()
{
    _headerBar->applyVisibilitySettings();
    calcGeometry();
}

370
371
372
373
374
375
void TerminalDisplay::hideDragTarget()
{
    _drawOverlay = false;
    update();
}

376
void TerminalDisplay::showDragTarget(const QPoint& cursorPos)
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
{
    using EdgeDistance = std::pair<int, Qt::Edge>;
    auto closerToEdge = std::min<EdgeDistance>(
        {
            {cursorPos.x(), Qt::LeftEdge},
            {cursorPos.y(), Qt::TopEdge},
            {width() - cursorPos.x(), Qt::RightEdge},
            {height() - cursorPos.y(), Qt::BottomEdge}
        },
        [](const EdgeDistance& left, const EdgeDistance& right) -> bool {
            return left.first < right.first;
        }
    );
    if (_overlayEdge == closerToEdge.second) {
        return;
    }
    _overlayEdge = closerToEdge.second;
    _drawOverlay = true;
    update();
}

398
399
400
401
402
403
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Display Operations                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

404
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
405
406
407
{
    _cursorShape = shape;
}
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425

void TerminalDisplay::setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking)
{
    setKeyboardCursorShape(shape);

    setBlinkingCursorEnabled(isBlinking);

    // when the cursor shape and blinking state are changed via the
    // Set Cursor Style (DECSCUSR) escape sequences in vim, and if the
    // cursor isn't set to blink, the cursor shape doesn't actually
    // change until the cursor is moved by the user; calling update()
    // makes the cursor shape get updated sooner.
    if (!isBlinking) {
        update();
    }
}
void TerminalDisplay::resetCursorStyle()
{
426
    Q_ASSERT(_sessionController != nullptr);
427
    Q_ASSERT(!_sessionController->session().isNull());
428

429
    Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(_sessionController->session());
430

431
    if (currentProfile != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
432
        auto shape = static_cast<Enum::CursorShapeEnum>(currentProfile->property<int>(Profile::CursorShape));
433
434
435

        setKeyboardCursorShape(shape);
        setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled());
436
437
438
    }
}

439
void TerminalDisplay::setWallpaper(const ColorSchemeWallpaper::Ptr &p)
440
{
441
    _wallpaper = p;
442
443
}

444
void TerminalDisplay::scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount)
445
446
447
448
449
450
451
452
{
    _screenWindow->scrollBy(mode, amount, _scrollBar->scrollFullPage());
    _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
    updateLineProperties();
    updateImage();
    viewScrolledByUser();
}

453
454
455
456
457
458
459
460
461
void TerminalDisplay::setRandomSeed(uint randomSeed)
{
    _randomSeed = randomSeed;
}
uint TerminalDisplay::randomSeed() const
{
    return _randomSeed;
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
462
void TerminalDisplay::processFilters()
463
{
464

465
    if (_screenWindow.isNull()) {
466
467
468
469
        return;
    }

    if (!_filterUpdateRequired) {
470
        return;
471
    }
472

473
    const QRegion preUpdateHotSpots = _filterChain->hotSpotRegion();
474
475
476
477

    // use _screenWindow->getImage() here rather than _image because
    // other classes may call processFilters() when this display's
    // ScreenWindow emits a scrolled() signal - which will happen before
Kurt Hindenburg's avatar
Kurt Hindenburg committed
478
    // updateImage() is called on the display and therefore _image is
479
    // out of date at this point
Kurt Hindenburg's avatar
Kurt Hindenburg committed
480
481
482
483
    _filterChain->setImage(_screenWindow->getImage(),
                           _screenWindow->windowLines(),
                           _screenWindow->windowColumns(),
                           _screenWindow->getLineProperties());
484
    _filterChain->process();
485

486
    const QRegion postUpdateHotSpots = _filterChain->hotSpotRegion();
487

Kurt Hindenburg's avatar
Kurt Hindenburg committed
488
    update(preUpdateHotSpots | postUpdateHotSpots);
489
    _filterUpdateRequired = false;
490
491
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
492
void TerminalDisplay::updateImage()
493
{
494
    if (_screenWindow.isNull()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
495
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
496
    }
497

498
499
500
    // Better control over screen resizing visual glitches
    _screenWindow->updateCurrentLine();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
501
502
503
    // optimization - scroll the existing image where possible and
    // avoid expensive text drawing for parts of the image that
    // can simply be moved up or down
Yuri Chornoivan's avatar
Yuri Chornoivan committed
504
    // disable this shortcut for transparent konsole with scaled pixels, otherwise we get rendering artifacts, see BUG 350651
505
    if (!(WindowSystemInfo::HAVE_TRANSPARENCY && (qApp->devicePixelRatio() > 1.0)) && _wallpaper->isNull() && !_searchBar->isVisible()) {
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
        // if the flow control warning is enabled this will interfere with the
        // scrolling optimizations and cause artifacts.  the simple solution here
        // is to just disable the optimization whilst it is visible
        if (!((_outputSuspendedMessageWidget != nullptr) && _outputSuspendedMessageWidget->isVisible()) &&
            !((_readOnlyMessageWidget != nullptr) && _readOnlyMessageWidget->isVisible())) {

            // hide terminal size label to prevent it being scrolled and show again after scroll
            const bool viewResizeWidget = (_resizeWidget != nullptr) && _resizeWidget->isVisible();
            if (viewResizeWidget) {
                _resizeWidget->hide();
            }
            _scrollBar->scrollImage(_screenWindow->scrollCount(), _screenWindow->scrollRegion(), _image, _imageSize);
            if (viewResizeWidget) {
                _resizeWidget->show();
            }
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
522
    }
523

524
    if (_image == nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
525
526
527
528
        // Create _image.
        // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
        updateImageSize();
    }
529

Kurt Hindenburg's avatar
Kurt Hindenburg committed
530
    Character* const newimg = _screenWindow->getImage();
Jekyll Wu's avatar
Jekyll Wu committed
531
532
    const int lines = _screenWindow->windowLines();
    const int columns = _screenWindow->windowColumns();
533

534
    _scrollBar->setScroll(_screenWindow->currentLine() , _screenWindow->lineCount());
535

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
536
537
    Q_ASSERT(_usedLines <= _lines);
    Q_ASSERT(_usedColumns <= _columns);
538

539
540
541
    int y;
    int x;
    int len;
542

Jekyll Wu's avatar
Jekyll Wu committed
543
544
545
    const QPoint tL  = contentsRect().topLeft();
    const int    tLx = tL.x();
    const int    tLy = tL.y();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
546
    _hasTextBlinker = false;
547

Kurt Hindenburg's avatar
Kurt Hindenburg committed
548
    CharacterColor cf;       // undefined
549

Carlos Alves's avatar
Carlos Alves committed
550
551
    const int linesToUpdate = qBound(0, lines, _lines);
    const int columnsToUpdate = qBound(0, columns, _columns);
552

Kurt Hindenburg's avatar
Kurt Hindenburg committed
553
    auto dirtyMask = new char[columnsToUpdate + 2];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
554
    QRegion dirtyRegion;
555

Kurt Hindenburg's avatar
Kurt Hindenburg committed
556
557
558
559
    // debugging variable, this records the number of lines that are found to
    // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
    // which therefore need to be repainted
    int dirtyLineCount = 0;
560

Kurt Hindenburg's avatar
Kurt Hindenburg committed
561
    for (y = 0; y < linesToUpdate; ++y) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
562
        const Character* currentLine = &_image[y * _columns];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
563
        const Character* const newLine = &newimg[y * columns];
564

Kurt Hindenburg's avatar
Kurt Hindenburg committed
565
        bool updateLine = false;
566

Kurt Hindenburg's avatar
Kurt Hindenburg committed
567
        // The dirty mask indicates which characters need repainting. We also
568
        // mark surrounding neighbors dirty, in case the character exceeds
Kurt Hindenburg's avatar
Kurt Hindenburg committed
569
570
        // its cell boundaries
        memset(dirtyMask, 0, columnsToUpdate + 2);
571

Kurt Hindenburg's avatar
Kurt Hindenburg committed
572
573
        for (x = 0 ; x < columnsToUpdate ; ++x) {
            if (newLine[x] != currentLine[x]) {
574
                dirtyMask[x] = 1;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
575
            }
576
        }
577

Kurt Hindenburg's avatar
Kurt Hindenburg committed
578
        if (!_resizing) { // not while _resizing, we're expecting a paintEvent
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579
580
581
582
583
584
            for (x = 0; x < columnsToUpdate; ++x) {
                _hasTextBlinker |= (newLine[x].rendition & RE_BLINK);

                // Start drawing if this character or the next one differs.
                // We also take the next one into account to handle the situation
                // where characters exceed their cell width.
585
                if (dirtyMask[x] != 0) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
                    if (newLine[x + 0].character == 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
587
                        continue;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
588
                    }
589
                    const bool lineDraw = LineBlockCharacters::canDraw(newLine[x + 0].character);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
590
                    const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0);
591
                    const RenditionFlags cr = newLine[x].rendition;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
592
                    const CharacterColor clipboard = newLine[x].backgroundColor;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
593
594
595
                    if (newLine[x].foregroundColor != cf) {
                        cf = newLine[x].foregroundColor;
                    }
Jekyll Wu's avatar
Jekyll Wu committed
596
                    const int lln = columnsToUpdate - x;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
597
598
599
                    for (len = 1; len < lln; ++len) {
                        const Character& ch = newLine[x + len];

Kurt Hindenburg's avatar
Kurt Hindenburg committed
600
                        if (ch.character == 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
601
                            continue; // Skip trailing part of multi-col chars.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
602
                        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
603

Jekyll Wu's avatar
Jekyll Wu committed
604
                        const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character == 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
605
606
607
608

                        if (ch.foregroundColor != cf ||
                                ch.backgroundColor != clipboard ||
                                (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) ||
609
                                (dirtyMask[x + len] == 0) ||
610
                                LineBlockCharacters::canDraw(ch.character) != lineDraw ||
Kurt Hindenburg's avatar
Kurt Hindenburg committed
611
                                nextIsDoubleWidth != doubleWidth) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
612
                            break;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
613
                        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
614
615
616
617
618
                    }
                    updateLine = true;
                    x += len - 1;
                }
            }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
619
        }
620

Kurt Hindenburg's avatar
Kurt Hindenburg committed
621
622
623
624
        //both the top and bottom halves of double height _lines must always be redrawn
        //although both top and bottom halves contain the same characters, only
        //the top one is actually
        //drawn.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
625
        if (_lineProperties.count() > y) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
626
            updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
627
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
628
629
630
631
632
633
634
635

        // if the characters on the line are different in the old and the new _image
        // then this line must be repainted.
        if (updateLine) {
            dirtyLineCount++;

            // add the area occupied by this line to the region which needs to be
            // repainted
636
            QRect dirtyRect = QRect(_contentRect.left() + tLx ,
637
638
639
                                    _contentRect.top() + tLy + _terminalFont->fontHeight() * y ,
                                    _terminalFont->fontWidth() * columnsToUpdate ,
                                    _terminalFont->fontHeight());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
640
641
642

            dirtyRegion |= dirtyRect;
        }
643

Kurt Hindenburg's avatar
Kurt Hindenburg committed
644
645
646
        // replace the line of characters in the old _image with the
        // current line of the new _image
        memcpy((void*)currentLine, (const void*)newLine, columnsToUpdate * sizeof(Character));
647
    }
648

Kurt Hindenburg's avatar
Kurt Hindenburg committed
649
650
651
    // if the new _image is smaller than the previous _image, then ensure that the area
    // outside the new _image is cleared
    if (linesToUpdate < _usedLines) {
652
        dirtyRegion |= QRect(_contentRect.left() + tLx ,
653
654
655
                             _contentRect.top() + tLy + _terminalFont->fontHeight() * linesToUpdate ,
                             _terminalFont->fontWidth() * _columns ,
                             _terminalFont->fontHeight() * (_usedLines - linesToUpdate));
656
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
657
    _usedLines = linesToUpdate;
658

Kurt Hindenburg's avatar
Kurt Hindenburg committed
659
    if (columnsToUpdate < _usedColumns) {
660
        dirtyRegion |= QRect(_contentRect.left() + tLx + columnsToUpdate * _terminalFont->fontWidth(),
661
                             _contentRect.top() + tLy ,
662
663
                             _terminalFont->fontWidth() * (_usedColumns - columnsToUpdate) ,
                             _terminalFont->fontHeight() * _lines);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
664
665
    }
    _usedColumns = columnsToUpdate;
666

Kurt Hindenburg's avatar
Kurt Hindenburg committed
667
    dirtyRegion |= _inputMethodData.previousPreeditRect;
668

669
    if ((_screenWindow->currentResultLine() != -1) && (_screenWindow->scrollCount() != 0)) {
670
671
672
        // De-highlight previous result region
        dirtyRegion |= _searchResultRect;
        // Highlight new result region
673
674
        dirtyRegion |= QRect(0, _contentRect.top() + (_screenWindow->currentResultLine() - _screenWindow->currentLine()) * _terminalFont->fontHeight(),
                             _columns * _terminalFont->fontWidth(), _terminalFont->fontHeight());
675
676
    }

677
678
    if (_scrollBar->highlightScrolledLines().isEnabled()) {
        dirtyRegion |= emit highlightScrolledLinesRegion(dirtyRegion.isEmpty(), _scrollBar);
679
680
    }
    _screenWindow->resetScrollCount();
681

Kurt Hindenburg's avatar
Kurt Hindenburg committed
682
683
    // update the parts of the display which have changed
    update(dirtyRegion);
Stephan Kulow's avatar
Stephan Kulow committed
684

685
686
687
    if (_allowBlinkingText && _hasTextBlinker && !_blinkTextTimer->isActive()) {
        _blinkTextTimer->start();
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
688
689
690
691
692
    if (!_hasTextBlinker && _blinkTextTimer->isActive()) {
        _blinkTextTimer->stop();
        _textBlinking = false;
    }
    delete[] dirtyMask;
693

694
#ifndef QT_NO_ACCESSIBILITY
695
696
697
698
    QAccessibleEvent dataChangeEvent(this, QAccessible::VisibleDataChanged);
    QAccessible::updateAccessibility(&dataChangeEvent);
    QAccessibleTextCursorEvent cursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX());
    QAccessible::updateAccessibility(&cursorEvent);
699
#endif
700
701
}

702
void TerminalDisplay::showResizeNotification()
703
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
704
    if (_showTerminalSizeHint && isVisible()) {
705
        if (_resizeWidget == nullptr) {
706
            _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
707
            _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().boundingRect(i18n("Size: XXX x XXX")).width());
708
709
710
            _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
            _resizeWidget->setAlignment(Qt::AlignCenter);

711
            _resizeWidget->setStyleSheet(QStringLiteral("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"));
712
713

            _resizeTimer = new QTimer(this);
Jekyll Wu's avatar
Jekyll Wu committed
714
            _resizeTimer->setInterval(SIZE_HINT_DURATION);
715
            _resizeTimer->setSingleShot(true);
716
            connect(_resizeTimer, &QTimer::timeout, _resizeWidget, &QLabel::hide);
717
718
719
        }
        QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
        _resizeWidget->setText(sizeStr);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
720
721
        _resizeWidget->move((width() - _resizeWidget->width()) / 2,
                            (height() - _resizeWidget->height()) / 2 + 20);
722
        _resizeWidget->show();
Jekyll Wu's avatar
Jekyll Wu committed
723
        _resizeTimer->start();
724
    }
725
726
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
727
void TerminalDisplay::paintEvent(QPaintEvent* pe)
728
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
729
    QPainter paint(this);
730

731
732
    // Determine which characters should be repainted (1 region unit = 1 character)
    QRegion dirtyImageRegion;
733
734
735
    const QRegion region = pe->region() & contentsRect();

    for (const QRect &rect : region) {
736
        dirtyImageRegion += widgetToImage(rect);
737
        emit drawBackground(paint, rect, _terminalColor->backgroundColor(), true /* use opacity setting */);
738
    }
739

740
    if (_displayVerticalLine) {
741
742
        const int fontWidth = _terminalFont->fontWidth();
        const int x = (fontWidth/2) + (fontWidth * _displayVerticalLineAtChar);
743
        const QColor lineColor = _terminalColor->foregroundColor();
744
745
746
747
748

        paint.setPen(lineColor);
        paint.drawLine(QPoint(x, 0), QPoint(x, height()));
    }

749
750
    // only turn on text anti-aliasing, never turn on normal antialiasing
    // set https://bugreports.qt.io/browse/QTBUG-66036
751
    paint.setRenderHint(QPainter::TextAntialiasing, _terminalFont->antialiasText());
752

753
    for (const QRect &rect : qAsConst(dirtyImageRegion)) {
754
        emit drawContents(_image, paint, rect, false, _imageSize, _bidiEnabled, _lineProperties);
755
756
    }
    emit drawCurrentResultRect(paint, _searchResultRect);
757
758
    if (_scrollBar->highlightScrolledLines().isEnabled()) {
        emit highlightScrolledLines(paint, _scrollBar->highlightScrolledLines().isTimerActive(), _scrollBar->highlightScrolledLines().rect());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
759
    }
760
    emit drawInputMethodPreeditString(paint, preeditRect(), _inputMethodData, _image);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
761
    paintFilters(paint);
762
763

    const bool drawDimmed = _dimWhenInactive && !hasFocus();
764
765
766
    if (drawDimmed) {
        const QColor dimColor(0, 0, 0, _dimValue);
        for (const QRect &rect : region) {
767
768
769
            paint.fillRect(rect, dimColor);
        }
    }
770
771
772
773
774
775
776
777
778
779
780
781
782

    if (_drawOverlay) {
        const auto y = _headerBar->isVisible() ? _headerBar->height() : 0;
        const auto rect = _overlayEdge == Qt::LeftEdge ? QRect(0, y, width() / 2, height())
                  : _overlayEdge == Qt::TopEdge ? QRect(0, y, width(), height() / 2)
                  : _overlayEdge == Qt::RightEdge ? QRect(width() - width() / 2, y, width() / 2, height())
                  : QRect(0, height() - height() / 2, width(), height() / 2);

        paint.setRenderHint(QPainter::Antialiasing);
        paint.setPen(Qt::NoPen);
        paint.setBrush(QColor(100,100,100, 127));
        paint.drawRect(rect);
    }
783
784
}

785
786
QPoint TerminalDisplay::cursorPosition() const
{
787
    if (!_screenWindow.isNull()) {
788
        return _screenWindow->cursorPosition();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
789
    } else {
790
        return {0, 0};
Kurt Hindenburg's avatar
Kurt Hindenburg committed
791
    }
792
793
}

794
bool TerminalDisplay::isCursorOnDisplay() const
795
796
797
798
799
{
    return cursorPosition().x() < _columns &&
           cursorPosition().y() < _lines;
}

800
FilterChain* TerminalDisplay::filterChain() const
801
802
803
804
{
    return _filterChain;
}

805
void TerminalDisplay::paintFilters(QPainter& painter)
806
{
807
808
809
810
    if (_filterUpdateRequired) {
        return;
    }

811

812
    _filterChain->paint(this, painter);
813
}
814

Jekyll Wu's avatar
Jekyll Wu committed
815
816
817
QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
{
    QRect result;
818
819
820
821
822
823
    const int fontWidth = _terminalFont->fontWidth();
    const int fontHeight = _terminalFont->fontHeight();
    result.setLeft(_contentRect.left() + fontWidth * imageArea.left());
    result.setTop(_contentRect.top() + fontHeight * imageArea.top());
    result.setWidth(fontWidth * imageArea.width());
    result.setHeight(fontHeight * imageArea.height());
Jekyll Wu's avatar
Jekyll Wu committed
824
825
826
827

    return result;
}

828
829
830
QRect TerminalDisplay::widgetToImage(const QRect &widgetArea) const
{
    QRect result;
831
832
    const int fontWidth = _terminalFont->fontWidth();
    const int fontHeight = _terminalFont->fontHeight();
Carlos Alves's avatar
Carlos Alves committed
833
834
835
836
    result.setLeft(  qBound(0, (widgetArea.left()   - contentsRect().left() - _contentRect.left()) / fontWidth,  _usedColumns - 1));
    result.setTop(   qBound(0, (widgetArea.top()    - contentsRect().top()  - _contentRect.top() ) / fontHeight, _usedLines   - 1));
    result.setRight( qBound(0, (widgetArea.right()  - contentsRect().left() - _contentRect.left()) / fontWidth,  _usedColumns - 1));
    result.setBottom(qBound(0, (widgetArea.bottom() - contentsRect().top()  - _contentRect.top() ) / fontHeight, _usedLines   - 1));
837
838
839
    return result;
}

Jekyll Wu's avatar
Jekyll Wu committed
840
841
842
843
844
845
846
847
848
849
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                          Blinking Text & Cursor                           */
/*                                                                           */
/* ------------------------------------------------------------------------- */

void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
{
    _allowBlinkingCursor = blink;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
850
    if (blink && !_blinkCursorTimer->isActive()) {
Jekyll Wu's avatar
Jekyll Wu committed
851
        _blinkCursorTimer->start();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
852
    }
Jekyll Wu's avatar
Jekyll Wu committed
853
854
855
856
857

    if (!blink && _blinkCursorTimer->isActive()) {
        _blinkCursorTimer->stop();
        if (_cursorBlinking) {
            // if cursor is blinking(hidden), blink it again to make it show
858
859
            _cursorBlinking = false;
            updateCursor();
Jekyll Wu's avatar
Jekyll Wu committed
860
        }
861
        Q_ASSERT(!_cursorBlinking);
Jekyll Wu's avatar
Jekyll Wu committed
862
863
864
865
866
867
868
    }
}

void TerminalDisplay::setBlinkingTextEnabled(bool blink)
{
    _allowBlinkingText = blink;