diff --git a/src/Emulation.cpp b/src/Emulation.cpp index 4c620806..147cca1c 100644 --- a/src/Emulation.cpp +++ b/src/Emulation.cpp @@ -1,346 +1,346 @@ /* Copyright 2007-2008 Robert Knight Copyright 1997,1998 by Lars Doelle Copyright 1996 by Matthias Ettrich 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 #include "Emulation.h" // Qt #include // Konsole #include "KeyboardTranslator.h" #include "KeyboardTranslatorManager.h" #include "Screen.h" #include "ScreenWindow.h" using namespace Konsole; Emulation::Emulation() : _windows(QList()), _currentScreen(nullptr), _codec(nullptr), _decoder(nullptr), _keyTranslator(nullptr), - _usesMouse(false), + _usesMouseTracking(false), _bracketedPasteMode(false), _bulkTimer1(new QTimer(this)), _bulkTimer2(new QTimer(this)), _imageSizeInitialized(false) { // create screens with a default size _screen[0] = new Screen(40, 80); _screen[1] = new Screen(40, 80); _currentScreen = _screen[0]; QObject::connect(&_bulkTimer1, &QTimer::timeout, this, &Konsole::Emulation::showBulk); QObject::connect(&_bulkTimer2, &QTimer::timeout, this, &Konsole::Emulation::showBulk); // listen for mouse status changes - connect(this, &Konsole::Emulation::programUsesMouseChanged, this, - &Konsole::Emulation::usesMouseChanged); + connect(this, &Konsole::Emulation::programRequestsMouseTracking, this, + &Konsole::Emulation::setUsesMouseTracking); connect(this, &Konsole::Emulation::programBracketedPasteModeChanged, this, &Konsole::Emulation::bracketedPasteModeChanged); } -bool Emulation::programUsesMouse() const +bool Emulation::programUsesMouseTracking() const { - return _usesMouse; + return _usesMouseTracking; } -void Emulation::usesMouseChanged(bool usesMouse) +void Emulation::setUsesMouseTracking(bool usesMouseTracking) { - _usesMouse = usesMouse; + _usesMouseTracking = usesMouseTracking; } bool Emulation::programBracketedPasteMode() const { return _bracketedPasteMode; } void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode) { _bracketedPasteMode = bracketedPasteMode; } ScreenWindow *Emulation::createWindow() { auto window = new ScreenWindow(_currentScreen); _windows << window; connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::bufferedUpdate); connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::checkSelectedText); connect(this, &Konsole::Emulation::outputChanged, window, &Konsole::ScreenWindow::notifyOutputChanged); return window; } void Emulation::checkScreenInUse() { emit primaryScreenInUse(_currentScreen == _screen[0]); } void Emulation::checkSelectedText() { QString text = _currentScreen->selectedText(Screen::PreserveLineBreaks); emit selectionChanged(text); } Emulation::~Emulation() { foreach (ScreenWindow *window, _windows) { delete window; } delete _screen[0]; delete _screen[1]; delete _decoder; } void Emulation::setScreen(int index) { Screen *oldScreen = _currentScreen; _currentScreen = _screen[index & 1]; if (_currentScreen != oldScreen) { // tell all windows onto this emulation to switch to the newly active screen foreach (ScreenWindow *window, _windows) { window->setScreen(_currentScreen); } checkScreenInUse(); checkSelectedText(); } } void Emulation::clearHistory() { _screen[0]->setScroll(_screen[0]->getScroll(), false); } void Emulation::setHistory(const HistoryType &history) { _screen[0]->setScroll(history); showBulk(); } const HistoryType &Emulation::history() const { return _screen[0]->getScroll(); } void Emulation::setCodec(const QTextCodec *codec) { if (codec != nullptr) { _codec = codec; delete _decoder; _decoder = _codec->makeDecoder(); emit useUtf8Request(utf8()); } else { setCodec(LocaleCodec); } } void Emulation::setCodec(EmulationCodec codec) { if (codec == Utf8Codec) { setCodec(QTextCodec::codecForName("utf8")); } else if (codec == LocaleCodec) { setCodec(QTextCodec::codecForLocale()); } } void Emulation::setKeyBindings(const QString &name) { _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name); if (_keyTranslator == nullptr) { _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator(); } } QString Emulation::keyBindings() const { return _keyTranslator->name(); } // process application unicode input to terminal // this is a trivial scanner void Emulation::receiveChar(uint c) { c &= 0xff; switch (c) { case '\b': _currentScreen->backspace(); break; case '\t': _currentScreen->tab(); break; case '\n': _currentScreen->newLine(); break; case '\r': _currentScreen->toStartOfLine(); break; case 0x07: emit stateSet(NOTIFYBELL); break; default: _currentScreen->displayCharacter(c); break; } } void Emulation::sendKeyEvent(QKeyEvent *ev) { emit stateSet(NOTIFYNORMAL); if (!ev->text().isEmpty()) { // A block of text // Note that the text is proper unicode. // We should do a conversion here emit sendData(ev->text().toLocal8Bit()); } } void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) { // default implementation does nothing } /* We are doing code conversion from locale to unicode first. */ void Emulation::receiveData(const char *text, int length) { emit stateSet(NOTIFYACTIVITY); bufferedUpdate(); QVector unicodeText = _decoder->toUnicode(text, length).toUcs4(); //send characters to terminal emulator for (auto &&i : unicodeText) { receiveChar(i); } //look for z-modem indicator //-- someone who understands more about z-modems that I do may be able to move //this check into the above for loop? for (int i = 0; i < length; i++) { if (text[i] == '\030') { if (length - i - 1 > 3) { if (qstrncmp(text + i + 1, "B00", 3) == 0) { emit zmodemDownloadDetected(); } else if (qstrncmp(text + i + 1, "B01", 3) == 0) { emit zmodemUploadDetected(); } } } } } void Emulation::writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine) { _currentScreen->writeLinesToStream(decoder, startLine, endLine); } int Emulation::lineCount() const { // sum number of lines currently on _screen plus number of lines in history return _currentScreen->getLines() + _currentScreen->getHistLines(); } void Emulation::showBulk() { _bulkTimer1.stop(); _bulkTimer2.stop(); emit outputChanged(); _currentScreen->resetScrolledLines(); _currentScreen->resetDroppedLines(); } void Emulation::bufferedUpdate() { static const int BULK_TIMEOUT1 = 10; static const int BULK_TIMEOUT2 = 40; _bulkTimer1.setSingleShot(true); _bulkTimer1.start(BULK_TIMEOUT1); if (!_bulkTimer2.isActive()) { _bulkTimer2.setSingleShot(true); _bulkTimer2.start(BULK_TIMEOUT2); } } char Emulation::eraseChar() const { return '\b'; } void Emulation::setImageSize(int lines, int columns) { if ((lines < 1) || (columns < 1)) { return; } QSize screenSize[2] = { QSize(_screen[0]->getColumns(), _screen[0]->getLines()), QSize(_screen[1]->getColumns(), _screen[1]->getLines()) }; QSize newSize(columns, lines); if (newSize == screenSize[0] && newSize == screenSize[1]) { // If this method is called for the first time, always emit // SIGNAL(imageSizeChange()), even if the new size is the same as the // current size. See #176902 if (!_imageSizeInitialized) { emit imageSizeChanged(lines, columns); } } else { _screen[0]->resizeImage(lines, columns); _screen[1]->resizeImage(lines, columns); emit imageSizeChanged(lines, columns); bufferedUpdate(); } if (!_imageSizeInitialized) { _imageSizeInitialized = true; emit imageSizeInitialized(); } } QSize Emulation::imageSize() const { return QSize(_currentScreen->getColumns(), _currentScreen->getLines()); } diff --git a/src/Emulation.h b/src/Emulation.h index 37aee533..78eba56b 100644 --- a/src/Emulation.h +++ b/src/Emulation.h @@ -1,522 +1,527 @@ /* This file is part of Konsole, an X terminal. Copyright 2007-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle 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. */ #ifndef EMULATION_H #define EMULATION_H // Qt #include #include #include // Konsole #include "Enumeration.h" #include "konsoleprivate_export.h" class QKeyEvent; namespace Konsole { class KeyboardTranslator; class HistoryType; class Screen; class ScreenWindow; class TerminalCharacterDecoder; /** * This enum describes the available states which * the terminal emulation may be set to. * * These are the values used by Emulation::stateChanged() */ enum { /** The emulation is currently receiving user input. */ NOTIFYNORMAL = 0, /** * The terminal program has triggered a bell event * to get the user's attention. */ NOTIFYBELL = 1, /** * The emulation is currently receiving data from its * terminal input. */ NOTIFYACTIVITY = 2, // unused here? NOTIFYSILENCE = 3 }; /** * Base class for terminal emulation back-ends. * * The back-end is responsible for decoding an incoming character stream and * producing an output image of characters. * * When input from the terminal is received, the receiveData() slot should be called with * the data which has arrived. The emulation will process the data and update the * screen image accordingly. The codec used to decode the incoming character stream * into the unicode characters used internally can be specified using setCodec() * * The size of the screen image can be specified by calling setImageSize() with the * desired number of lines and columns. When new lines are added, old content * is moved into a history store, which can be set by calling setHistory(). * * The screen image can be accessed by creating a ScreenWindow onto this emulation * by calling createWindow(). Screen windows provide access to a section of the * output. Each screen window covers the same number of lines and columns as the * image size returned by imageSize(). The screen window can be moved up and down * and provides transparent access to both the current on-screen image and the * previous output. The screen windows emit an outputChanged signal * when the section of the image they are looking at changes. * Graphical views can then render the contents of a screen window, listening for notifications * of output changes from the screen window which they are associated with and updating * accordingly. * * The emulation also is also responsible for converting input from the connected views such * as keypresses and mouse activity into a character string which can be sent * to the terminal program. Key presses can be processed by calling the sendKeyEvent() slot, * while mouse events can be processed using the sendMouseEvent() slot. When the character * stream has been produced, the emulation will emit a sendData() signal with a pointer * to the character buffer. This data should be fed to the standard input of the terminal * process. The translation of key presses into an output character stream is performed * using a lookup in a set of key bindings which map key sequences to output * character sequences. The name of the key bindings set used can be specified using * setKeyBindings() * * The emulation maintains certain state information which changes depending on the * input received. The emulation can be reset back to its starting state by calling * reset(). * * The emulation also maintains an activity state, which specifies whether * terminal is currently active ( when data is received ), normal * ( when the terminal is idle or receiving user input ) or trying * to alert the user ( also known as a "Bell" event ). The stateSet() signal * is emitted whenever the activity state is set. This can be used to determine * how long the emulation has been active/idle for and also respond to * a 'bell' event in different ways. */ class KONSOLEPRIVATE_EXPORT Emulation : public QObject { Q_OBJECT public: /** Constructs a new terminal emulation */ Emulation(); ~Emulation() Q_DECL_OVERRIDE; /** * Creates a new window onto the output from this emulation. The contents * of the window are then rendered by views which are set to use this window using the * TerminalDisplay::setScreenWindow() method. */ ScreenWindow *createWindow(); /** Returns the size of the screen image which the emulation produces */ QSize imageSize() const; /** * Returns the total number of lines, including those stored in the history. */ int lineCount() const; /** * Sets the history store used by this emulation. When new lines * are added to the output, older lines at the top of the screen are transferred to a history * store. * * The number of lines which are kept and the storage location depend on the * type of store. */ void setHistory(const HistoryType &); /** Returns the history store used by this emulation. See setHistory() */ const HistoryType &history() const; /** Clears the history scroll. */ void clearHistory(); /** * Copies the output history from @p startLine to @p endLine * into @p stream, using @p decoder to convert the terminal * characters into text. * * @param decoder A decoder which converts lines of terminal characters with * appearance attributes into output text. PlainTextDecoder is the most commonly * used decoder. * @param startLine Index of first line to copy * @param endLine Index of last line to copy */ virtual void writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine); /** Returns the codec used to decode incoming characters. See setCodec() */ const QTextCodec *codec() const { return _codec; } /** Sets the codec used to decode incoming characters. */ void setCodec(const QTextCodec *); /** * Convenience method. * Returns true if the current codec used to decode incoming * characters is UTF-8 */ bool utf8() const { Q_ASSERT(_codec); return _codec->mibEnum() == 106; } /** Returns the special character used for erasing character. */ virtual char eraseChar() const; /** * Sets the key bindings used to key events * ( received through sendKeyEvent() ) into character * streams to send to the terminal. */ void setKeyBindings(const QString &name); /** * Returns the name of the emulation's current key bindings. * See setKeyBindings() */ QString keyBindings() const; /** * Copies the current image into the history and clears the screen. */ virtual void clearEntireScreen() = 0; /** Resets the state of the terminal. */ virtual void reset() = 0; /** - * Returns true if the active terminal program wants - * mouse input events. + * Returns true if the active terminal program is interested in Mouse + * Tracking events. * - * The programUsesMouseChanged() signal is emitted when this - * changes. + * The programRequestsMouseTracking() signal is emitted when a program + * indicates it's interested in Mouse Tracking events. + * + * See MODE_Mouse100{0,1,2,3} in Vt102Emulation. */ - bool programUsesMouse() const; + bool programUsesMouseTracking() const; bool programBracketedPasteMode() const; public Q_SLOTS: /** Change the size of the emulation's image */ virtual void setImageSize(int lines, int columns); /** * Interprets a sequence of characters and sends the result to the terminal. * This is equivalent to calling sendKeyEvent() for each character in @p text in succession. */ virtual void sendText(const QString &text) = 0; /** * Interprets a key press event and emits the sendData() signal with * the resulting character stream. */ virtual void sendKeyEvent(QKeyEvent *); /** * Converts information about a mouse event into an xterm-compatible escape * sequence and emits the character sequence via sendData() */ virtual void sendMouseEvent(int buttons, int column, int line, int eventType); /** * Sends a string of characters to the foreground terminal process. * * @param string The characters to send. */ virtual void sendString(const QByteArray &string) = 0; /** * Processes an incoming stream of characters. receiveData() decodes the incoming * character buffer using the current codec(), and then calls receiveChar() for * each unicode character in the resulting buffer. * * receiveData() also starts a timer which causes the outputChanged() signal * to be emitted when it expires. The timer allows multiple updates in quick * succession to be buffered into a single outputChanged() signal emission. * * @param text A string of characters received from the terminal program. * @param length The length of @p text */ void receiveData(const char *text, int length); /** * Sends information about the focus lost event to the terminal. */ virtual void focusLost() = 0; /** * Sends information about the focus gained event to the terminal. */ virtual void focusGained() = 0; Q_SIGNALS: /** * Emitted when a buffer of data is ready to send to the * standard input of the terminal. * * @param data The buffer of data ready to be sent */ void sendData(const QByteArray &data); /** * Requests that the pty used by the terminal process * be set to UTF 8 mode. * * Refer to the IUTF8 entry in termios(3) for more information. */ void useUtf8Request(bool); /** * Emitted when the activity state of the emulation is set. * * @param state The new activity state, one of NOTIFYNORMAL, NOTIFYACTIVITY * or NOTIFYBELL */ void stateSet(int state); /** * Emitted when the special sequence indicating the request for data * transmission through ZModem protocol is detected. */ void zmodemDownloadDetected(); void zmodemUploadDetected(); /** * Requests that the color of the text used * to represent the tabs associated with this * emulation be changed. This is a Konsole-specific * extension from pre-KDE 4 times. * * TODO: Document how the parameter works. */ void changeTabTextColorRequest(int color); /** - * This is emitted when the program running in the shell indicates whether or - * not it is interested in mouse events. + * This is emitted when the program (typically editors and other full-screen + * applications, ones that take up the whole terminal display), running in + * the terminal indicates whether or not it is interested in Mouse Tracking + * events. This is an XTerm extension, for more information have a look at: + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking * - * @param usesMouse This will be true if the program wants to be informed about - * mouse events or false otherwise. + * @param usesMouseTracking This will be true if the program is interested + * in Mouse Tracking events or false otherwise. */ - void programUsesMouseChanged(bool usesMouse); + void programRequestsMouseTracking(bool usesMouseTracking); void enableAlternateScrolling(bool enable); void programBracketedPasteModeChanged(bool bracketedPasteMode); /** * Emitted when the contents of the screen image change. * The emulation buffers the updates from successive image changes, * and only emits outputChanged() at sensible intervals when * there is a lot of terminal activity. * * Normally there is no need for objects other than the screen windows * created with createWindow() to listen for this signal. * * ScreenWindow objects created using createWindow() will emit their * own outputChanged() signal in response to this signal. */ void outputChanged(); /** * Emitted when the program running in the terminal wishes to update the * session's title. This also allows terminal programs to customize other * aspects of the terminal emulation display. * * This signal is emitted when the escape sequence "\033]ARG;VALUE\007" * is received in the input string, where ARG is a number specifying what * should change and VALUE is a string specifying the new value. * * TODO: The name of this method is not very accurate since this method * is used to perform a whole range of tasks besides just setting * the user-title of the session. * * @param title Specifies what to change. *
    *
  • 0 - Set window icon text and session title to @p newTitle
  • *
  • 1 - Set window icon text to @p newTitle
  • *
  • 2 - Set session title to @p newTitle
  • *
  • 11 - Set the session's default background color to @p newTitle, * where @p newTitle can be an HTML-style string ("#RRGGBB") or a named * color (eg 'red', 'blue'). * See http://doc.trolltech.com/4.2/qcolor.html#setNamedColor for more * details. *
  • *
  • 31 - Supposedly treats @p newTitle as a URL and opens it (NOT IMPLEMENTED)
  • *
  • 32 - Sets the icon associated with the session. @p newTitle is the name * of the icon to use, which can be the name of any icon in the current KDE icon * theme (eg: 'konsole', 'kate', 'folder_home')
  • *
* @param newTitle Specifies the new title */ void titleChanged(int title, const QString &newTitle); /** * Emitted when the terminal emulator's size has changed */ void imageSizeChanged(int lineCount, int columnCount); /** * Emitted when the setImageSize() is called on this emulation for * the first time. */ void imageSizeInitialized(); /** * Emitted after receiving the escape sequence which asks to change * the terminal emulator's size */ void imageResizeRequest(const QSize &sizz); /** * Emitted when the terminal program requests to change various properties * of the terminal display. * * A profile change command occurs when a special escape sequence, followed * by a string containing a series of name and value pairs is received. * This string can be parsed using a ProfileCommandParser instance. * * @param text A string expected to contain a series of key and value pairs in * the form: name=value;name2=value2 ... */ void profileChangeCommandReceived(const QString &text); /** * Emitted when a flow control key combination ( Ctrl+S or Ctrl+Q ) is pressed. * @param suspendKeyPressed True if Ctrl+S was pressed to suspend output or Ctrl+Q to * resume output. */ void flowControlKeyPressed(bool suspendKeyPressed); /** * Emitted when the active screen is switched, to indicate whether the primary * screen is in use. */ void primaryScreenInUse(bool use); /** * Emitted when the text selection is changed */ void selectionChanged(const QString &text); /** * Emitted when terminal code requiring terminal's response received. */ void sessionAttributeRequest(int id); /** * Emitted when Set Cursor Style (DECSCUSR) escape sequences are sent * to the terminal. * @p shape cursor shape * @p isBlinking if true, the cursor will be set to blink */ void setCursorStyleRequest(Enum::CursorShapeEnum shape = Enum::BlockCursor, bool isBlinking = false); /** * Emitted when reset() is called to reset the cursor style to the * current profile cursor shape and blinking settings. */ void resetCursorStyleRequest(); protected: virtual void setMode(int mode) = 0; virtual void resetMode(int mode) = 0; /** * Processes an incoming character. See receiveData() * @p c A unicode character code. */ virtual void receiveChar(uint c); /** * Sets the active screen. The terminal has two screens, primary and alternate. * The primary screen is used by default. When certain interactive programs such * as Vim are run, they trigger a switch to the alternate screen. * * @param index 0 to switch to the primary screen, or 1 to switch to the alternate screen */ void setScreen(int index); enum EmulationCodec { LocaleCodec = 0, Utf8Codec = 1 }; void setCodec(EmulationCodec codec); QList _windows; Screen *_currentScreen; // pointer to the screen which is currently active, // this is one of the elements in the screen[] array Screen *_screen[2]; // 0 = primary screen ( used by most programs, including the shell // scrollbars are enabled in this mode ) // 1 = alternate ( used by vi , emacs etc. // scrollbars are not enabled in this mode ) //decodes an incoming C-style character stream into a unicode QString using //the current text codec. (this allows for rendering of non-ASCII characters in text files etc.) const QTextCodec *_codec; QTextDecoder *_decoder; const KeyboardTranslator *_keyTranslator; // the keyboard layout protected Q_SLOTS: /** * Schedules an update of attached views. * Repeated calls to bufferedUpdate() in close succession will result in only a single update, * much like the Qt buffered update of widgets. */ void bufferedUpdate(); // used to emit the primaryScreenInUse(bool) signal void checkScreenInUse(); // used to emit the selectionChanged(QString) signal void checkSelectedText(); private Q_SLOTS: // triggered by timer, causes the emulation to send an updated screen image to each // view void showBulk(); - void usesMouseChanged(bool usesMouse); + void setUsesMouseTracking(bool usesMouseTracking); void bracketedPasteModeChanged(bool bracketedPasteMode); private: Q_DISABLE_COPY(Emulation) - bool _usesMouse; + bool _usesMouseTracking; bool _bracketedPasteMode; QTimer _bulkTimer1; QTimer _bulkTimer2; bool _imageSizeInitialized; }; } #endif // ifndef EMULATION_H diff --git a/src/Profile.h b/src/Profile.h index fbb2aaff..818e85eb 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -1,792 +1,795 @@ /* This source file is part of Konsole, a terminal emulator. Copyright 2007-2008 by Robert Knight 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. */ #ifndef PROFILE_H #define PROFILE_H // Qt #include #include #include #include #include // Konsole #include "konsoleprivate_export.h" namespace Konsole { class ProfileGroup; /** * Represents a terminal set-up which can be used to * set the initial state of new terminal sessions or applied * to existing sessions. Profiles consist of a number of named * properties, which can be retrieved using property() and * set using setProperty(). isPropertySet() can be used to check * whether a particular property has been set in a profile. * * Profiles support a simple form of inheritance. When a new Profile * is constructed, a pointer to a parent profile can be passed to * the constructor. When querying a particular property of a profile * using property(), the profile will return its own value for that * property if one has been set or otherwise it will return the * parent's value for that property. * * Profiles can be loaded from disk using ProfileReader instances * and saved to disk using ProfileWriter instances. */ class KONSOLEPRIVATE_EXPORT Profile : public QSharedData { friend class ProfileReader; friend class ProfileWriter; friend class ProfileGroup; public: typedef QExplicitlySharedDataPointer Ptr; typedef QExplicitlySharedDataPointer GroupPtr; /** * This enum describes the available properties * which a Profile may consist of. * * Properties can be set using setProperty() and read * using property() */ enum Property { /** (QString) Path to the profile's configuration file on-disk. */ Path, /** (QString) The descriptive name of this profile. */ Name, /** (QString) The untranslated name of this profile. * Warning: this is an internal property. Do not touch it. */ UntranslatedName, /** (QString) The name of the icon associated with this profile. * This is used in menus and tabs to represent the profile. */ Icon, /** (QString) The command to execute ( excluding arguments ) when * creating a new terminal session using this profile. */ Command, /** (QStringList) The arguments which are passed to the program * specified by the Command property when creating a new terminal * session using this profile. */ Arguments, /** (QStringList) Additional environment variables (in the form of * NAME=VALUE pairs) which are passed to the program specified by * the Command property when creating a new terminal session using * this profile. */ Environment, /** (QString) The initial working directory for sessions created * using this profile. */ Directory, /** (QString) The format used for tab titles when running normal * commands. */ LocalTabTitleFormat, /** (QString) The format used for tab titles when the session is * running a remote command (eg. SSH) */ RemoteTabTitleFormat, /** (bool) Specifies whether show hint for terminal size after * resizing the application window. */ ShowTerminalSizeHint, /** (QFont) The font to use in terminal displays using this profile. */ Font, /** (QString) The name of the color scheme to use in terminal * displays using this profile. * Color schemes are managed by the ColorSchemeManager class. */ ColorScheme, /** (QString) The name of the key bindings. * Key bindings are managed by the KeyboardTranslatorManager class. */ KeyBindings, /** (HistoryModeEnum) Specifies the storage type used for keeping * the output produced by terminal sessions using this profile. * * See Enum::HistoryModeEnum */ HistoryMode, /** (int) Specifies the number of lines of output to remember in * terminal sessions using this profile. Once the limit is reached, * the oldest lines are lost if the HistoryMode property is * FixedSizeHistory */ HistorySize, /** (ScrollBarPositionEnum) Specifies the position of the scroll bar * in terminal displays using this profile. * * See Enum::ScrollBarPositionEnum */ ScrollBarPosition, /** (bool) Specifies whether the PageUp/Down will scroll the full * height or half height. */ ScrollFullPage, /** (bool) Specifies whether the terminal will enable Bidirectional * text display */ BidiRenderingEnabled, /** (bool) Specifies whether text in terminal displays is allowed * to blink. */ BlinkingTextEnabled, /** (bool) Specifies whether the flow control keys (typically Ctrl+S, * Ctrl+Q) have any effect. Also known as Xon/Xoff */ FlowControlEnabled, /** (int) Specifies the pixels between the terminal lines. */ LineSpacing, /** (bool) Specifies whether the cursor blinks ( in a manner similar * to text editing applications ) */ BlinkingCursorEnabled, /** (bool) If true, terminal displays use a fixed color to draw the * cursor, specified by the CustomCursorColor property. Otherwise * the cursor changes color to match the character underneath it. */ UseCustomCursorColor, /** (CursorShapeEnum) The shape used by terminal displays to * represent the cursor. * * See Enum::CursorShapeEnum */ CursorShape, /** (QColor) The color used by terminal displays to draw the cursor. * Only applicable if the UseCustomCursorColor property is true. */ CustomCursorColor, /** (QString) A string consisting of the characters used to delimit * words when selecting text in the terminal display. */ WordCharacters, /** (TripleClickModeEnum) Specifies which part of current line should * be selected with triple click action. * * See Enum::TripleClickModeEnum */ TripleClickMode, /** (bool) If true, text that matches a link or an email address is * underlined when hovered by the mouse pointer. */ UnderlineLinksEnabled, /** (bool) If true, text that matches a file is * underlined when hovered by the mouse pointer. */ UnderlineFilesEnabled, /** (bool) If true, links can be opened by direct mouse click.*/ OpenLinksByDirectClickEnabled, /** (bool) If true, control key must be pressed to click and drag selected text. */ CtrlRequiredForDrag, /** (bool) If true, automatically copy selected text into the clipboard */ AutoCopySelectedText, /** (bool) The QMimeData object used when copying text always * has the plain/text MIME set and if this is @c true then the * text/html MIME is set too in that object i.e. the copied * text will include formatting, font faces, colors... etc; users * can paste the text as HTML (default) or as plain/text by using * e.g. the "Paste Special" functionality in LibreOffice. */ CopyTextAsHTML, /** (bool) If true, leading spaces are trimmed in selected text */ TrimLeadingSpacesInSelectedText, /** (bool) If true, trailing spaces are trimmed in selected text */ TrimTrailingSpacesInSelectedText, /** (bool) If true, then dropped URLs will be pasted as text without asking */ DropUrlsAsText, /** (bool) If true, middle mouse button pastes from X Selection */ PasteFromSelectionEnabled, /** (bool) If true, middle mouse button pastes from Clipboard */ PasteFromClipboardEnabled, /** (MiddleClickPasteModeEnum) Specifies the source from which mouse * middle click pastes data. * * See Enum::MiddleClickPasteModeEnum */ MiddleClickPasteMode, /** (String) Default text codec */ DefaultEncoding, /** (bool) Whether fonts should be aliased or not */ AntiAliasFonts, /** (bool) Whether character with intense colors should be rendered * in bold font or just in bright color. */ BoldIntense, /** (bool) Whether to use font's line characters instead of the * builtin code. */ UseFontLineCharacters, /** (bool) Whether new sessions should be started in the same * directory as the currently active session. */ StartInCurrentSessionDir, /** (int) Specifies the threshold of detected silence in seconds. */ SilenceSeconds, /** (BellModeEnum) Specifies the behavior of bell. * * See Enum::BellModeEnum */ BellMode, /** (int) Specifies the preferred columns. */ TerminalColumns, /** (int) Specifies the preferred rows. */ TerminalRows, /** Index of profile in the File Menu * WARNING: this is currently an internal field, which is * expected to be zero on disk. Do not modify it manually. * * In future, the format might be #.#.# to account for levels */ MenuIndex, /** (int) Margin width in pixels */ TerminalMargin, /** (bool) Center terminal when there is a margin */ TerminalCenter, /** (bool) If true, mouse wheel scroll with Ctrl key pressed * increases/decreases the terminal font size. */ MouseWheelZoomEnabled, - /** (bool) Specifies whether emulated up/down key presses are sent - * for mouse scroll wheel events, to programs using the Alternate - * Screen buffer if those programs indicate they're not interested - * in mouse events (check _usesMouse in the Emulation header); this - * is mainly for the benefit of programs that are never interested - * in mouse events (e.g. less). - * Some programs, such as vim, can tell the terminal whether they - * are interested in mouse events or not. Consequently, this option - * affects vim only if it indicates that it's not interested in - * mouse events. + /** (bool) Specifies whether emulated up/down key press events are + * sent, for mouse scroll wheel events, to programs using the + * Alternate Screen buffer; this is mainly for the benefit of + * programs that don't natively support mouse scroll events, e.g. + * less. + * + * This also works for scrolling in applications that support Mouse + * Tracking events but don't indicate they're interested in those + * events; for example, when vim doesn't indicate it's interested + * in Mouse Tracking events (i.e. when the mouse is in Normal + * (not Visual) mode): http://vimdoc.sourceforge.net/htmldoc/intro.html#Normal + * mouse wheel scroll events will send up/down key press events. + * * Default value is true. * See also, MODE_Mouse1007 in the Emulation header, which toggles * Alternate Scrolling with escape sequences. */ AlternateScrolling, /** (int) Keyboard modifiers to show URL hints */ UrlHintsModifiers }; /** * Constructs a new profile * * @param parent The parent profile. When querying the value of a * property using property(), if the property has not been set in this * profile then the parent's value for the property will be returned. */ explicit Profile(Ptr parent = Ptr()); virtual ~Profile(); /** * Copies all properties except Name and Path from the specified @p * profile into this profile * * @param profile The profile to copy properties from * @param differentOnly If true, only properties in @p profile which have * a different value from this profile's current value (either set via * setProperty() or inherited from the parent profile) will be set. */ void clone(Ptr profile, bool differentOnly = true); /** * A profile which contains a number of default settings for various * properties. This can be used as a parent for other profiles or a * fallback in case a profile cannot be loaded from disk. */ void useFallback(); /** * Changes the parent profile. When calling the property() method, * if the specified property has not been set for this profile, * the parent's value for the property will be returned instead. */ void setParent(Ptr parent); /** Returns the parent profile. */ const Ptr parent() const; /** Returns this profile as a group or null if this profile is not a * group. */ const GroupPtr asGroup() const; GroupPtr asGroup(); /** * Returns the current value of the specified @p property, cast to type T. * Internally properties are stored using the QVariant type and cast to T * using QVariant::value(); * * If the specified @p property has not been set in this profile, * and a non-null parent was specified in the Profile's constructor, * the parent's value for @p property will be returned. */ template T property(Property p) const; /** Sets the value of the specified @p property to @p value. */ virtual void setProperty(Property p, const QVariant &value); /** Returns true if the specified property has been set in this Profile * instance. */ virtual bool isPropertySet(Property p) const; /** Returns a map of the properties set in this Profile instance. */ virtual QHash setProperties() const; /** Returns true if no properties have been set in this Profile instance. */ bool isEmpty() const; /** * Returns true if this is a 'hidden' profile which should not be * displayed in menus or saved to disk. * * This is used for the fallback profile, in case there are no profiles on * disk which can be loaded, or for overlay profiles created to handle * command-line arguments which change profile properties. */ bool isHidden() const; /** Specifies whether this is a hidden profile. See isHidden() */ void setHidden(bool hidden); // // Convenience methods for property() and setProperty() go here // /** Convenience method for property(Profile::Path) */ QString path() const { return property(Profile::Path); } /** Convenience method for property(Profile::Name) */ QString name() const { return property(Profile::Name); } /** Convenience method for property(Profile::UntranslatedName) */ QString untranslatedName() const { return property(Profile::UntranslatedName); } /** Convenience method for property(Profile::Directory) */ QString defaultWorkingDirectory() const { return property(Profile::Directory); } /** Convenience method for property(Profile::Icon) */ QString icon() const { return property(Profile::Icon); } /** Convenience method for property(Profile::Command) */ QString command() const { return property(Profile::Command); } /** Convenience method for property(Profile::Arguments) */ QStringList arguments() const { return property(Profile::Arguments); } /** Convenience method for property(Profile::LocalTabTitleFormat) */ QString localTabTitleFormat() const { return property(Profile::LocalTabTitleFormat); } /** Convenience method for property(Profile::RemoteTabTitleFormat) */ QString remoteTabTitleFormat() const { return property(Profile::RemoteTabTitleFormat); } /** Convenience method for property(Profile::ShowTerminalSizeHint) */ bool showTerminalSizeHint() const { return property(Profile::ShowTerminalSizeHint); } /** Convenience method for property(Profile::Font) */ QFont font() const { return property(Profile::Font); } /** Convenience method for property(Profile::ColorScheme) */ QString colorScheme() const { return property(Profile::ColorScheme); } /** Convenience method for property(Profile::Environment) */ QStringList environment() const { return property(Profile::Environment); } /** Convenience method for property(Profile::KeyBindings) */ QString keyBindings() const { return property(Profile::KeyBindings); } /** Convenience method for property(Profile::HistorySize) */ int historySize() const { return property(Profile::HistorySize); } /** Convenience method for property(Profile::BidiRenderingEnabled) */ bool bidiRenderingEnabled() const { return property(Profile::BidiRenderingEnabled); } /** Convenience method for property(Profile::LineSpacing) */ int lineSpacing() const { return property(Profile::LineSpacing); } /** Convenience method for property(Profile::BlinkingTextEnabled) */ bool blinkingTextEnabled() const { return property(Profile::BlinkingTextEnabled); } /** Convenience method for property(Profile::MouseWheelZoomEnabled) */ bool mouseWheelZoomEnabled() const { return property(Profile::MouseWheelZoomEnabled); } /** Convenience method for property(Profile::BlinkingCursorEnabled) */ bool blinkingCursorEnabled() const { return property(Profile::BlinkingCursorEnabled); } /** Convenience method for property(Profile::FlowControlEnabled) */ bool flowControlEnabled() const { return property(Profile::FlowControlEnabled); } /** Convenience method for property(Profile::UseCustomCursorColor) */ bool useCustomCursorColor() const { return property(Profile::UseCustomCursorColor); } /** Convenience method for property(Profile::CustomCursorColor) */ QColor customCursorColor() const { return property(Profile::CustomCursorColor); } /** Convenience method for property(Profile::WordCharacters) */ QString wordCharacters() const { return property(Profile::WordCharacters); } /** Convenience method for property(Profile::UnderlineLinksEnabled) */ bool underlineLinksEnabled() const { return property(Profile::UnderlineLinksEnabled); } /** Convenience method for property(Profile::UnderlineFilesEnabled) */ bool underlineFilesEnabled() const { return property(Profile::UnderlineFilesEnabled); } bool autoCopySelectedText() const { return property(Profile::AutoCopySelectedText); } /** Convenience method for property(Profile::DefaultEncoding) */ QString defaultEncoding() const { return property(Profile::DefaultEncoding); } /** Convenience method for property(Profile::AntiAliasFonts) */ bool antiAliasFonts() const { return property(Profile::AntiAliasFonts); } /** Convenience method for property(Profile::BoldIntense) */ bool boldIntense() const { return property(Profile::BoldIntense); } /** Convenience method for property(Profile::UseFontLineCharacters)*/ bool useFontLineCharacters() const { return property(Profile::UseFontLineCharacters); } /** Convenience method for property(Profile::StartInCurrentSessionDir) */ bool startInCurrentSessionDir() const { return property(Profile::StartInCurrentSessionDir); } /** Convenience method for property(Profile::SilenceSeconds) */ int silenceSeconds() const { return property(Profile::SilenceSeconds); } /** Convenience method for property(Profile::TerminalColumns) */ int terminalColumns() const { return property(Profile::TerminalColumns); } /** Convenience method for property(Profile::TerminalRows) */ int terminalRows() const { return property(Profile::TerminalRows); } /** Convenience method for property(Profile::TerminalMargin) */ int terminalMargin() const { return property(Profile::TerminalMargin); } /** Convenience method for property(Profile::TerminalCenter) */ bool terminalCenter() const { return property(Profile::TerminalCenter); } /** Convenience method for property(Profile::MenuIndex) */ QString menuIndex() const { return property(Profile::MenuIndex); } int menuIndexAsInt() const; /** Return a list of all properties names and their type * (for use with -p option). */ const QStringList propertiesInfoList() const; /** * Returns the element from the Property enum associated with the * specified @p name. * * @param name The name of the property to look for, this is case * insensitive. */ static Property lookupByName(const QString &name); private: struct PropertyInfo; // Defines a new property, this property is then available // to all Profile instances. static void registerProperty(const PropertyInfo &info); // fills the table with default names for profile properties // the first time it is called. // subsequent calls return immediately static void fillTableWithDefaultNames(); // returns true if the property can be inherited static bool canInheritProperty(Property p); QHash _propertyValues; Ptr _parent; bool _hidden; static QHash PropertyInfoByName; static QHash PropertyInfoByProperty; // Describes a property. Each property has a name and group // which is used when saving/loading the profile. struct PropertyInfo { Property property; const char *name; const char *group; QVariant::Type type; }; static const PropertyInfo DefaultPropertyNames[]; }; inline bool Profile::canInheritProperty(Property p) { return p != Name && p != Path; } template inline T Profile::property(Property p) const { return property(p).value(); } template<> inline QVariant Profile::property(Property p) const { if (_propertyValues.contains(p)) { return _propertyValues[p]; } else if (_parent && canInheritProperty(p)) { return _parent->property(p); } else { return QVariant(); } } /** * A composite profile which allows a group of profiles to be treated as one. * When setting a property, the new value is applied to all profiles in the * group. When reading a property, if all profiles in the group have the same * value then that value is returned, otherwise the result is null. * * Profiles can be added to the group using addProfile(). When all profiles * have been added updateValues() must be called * to sync the group's property values with those of the group's profiles. * * The Profile::Name and Profile::Path properties are unique to individual * profiles, setting these properties on a ProfileGroup has no effect. */ class KONSOLEPRIVATE_EXPORT ProfileGroup : public Profile { public: typedef QExplicitlySharedDataPointer Ptr; /** Construct a new profile group, which is hidden by default. */ explicit ProfileGroup(Profile::Ptr profileParent = Profile::Ptr()); /** Add a profile to the group. Calling setProperty() will update this * profile. When creating a group, add the profiles to the group then * call updateValues() to make the group's property values reflect the * profiles currently in the group. */ void addProfile(Profile::Ptr profile) { _profiles.append(profile); } /** Remove a profile from the group. Calling setProperty() will no longer * affect this profile. */ void removeProfile(Profile::Ptr profile) { _profiles.removeAll(profile); } /** Returns the profiles in this group .*/ QList profiles() const { return _profiles; } /** * Updates the property values in this ProfileGroup to match those from * the group's profiles() * * For each available property, if each profile in the group has the same * value then the ProfileGroup will use that value for the property. * Otherwise the value for the property will be set to a null QVariant * * Some properties such as the name and the path of the profile * will always be set to null if the group has more than one profile. */ void updateValues(); /** Sets the value of @p property in each of the group's profiles to * @p value. */ void setProperty(Property p, const QVariant &value) Q_DECL_OVERRIDE; private: Q_DISABLE_COPY(ProfileGroup) QList _profiles; }; inline ProfileGroup::ProfileGroup(Profile::Ptr profileParent) : Profile(profileParent), _profiles(QList()) { setHidden(true); } inline const Profile::GroupPtr Profile::asGroup() const { const Profile::GroupPtr ptr(dynamic_cast( const_cast(this))); return ptr; } inline Profile::GroupPtr Profile::asGroup() { return Profile::GroupPtr(dynamic_cast(this)); } /** * Parses an input string consisting of property names * and assigned values and returns a table of properties * and values. * * The input string will typically look like this: * * @code * PropertyName=Value;PropertyName=Value ... * @endcode * * For example: * * @code * Icon=konsole;Directory=/home/bob * @endcode */ class KONSOLEPRIVATE_EXPORT ProfileCommandParser { public: /** * Parses an input string consisting of property names * and assigned values and returns a table of * properties and values. */ QHash parse(const QString &input); }; } Q_DECLARE_METATYPE(Konsole::Profile::Ptr) #endif // PROFILE_H diff --git a/src/Session.cpp b/src/Session.cpp index 23dd3419..26cc547e 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -1,1750 +1,1750 @@ /* This file is part of Konsole Copyright 2006-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle Copyright 2009 by Thomas Dreibholz 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 #include "Session.h" // Standard #include #include #include // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include #include // Konsole #include #include "ProcessInfo.h" #include "Pty.h" #include "TerminalDisplay.h" #include "ShellCommand.h" #include "Vt102Emulation.h" #include "ZModemDialog.h" #include "History.h" #include "konsoledebug.h" using namespace Konsole; int Session::lastSessionId = 0; static bool show_disallow_certain_dbus_methods_message = true; static const int ZMODEM_BUFFER_SIZE = 1048576; // 1 Mb Session::Session(QObject* parent) : QObject(parent) , _uniqueIdentifier(QUuid()) , _shellProcess(nullptr) , _emulation(nullptr) , _views(QList()) , _monitorActivity(false) , _monitorSilence(false) , _notifiedActivity(false) , _silenceSeconds(10) , _silenceTimer(nullptr) , _activityTimer(nullptr) , _autoClose(true) , _closePerUserRequest(false) , _nameTitle(QString()) , _displayTitle(QString()) , _userTitle(QString()) , _localTabTitleFormat(QString()) , _remoteTabTitleFormat(QString()) , _tabTitleSetByUser(false) , _iconName(QString()) , _iconText(QString()) , _addToUtmp(true) , _flowControlEnabled(true) , _program(QString()) , _arguments(QStringList()) , _environment(QStringList()) , _sessionId(0) , _initialWorkingDir(QString()) , _currentWorkingDir(QString()) , _reportedWorkingUrl(QUrl()) , _sessionProcessInfo(nullptr) , _foregroundProcessInfo(nullptr) , _foregroundPid(0) , _zmodemBusy(false) , _zmodemProc(nullptr) , _zmodemProgress(nullptr) , _hasDarkBackground(false) , _preferredSize(QSize()) , _readOnly(false) { _uniqueIdentifier = QUuid::createUuid(); //prepare DBus communication new SessionAdaptor(this); _sessionId = ++lastSessionId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Sessions/") + QString::number(_sessionId), this); //create emulation backend _emulation = new Vt102Emulation(); connect(_emulation, &Konsole::Emulation::titleChanged, this, &Konsole::Session::setUserTitle); connect(_emulation, &Konsole::Emulation::stateSet, this, &Konsole::Session::activityStateSet); connect(_emulation, &Konsole::Emulation::zmodemDownloadDetected, this, &Konsole::Session::fireZModemDownloadDetected); connect(_emulation, &Konsole::Emulation::zmodemUploadDetected, this, &Konsole::Session::fireZModemUploadDetected); connect(_emulation, &Konsole::Emulation::changeTabTextColorRequest, this, &Konsole::Session::changeTabTextColorRequest); connect(_emulation, &Konsole::Emulation::profileChangeCommandReceived, this, &Konsole::Session::profileChangeCommandReceived); connect(_emulation, &Konsole::Emulation::flowControlKeyPressed, this, &Konsole::Session::updateFlowControlState); connect(_emulation, &Konsole::Emulation::primaryScreenInUse, this, &Konsole::Session::onPrimaryScreenInUse); connect(_emulation, &Konsole::Emulation::selectionChanged, this, &Konsole::Session::selectionChanged); connect(_emulation, &Konsole::Emulation::imageResizeRequest, this, &Konsole::Session::resizeRequest); connect(_emulation, &Konsole::Emulation::sessionAttributeRequest, this, &Konsole::Session::sessionAttributeRequest); //create new teletype for I/O with shell process openTeletype(-1); //setup timer for monitoring session activity & silence _silenceTimer = new QTimer(this); _silenceTimer->setSingleShot(true); connect(_silenceTimer, &QTimer::timeout, this, &Konsole::Session::silenceTimerDone); _activityTimer = new QTimer(this); _activityTimer->setSingleShot(true); connect(_activityTimer, &QTimer::timeout, this, &Konsole::Session::activityTimerDone); connect(this, &Konsole::Session::tabRenamedByUser, this, &Konsole::Session::tabTitleSetByUser); } Session::~Session() { delete _foregroundProcessInfo; delete _sessionProcessInfo; delete _emulation; delete _shellProcess; delete _zmodemProc; } void Session::openTeletype(int fd) { if (isRunning()) { qWarning() << "Attempted to open teletype in a running session."; return; } delete _shellProcess; if (fd < 0) { _shellProcess = new Pty(); } else { _shellProcess = new Pty(fd); } _shellProcess->setUtf8Mode(_emulation->utf8()); // connect the I/O between emulator and pty process connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock); connect(_emulation, &Konsole::Emulation::sendData, _shellProcess, &Konsole::Pty::sendData); // UTF8 mode connect(_emulation, &Konsole::Emulation::useUtf8Request, _shellProcess, &Konsole::Pty::setUtf8Mode); // get notified when the pty process is finished connect(_shellProcess, static_cast(&Konsole::Pty::finished), this, &Konsole::Session::done); // emulator size // Use a direct connection to ensure that the window size is set before it runs connect(_emulation, &Konsole::Emulation::imageSizeChanged, this, &Konsole::Session::updateWindowSize, Qt::DirectConnection); connect(_emulation, &Konsole::Emulation::imageSizeInitialized, this, &Konsole::Session::run); } WId Session::windowId() const { // Returns a window ID for this session which is used // to set the WINDOWID environment variable in the shell // process. // // Sessions can have multiple views or no views, which means // that a single ID is not always going to be accurate. // // If there are no views, the window ID is just 0. If // there are multiple views, then the window ID for the // top-level window which contains the first view is // returned if (_views.count() == 0) { return 0; } else { QWidget* window = _views.first(); Q_ASSERT(window); while (window->parentWidget() != nullptr) { window = window->parentWidget(); } return window->winId(); } } void Session::setDarkBackground(bool darkBackground) { _hasDarkBackground = darkBackground; } bool Session::isRunning() const { return (_shellProcess != nullptr) && (_shellProcess->state() == QProcess::Running); } void Session::setCodec(QTextCodec* codec) { if (isReadOnly()) { return; } emulation()->setCodec(codec); } bool Session::setCodec(const QByteArray& name) { QTextCodec* codec = QTextCodec::codecForName(name); if (codec != nullptr) { setCodec(codec); return true; } else { return false; } } QByteArray Session::codec() { return _emulation->codec()->name(); } void Session::setProgram(const QString& program) { _program = ShellCommand::expand(program); } void Session::setArguments(const QStringList& arguments) { _arguments = ShellCommand::expand(arguments); } void Session::setInitialWorkingDirectory(const QString& dir) { _initialWorkingDir = validDirectory(KShell::tildeExpand(ShellCommand::expand(dir))); } QString Session::currentWorkingDirectory() { if (_reportedWorkingUrl.isValid() && _reportedWorkingUrl.isLocalFile()) { return _reportedWorkingUrl.path(); } // only returned cached value if (_currentWorkingDir.isEmpty()) { updateWorkingDirectory(); } return _currentWorkingDir; } void Session::updateWorkingDirectory() { updateSessionProcessInfo(); const QString currentDir = _sessionProcessInfo->validCurrentDir(); if (currentDir != _currentWorkingDir) { _currentWorkingDir = currentDir; emit currentDirectoryChanged(_currentWorkingDir); } } QList Session::views() const { return _views; } void Session::addView(TerminalDisplay* widget) { Q_ASSERT(!_views.contains(widget)); _views.append(widget); // connect emulation - view signals and slots connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, _emulation, &Konsole::Emulation::sendKeyEvent); connect(widget, &Konsole::TerminalDisplay::mouseSignal, _emulation, &Konsole::Emulation::sendMouseEvent); connect(widget, &Konsole::TerminalDisplay::sendStringToEmu, _emulation, &Konsole::Emulation::sendString); - // allow emulation to notify view when the foreground process - // indicates whether or not it is interested in mouse signals - connect(_emulation, &Konsole::Emulation::programUsesMouseChanged, widget, &Konsole::TerminalDisplay::setUsesMouse); + // allow emulation to notify the view when the foreground process + // indicates whether or not it is interested in Mouse Tracking events + connect(_emulation, &Konsole::Emulation::programRequestsMouseTracking, widget, &Konsole::TerminalDisplay::setUsesMouseTracking); - widget->setUsesMouse(_emulation->programUsesMouse()); + widget->setUsesMouseTracking(_emulation->programUsesMouseTracking()); connect(_emulation, &Konsole::Emulation::enableAlternateScrolling, widget, &Konsole::TerminalDisplay::setAlternateScrolling); connect(_emulation, &Konsole::Emulation::programBracketedPasteModeChanged, widget, &Konsole::TerminalDisplay::setBracketedPasteMode); widget->setBracketedPasteMode(_emulation->programBracketedPasteMode()); widget->setScreenWindow(_emulation->createWindow()); //connect view signals and slots connect(widget, &Konsole::TerminalDisplay::changedContentSizeSignal, this, &Konsole::Session::onViewSizeChange); connect(widget, &Konsole::TerminalDisplay::destroyed, this, &Konsole::Session::viewDestroyed); connect(widget, &Konsole::TerminalDisplay::focusLost, _emulation, &Konsole::Emulation::focusLost); connect(widget, &Konsole::TerminalDisplay::focusGained, _emulation, &Konsole::Emulation::focusGained); connect(_emulation, &Konsole::Emulation::setCursorStyleRequest, widget, &Konsole::TerminalDisplay::setCursorStyle); connect(_emulation, &Konsole::Emulation::resetCursorStyleRequest, widget, &Konsole::TerminalDisplay::resetCursorStyle); } void Session::viewDestroyed(QObject* view) { TerminalDisplay* display = reinterpret_cast(view); Q_ASSERT(_views.contains(display)); removeView(display); } void Session::removeView(TerminalDisplay* widget) { _views.removeAll(widget); disconnect(widget, nullptr, this, nullptr); // disconnect // - key presses signals from widget // - mouse activity signals from widget // - string sending signals from widget // // ... and any other signals connected in addView() disconnect(widget, nullptr, _emulation, nullptr); // disconnect state change signals emitted by emulation disconnect(_emulation, nullptr, widget, nullptr); // close the session automatically when the last view is removed if (_views.count() == 0) { close(); } } // Upon a KPty error, there is no description on what that error was... // Check to see if the given program is executable. QString Session::checkProgram(const QString& program) { QString exec = program; if (exec.isEmpty()) { return QString(); } QFileInfo info(exec); if (info.isAbsolute() && info.exists() && info.isExecutable()) { return exec; } exec = KRun::binaryName(exec, false); exec = KShell::tildeExpand(exec); const QString pexec = QStandardPaths::findExecutable(exec); if (pexec.isEmpty()) { qCritical() << i18n("Could not find binary: ") << exec; return QString(); } return exec; } void Session::terminalWarning(const QString& message) { static const QByteArray warningText = i18nc("@info:shell Alert the user with red color text", "Warning: ").toLocal8Bit(); QByteArray messageText = message.toLocal8Bit(); static const char redPenOn[] = "\033[1m\033[31m"; static const char redPenOff[] = "\033[0m"; _emulation->receiveData(redPenOn, qstrlen(redPenOn)); _emulation->receiveData("\n\r\n\r", 4); _emulation->receiveData(warningText.constData(), qstrlen(warningText.constData())); _emulation->receiveData(messageText.constData(), qstrlen(messageText.constData())); _emulation->receiveData("\n\r\n\r", 4); _emulation->receiveData(redPenOff, qstrlen(redPenOff)); } QString Session::shellSessionId() const { QString friendlyUuid(_uniqueIdentifier.toString()); friendlyUuid.remove(QLatin1Char('-')).remove(QLatin1Char('{')).remove(QLatin1Char('}')); return friendlyUuid; } void Session::run() { // FIXME: run() is called twice in some instances if (isRunning()) { qCDebug(KonsoleDebug) << "Attempted to re-run an already running session (" << _shellProcess->pid() << ")"; return; } //check that everything is in place to run the session if (_program.isEmpty()) { qWarning() << "Program to run not set."; } if (_arguments.isEmpty()) { qWarning() << "No command line arguments specified."; } if (_uniqueIdentifier.isNull()) { _uniqueIdentifier = QUuid::createUuid(); } const int CHOICE_COUNT = 3; // if '_program' is empty , fall back to default shell. If that is not set // then fall back to /bin/sh QString programs[CHOICE_COUNT] = {_program, QString::fromUtf8(qgetenv("SHELL")), QStringLiteral("/bin/sh")}; QString exec; int choice = 0; while (choice < CHOICE_COUNT) { exec = checkProgram(programs[choice]); if (exec.isEmpty()) { choice++; } else { break; } } // if a program was specified via setProgram(), but it couldn't be found, print a warning if (choice != 0 && choice < CHOICE_COUNT && !_program.isEmpty()) { terminalWarning(i18n("Could not find '%1', starting '%2' instead. Please check your profile settings.", _program, exec)); // if none of the choices are available, print a warning } else if (choice == CHOICE_COUNT) { terminalWarning(i18n("Could not find an interactive shell to start.")); return; } // if no arguments are specified, fall back to program name QStringList arguments = _arguments.join(QLatin1Char(' ')).isEmpty() ? QStringList() << exec : _arguments; if (!_initialWorkingDir.isEmpty()) { _shellProcess->setInitialWorkingDirectory(_initialWorkingDir); } else { _shellProcess->setInitialWorkingDirectory(QDir::currentPath()); } _shellProcess->setFlowControlEnabled(_flowControlEnabled); _shellProcess->setEraseChar(_emulation->eraseChar()); _shellProcess->setUseUtmp(_addToUtmp); // this is not strictly accurate use of the COLORFGBG variable. This does not // tell the terminal exactly which colors are being used, but instead approximates // the color scheme as "black on white" or "white on black" depending on whether // the background color is deemed dark or not const QString backgroundColorHint = _hasDarkBackground ? QStringLiteral("COLORFGBG=15;0") : QStringLiteral("COLORFGBG=0;15"); addEnvironmentEntry(backgroundColorHint); addEnvironmentEntry(QStringLiteral("SHELL_SESSION_ID=%1").arg(shellSessionId())); addEnvironmentEntry(QStringLiteral("WINDOWID=%1").arg(QString::number(windowId()))); const QString dbusService = QDBusConnection::sessionBus().baseService(); addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SERVICE=%1").arg(dbusService)); const QString dbusObject = QStringLiteral("/Sessions/%1").arg(QString::number(_sessionId)); addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_SESSION=%1").arg(dbusObject)); int result = _shellProcess->start(exec, arguments, _environment); if (result < 0) { terminalWarning(i18n("Could not start program '%1' with arguments '%2'.", exec, arguments.join(QLatin1String(" ")))); terminalWarning(_shellProcess->errorString()); return; } _shellProcess->setWriteable(false); // We are reachable via kwrited. emit started(); } void Session::setUserTitle(int what, const QString& caption) { //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) bool modified = false; if ((what == IconNameAndWindowTitle) || (what == WindowTitle)) { if (_userTitle != caption) { _userTitle = caption; modified = true; } } if ((what == IconNameAndWindowTitle) || (what == IconName)) { if (_iconText != caption) { _iconText = caption; modified = true; } } if (what == TextColor || what == BackgroundColor) { QString colorString = caption.section(QLatin1Char(';'), 0, 0); QColor color = QColor(colorString); if (color.isValid()) { if (what == TextColor) { emit changeForegroundColorRequest(color); } else { emit changeBackgroundColorRequest(color); } } } if (what == SessionName) { if (_localTabTitleFormat != caption) { _localTabTitleFormat = caption; setTitle(Session::DisplayedTitleRole, caption); modified = true; } } /* I don't believe this has ever worked in KDE 4.x if (what == 31) { QString cwd = caption; cwd = cwd.replace(QRegExp("^~"), QDir::homePath()); emit openUrlRequest(cwd); }*/ /* The below use of 32 works but appears to non-standard. It is from a commit from 2004 c20973eca8776f9b4f15bee5fdcb5a3205aa69de */ // change icon via \033]32;Icon\007 if (what == SessionIcon) { if (_iconName != caption) { _iconName = caption; modified = true; } } if (what == CurrentDirectory) { _reportedWorkingUrl = QUrl::fromUserInput(caption); emit currentDirectoryChanged(currentWorkingDirectory()); modified = true; } if (what == ProfileChange) { emit profileChangeCommandReceived(caption); return; } if (modified) { emit titleChanged(); } } QString Session::userTitle() const { return _userTitle; } void Session::setTabTitleFormat(TabTitleContext context , const QString& format) { if (context == LocalTabTitle) { _localTabTitleFormat = format; ProcessInfo* process = getProcessInfo(); process->setUserNameRequired(format.contains(QLatin1String("%u"))); } else if (context == RemoteTabTitle) { _remoteTabTitleFormat = format; } } QString Session::tabTitleFormat(TabTitleContext context) const { if (context == LocalTabTitle) { return _localTabTitleFormat; } else if (context == RemoteTabTitle) { return _remoteTabTitleFormat; } return QString(); } void Session::tabTitleSetByUser(bool set) { _tabTitleSetByUser = set; } bool Session::isTabTitleSetByUser() const { return _tabTitleSetByUser; } void Session::silenceTimerDone() { //FIXME: The idea here is that the notification popup will appear to tell the user than output from //the terminal has stopped and the popup will disappear when the user activates the session. // //This breaks with the addition of multiple views of a session. The popup should disappear //when any of the views of the session becomes active //FIXME: Make message text for this notification and the activity notification more descriptive. if (!_monitorSilence) { emit stateChanged(NOTIFYNORMAL); return; } bool hasFocus = false; foreach(TerminalDisplay *display, _views) { if (display->hasFocus()) { hasFocus = true; break; } } KNotification::event(hasFocus ? QStringLiteral("Silence") : QStringLiteral("SilenceHidden"), i18n("Silence in session '%1'", _nameTitle), QPixmap(), QApplication::activeWindow(), KNotification::CloseWhenWidgetActivated); emit stateChanged(NOTIFYSILENCE); } void Session::activityTimerDone() { _notifiedActivity = false; } void Session::updateFlowControlState(bool suspended) { if (suspended) { if (flowControlEnabled()) { foreach(TerminalDisplay * display, _views) { if (display->flowControlWarningEnabled()) { display->outputSuspended(true); } } } } else { foreach(TerminalDisplay * display, _views) { display->outputSuspended(false); } } } void Session::onPrimaryScreenInUse(bool use) { emit primaryScreenInUse(use); } void Session::sessionAttributeRequest(int id) { switch (id) { case BackgroundColor: // Get 'TerminalDisplay' (_view) background color emit getBackgroundColor(); break; } } void Session::activityStateSet(int state) { // TODO: should this hardcoded interval be user configurable? const int activityMaskInSeconds = 15; if (state == NOTIFYBELL) { emit bellRequest(i18n("Bell in session '%1'", _nameTitle)); } else if (state == NOTIFYACTIVITY) { // Don't notify if the terminal is active bool hasFocus = false; foreach(TerminalDisplay *display, _views) { if (display->hasFocus()) { hasFocus = true; break; } } if (_monitorActivity && !_notifiedActivity) { KNotification::event(hasFocus ? QStringLiteral("Activity") : QStringLiteral("ActivityHidden"), i18n("Activity in session '%1'", _nameTitle), QPixmap(), QApplication::activeWindow(), KNotification::CloseWhenWidgetActivated); // mask activity notification for a while to avoid flooding _notifiedActivity = true; _activityTimer->start(activityMaskInSeconds * 1000); } // reset the counter for monitoring continuous silence since there is activity if (_monitorSilence) { _silenceTimer->start(_silenceSeconds * 1000); } } if (state == NOTIFYACTIVITY && !_monitorActivity) { state = NOTIFYNORMAL; } if (state == NOTIFYSILENCE && !_monitorSilence) { state = NOTIFYNORMAL; } emit stateChanged(state); } void Session::onViewSizeChange(int /*height*/, int /*width*/) { updateTerminalSize(); } void Session::updateTerminalSize() { int minLines = -1; int minColumns = -1; // minimum number of lines and columns that views require for // their size to be taken into consideration ( to avoid problems // with new view widgets which haven't yet been set to their correct size ) const int VIEW_LINES_THRESHOLD = 2; const int VIEW_COLUMNS_THRESHOLD = 2; //select largest number of lines and columns that will fit in all visible views foreach(TerminalDisplay* view, _views) { if (!view->isHidden() && view->lines() >= VIEW_LINES_THRESHOLD && view->columns() >= VIEW_COLUMNS_THRESHOLD) { minLines = (minLines == -1) ? view->lines() : qMin(minLines , view->lines()); minColumns = (minColumns == -1) ? view->columns() : qMin(minColumns , view->columns()); view->processFilters(); } } // backend emulation must have a _terminal of at least 1 column x 1 line in size if (minLines > 0 && minColumns > 0) { _emulation->setImageSize(minLines , minColumns); } } void Session::updateWindowSize(int lines, int columns) { Q_ASSERT(lines > 0 && columns > 0); _shellProcess->setWindowSize(columns, lines); } void Session::refresh() { // attempt to get the shell process to redraw the display // // this requires the program running in the shell // to cooperate by sending an update in response to // a window size change // // the window size is changed twice, first made slightly larger and then // resized back to its normal size so that there is actually a change // in the window size (some shells do nothing if the // new and old sizes are the same) // // if there is a more 'correct' way to do this, please // send an email with method or patches to konsole-devel@kde.org const QSize existingSize = _shellProcess->windowSize(); _shellProcess->setWindowSize(existingSize.width() + 1, existingSize.height()); // introduce small delay to avoid changing size too quickly QThread::usleep(500); _shellProcess->setWindowSize(existingSize.width(), existingSize.height()); } void Session::sendSignal(int signal) { const ProcessInfo* process = getProcessInfo(); bool ok = false; int pid; pid = process->foregroundPid(&ok); if (ok) { ::kill(pid, signal); } } void Session::reportBackgroundColor(const QColor& c) { #define to65k(a) (QStringLiteral("%1").arg(int(((a)*0xFFFF)), 4, 16, QLatin1Char('0'))) QString msg = QStringLiteral("\033]11;rgb:") + to65k(c.redF()) + QLatin1Char('/') + to65k(c.greenF()) + QLatin1Char('/') + to65k(c.blueF()) + QLatin1Char('\a'); _emulation->sendString(msg.toUtf8()); #undef to65k } bool Session::kill(int signal) { if (_shellProcess->pid() <= 0) { return false; } int result = ::kill(_shellProcess->pid(), signal); if (result == 0) { return _shellProcess->waitForFinished(1000); } else { return false; } } void Session::close() { if (isRunning()) { if (!closeInNormalWay()) { closeInForceWay(); } } else { // terminal process has finished, just close the session QTimer::singleShot(1, this, &Konsole::Session::finished); } } bool Session::closeInNormalWay() { _autoClose = true; _closePerUserRequest = true; // for the possible case where following events happen in sequence: // // 1). the terminal process crashes // 2). the tab stays open and displays warning message // 3). the user closes the tab explicitly // if (!isRunning()) { emit finished(); return true; } static QSet knownShells({QStringLiteral("ash"), QStringLiteral("bash"), QStringLiteral("csh"), QStringLiteral("dash"), QStringLiteral("fish"), QStringLiteral("hush"), QStringLiteral("ksh"), QStringLiteral("mksh"), QStringLiteral("pdksh"), QStringLiteral("tcsh"), QStringLiteral("zsh")}); // If only the session's shell is running, try sending an EOF for a clean exit if (!isForegroundProcessActive() && knownShells.contains(QFileInfo(_program).fileName())) { _shellProcess->sendEof(); if (_shellProcess->waitForFinished(1000)) { return true; } qWarning() << "shell did not close, sending SIGHUP"; } // We tried asking nicely, ask a bit less nicely if (kill(SIGHUP)) { return true; } else { qWarning() << "Process " << _shellProcess->pid() << " did not die with SIGHUP"; _shellProcess->closePty(); return (_shellProcess->waitForFinished(1000)); } } bool Session::closeInForceWay() { _autoClose = true; _closePerUserRequest = true; if (kill(SIGKILL)) { return true; } else { qWarning() << "Process " << _shellProcess->pid() << " did not die with SIGKILL"; return false; } } void Session::sendTextToTerminal(const QString& text, const QChar& eol) const { if (isReadOnly()) { return; } _emulation->sendText(text + eol); } // Only D-Bus calls this function (via SendText or runCommand) void Session::sendText(const QString& text) const { if (isReadOnly()) { return; } #if !defined(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS) if (show_disallow_certain_dbus_methods_message) { KNotification::event(KNotification::Warning, QStringLiteral("Konsole D-Bus Warning"), i18n("The D-Bus methods sendText/runCommand were just used. There are security concerns about allowing these methods to be public. If desired, these methods can be changed to internal use only by re-compiling Konsole.

This warning will only show once for this Konsole instance.

")); show_disallow_certain_dbus_methods_message = false; } #endif _emulation->sendText(text); } // Only D-Bus calls this function void Session::runCommand(const QString& command) const { sendText(command + QLatin1Char('\n')); } void Session::sendMouseEvent(int buttons, int column, int line, int eventType) { _emulation->sendMouseEvent(buttons, column, line, eventType); } void Session::done(int exitCode, QProcess::ExitStatus exitStatus) { // This slot should be triggered only one time disconnect(_shellProcess, static_cast(&Konsole::Pty::finished), this, &Konsole::Session::done); if (!_autoClose) { _userTitle = i18nc("@info:shell This session is done", "Finished"); emit titleChanged(); return; } if (_closePerUserRequest) { emit finished(); return; } QString message; if (exitCode != 0) { if (exitStatus != QProcess::NormalExit) { message = i18n("Program '%1' crashed.", _program); } else { message = i18n("Program '%1' exited with status %2.", _program, exitCode); } //FIXME: See comments in Session::silenceTimerDone() KNotification::event(QStringLiteral("Finished"), message , QPixmap(), QApplication::activeWindow(), KNotification::CloseWhenWidgetActivated); } if (exitStatus != QProcess::NormalExit) { // this seeming duplicated line is for the case when exitCode is 0 message = i18n("Program '%1' crashed.", _program); terminalWarning(message); } else { emit finished(); } } Emulation* Session::emulation() const { return _emulation; } QString Session::keyBindings() const { return _emulation->keyBindings(); } QStringList Session::environment() const { return _environment; } void Session::setEnvironment(const QStringList& environment) { if (isReadOnly()) { return; } _environment = environment; } void Session::addEnvironmentEntry(const QString& entry) { _environment << entry; } int Session::sessionId() const { return _sessionId; } void Session::setKeyBindings(const QString& name) { _emulation->setKeyBindings(name); } void Session::setTitle(TitleRole role , const QString& newTitle) { if (title(role) != newTitle) { if (role == NameRole) { _nameTitle = newTitle; } else if (role == DisplayedTitleRole) { _displayTitle = newTitle; } emit titleChanged(); } } QString Session::title(TitleRole role) const { if (role == NameRole) { return _nameTitle; } else if (role == DisplayedTitleRole) { return _displayTitle; } else { return QString(); } } ProcessInfo* Session::getProcessInfo() { ProcessInfo* process = nullptr; if (isForegroundProcessActive() && updateForegroundProcessInfo()) { process = _foregroundProcessInfo; } else { updateSessionProcessInfo(); process = _sessionProcessInfo; } return process; } void Session::updateSessionProcessInfo() { Q_ASSERT(_shellProcess); bool ok; // The checking for pid changing looks stupid, but it is needed // at the moment to workaround the problem that processId() might // return 0 if ((_sessionProcessInfo == nullptr) || (processId() != 0 && processId() != _sessionProcessInfo->pid(&ok))) { delete _sessionProcessInfo; _sessionProcessInfo = ProcessInfo::newInstance(processId(), tabTitleFormat(Session::LocalTabTitle)); _sessionProcessInfo->setUserHomeDir(); } _sessionProcessInfo->update(); } bool Session::updateForegroundProcessInfo() { Q_ASSERT(_shellProcess); const int foregroundPid = _shellProcess->foregroundProcessGroup(); if (foregroundPid != _foregroundPid) { delete _foregroundProcessInfo; _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid, tabTitleFormat(Session::LocalTabTitle)); _foregroundPid = foregroundPid; } if (_foregroundProcessInfo != nullptr) { _foregroundProcessInfo->update(); return _foregroundProcessInfo->isValid(); } else { return false; } } bool Session::isRemote() { ProcessInfo* process = getProcessInfo(); bool ok = false; return (process->name(&ok) == QLatin1String("ssh") && ok); } QString Session::getDynamicTitle() { ProcessInfo* process = getProcessInfo(); // format tab titles using process info bool ok = false; if (process->name(&ok) == QLatin1String("ssh") && ok) { SSHProcessInfo sshInfo(*process); return sshInfo.format(tabTitleFormat(Session::RemoteTabTitle)); } /* * Parses an input string, looking for markers beginning with a '%' * character and returns a string with the markers replaced * with information from this process description. *
* The markers recognized are: *
    *
  • %u - Name of the user which owns the process.
  • *
  • %n - Replaced with the name of the process.
  • *
  • %d - Replaced with the last part of the path name of the * process' current working directory. * * (eg. if the current directory is '/home/bob' then * 'bob' would be returned) *
  • *
  • %D - Replaced with the current working directory of the process.
  • *
*/ QString title = tabTitleFormat(Session::LocalTabTitle); // search for and replace known marker title.replace(QLatin1String("%u"), process->userName()); title.replace(QLatin1String("%h"), process->localHost()); title.replace(QLatin1String("%n"), process->name(&ok)); QString dir = _reportedWorkingUrl.toLocalFile(); if (dir.isEmpty()) { // update current directory from process updateWorkingDirectory(); dir = process->validCurrentDir(); } if (title.contains(QLatin1String("%D"))) { const QString homeDir = process->userHomeDir(); if (!homeDir.isEmpty()) { QString tempDir = dir; // Change User's Home Dir w/ ~ only at the beginning if (tempDir.startsWith(homeDir)) { tempDir.remove(0, homeDir.length()); tempDir.prepend(QLatin1Char('~')); } title.replace(QLatin1String("%D"), tempDir); } else { // Example: 'sudo top' We have to replace %D with something title.replace(QLatin1String("%D"), QStringLiteral("-")); } } title.replace(QLatin1String("%d"), process->formatShortDir(dir)); return title; } QUrl Session::getUrl() { if (_reportedWorkingUrl.isValid()) { return _reportedWorkingUrl; } QString path; updateSessionProcessInfo(); if (_sessionProcessInfo->isValid()) { bool ok = false; // check if foreground process is bookmark-able if (isForegroundProcessActive() && _foregroundProcessInfo->isValid()) { // for remote connections, save the user and host // bright ideas to get the directory at the other end are welcome :) if (_foregroundProcessInfo->name(&ok) == QLatin1String("ssh") && ok) { SSHProcessInfo sshInfo(*_foregroundProcessInfo); QUrl url; url.setScheme(QStringLiteral("ssh")); url.setUserName(sshInfo.userName()); url.setHost(sshInfo.host()); const QString port = sshInfo.port(); if (!port.isEmpty() && port != QLatin1String("22")) { url.setPort(port.toInt()); } return url; } else { path = _foregroundProcessInfo->currentDir(&ok); if (!ok) { path.clear(); } } } else { // otherwise use the current working directory of the shell process path = _sessionProcessInfo->currentDir(&ok); if (!ok) { path.clear(); } } } return QUrl::fromLocalFile(path); } void Session::setIconName(const QString& iconName) { if (iconName != _iconName) { _iconName = iconName; emit titleChanged(); } } void Session::setIconText(const QString& iconText) { _iconText = iconText; } QString Session::iconName() const { return isReadOnly() ? QStringLiteral("object-locked") : _iconName; } QString Session::iconText() const { return _iconText; } void Session::setHistoryType(const HistoryType& hType) { _emulation->setHistory(hType); } const HistoryType& Session::historyType() const { return _emulation->history(); } void Session::clearHistory() { _emulation->clearHistory(); } QStringList Session::arguments() const { return _arguments; } QString Session::program() const { return _program; } bool Session::isMonitorActivity() const { return _monitorActivity; } bool Session::isMonitorSilence() const { return _monitorSilence; } void Session::setMonitorActivity(bool monitor) { if (_monitorActivity == monitor) { return; } _monitorActivity = monitor; _notifiedActivity = false; // This timer is meaningful only after activity has been notified _activityTimer->stop(); activityStateSet(NOTIFYNORMAL); } void Session::setMonitorSilence(bool monitor) { if (_monitorSilence == monitor) { return; } _monitorSilence = monitor; if (_monitorSilence) { _silenceTimer->start(_silenceSeconds * 1000); } else { _silenceTimer->stop(); } activityStateSet(NOTIFYNORMAL); } void Session::setMonitorSilenceSeconds(int seconds) { _silenceSeconds = seconds; if (_monitorSilence) { _silenceTimer->start(_silenceSeconds * 1000); } } void Session::setAddToUtmp(bool add) { _addToUtmp = add; } void Session::setAutoClose(bool close) { _autoClose = close; } bool Session::autoClose() const { return _autoClose; } void Session::setFlowControlEnabled(bool enabled) { if (isReadOnly()) { return; } _flowControlEnabled = enabled; if (_shellProcess != nullptr) { _shellProcess->setFlowControlEnabled(_flowControlEnabled); } emit flowControlEnabledChanged(enabled); } bool Session::flowControlEnabled() const { if (_shellProcess != nullptr) { return _shellProcess->flowControlEnabled(); } else { return _flowControlEnabled; } } void Session::fireZModemDownloadDetected() { if (!_zmodemBusy) { QTimer::singleShot(10, this, &Konsole::Session::zmodemDownloadDetected); _zmodemBusy = true; } } void Session::fireZModemUploadDetected() { if (!_zmodemBusy) { QTimer::singleShot(10, this, &Konsole::Session::zmodemUploadDetected); } } void Session::cancelZModem() { _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort _zmodemBusy = false; } void Session::startZModem(const QString& zmodem, const QString& dir, const QStringList& list) { _zmodemBusy = true; _zmodemProc = new KProcess(); _zmodemProc->setOutputChannelMode(KProcess::SeparateChannels); *_zmodemProc << zmodem << QStringLiteral("-v") << QStringLiteral("-e") << list; if (!dir.isEmpty()) { _zmodemProc->setWorkingDirectory(dir); } connect(_zmodemProc, &KProcess::readyReadStandardOutput, this, &Konsole::Session::zmodemReadAndSendBlock); connect(_zmodemProc, &KProcess::readyReadStandardError, this, &Konsole::Session::zmodemReadStatus); connect(_zmodemProc, static_cast(&KProcess::finished), this, &Konsole::Session::zmodemFinished); _zmodemProc->start(); disconnect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock); connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::zmodemReceiveBlock); _zmodemProgress = new ZModemDialog(QApplication::activeWindow(), false, i18n("ZModem Progress")); connect(_zmodemProgress, &Konsole::ZModemDialog::zmodemCancel, this, &Konsole::Session::zmodemFinished); _zmodemProgress->show(); } void Session::zmodemReadAndSendBlock() { _zmodemProc->setReadChannel(QProcess::StandardOutput); QByteArray data = _zmodemProc->read(ZMODEM_BUFFER_SIZE); while (data.count() != 0) { _shellProcess->sendData(data); data = _zmodemProc->read(ZMODEM_BUFFER_SIZE); } } void Session::zmodemReadStatus() { _zmodemProc->setReadChannel(QProcess::StandardError); QByteArray msg = _zmodemProc->readAll(); while (!msg.isEmpty()) { int i = msg.indexOf('\015'); int j = msg.indexOf('\012'); QByteArray txt; if ((i != -1) && ((j == -1) || (i < j))) { msg = msg.mid(i + 1); } else if (j != -1) { txt = msg.left(j); msg = msg.mid(j + 1); } else { txt = msg; msg.truncate(0); } if (!txt.isEmpty()) { _zmodemProgress->addText(QString::fromLocal8Bit(txt)); } } } void Session::zmodemReceiveBlock(const char* data, int len) { static int steps = 0; QByteArray bytes(data, len); _zmodemProc->write(bytes); // Provide some feedback to dialog if (steps > 100) { _zmodemProgress->addProgressText(QStringLiteral(".")); steps = 0; } steps++; } void Session::zmodemFinished() { /* zmodemFinished() is called by QProcess's finished() and ZModemDialog's user1Clicked(). Therefore, an invocation by user1Clicked() will recursively invoke this function again when the KProcess is deleted! */ if (_zmodemProc != nullptr) { KProcess* process = _zmodemProc; _zmodemProc = nullptr; // Set _zmodemProc to 0 avoid recursive invocations! _zmodemBusy = false; delete process; // Now, the KProcess may be disposed safely. disconnect(_shellProcess, &Konsole::Pty::receivedData, this , &Konsole::Session::zmodemReceiveBlock); connect(_shellProcess, &Konsole::Pty::receivedData, this, &Konsole::Session::onReceiveBlock); _shellProcess->sendData(QByteArrayLiteral("\030\030\030\030")); // Abort _shellProcess->sendData(QByteArrayLiteral("\001\013\n")); // Try to get prompt back _zmodemProgress->transferDone(); } } void Session::onReceiveBlock(const char* buf, int len) { _emulation->receiveData(buf, len); } QSize Session::size() { return _emulation->imageSize(); } void Session::setSize(const QSize& size) { if ((size.width() <= 1) || (size.height() <= 1)) { return; } emit resizeRequest(size); } QSize Session::preferredSize() const { return _preferredSize; } void Session::setPreferredSize(const QSize& size) { _preferredSize = size; } int Session::processId() const { return _shellProcess->pid(); } void Session::setTitle(int role , const QString& title) { switch (role) { case 0: setTitle(Session::NameRole, title); break; case 1: setTitle(Session::DisplayedTitleRole, title); // without these, that title will be overridden by the expansion of // title format shortly after, which will confuses users. _localTabTitleFormat = title; _remoteTabTitleFormat = title; break; } } QString Session::title(int role) const { switch (role) { case 0: return title(Session::NameRole); case 1: return title(Session::DisplayedTitleRole); default: return QString(); } } void Session::setTabTitleFormat(int context , const QString& format) { switch (context) { case 0: setTabTitleFormat(Session::LocalTabTitle, format); break; case 1: setTabTitleFormat(Session::RemoteTabTitle, format); break; } } QString Session::tabTitleFormat(int context) const { switch (context) { case 0: return tabTitleFormat(Session::LocalTabTitle); case 1: return tabTitleFormat(Session::RemoteTabTitle); default: return QString(); } } void Session::setHistorySize(int lines) { if (isReadOnly()) { return; } if (lines < 0) { setHistoryType(HistoryTypeFile()); } else if (lines == 0) { setHistoryType(HistoryTypeNone()); } else { setHistoryType(CompactHistoryType(lines)); } } int Session::historySize() const { const HistoryType& currentHistory = historyType(); if (currentHistory.isEnabled()) { if (currentHistory.isUnlimited()) { return -1; } else { return currentHistory.maximumLineCount(); } } else { return 0; } } int Session::foregroundProcessId() { int pid; bool ok = false; pid = getProcessInfo()->pid(&ok); if (!ok) { pid = -1; } return pid; } bool Session::isForegroundProcessActive() { // foreground process info is always updated after this return (_shellProcess->pid() != _shellProcess->foregroundProcessGroup()); } QString Session::foregroundProcessName() { QString name; if (updateForegroundProcessInfo()) { bool ok = false; name = _foregroundProcessInfo->name(&ok); if (!ok) { name.clear(); } } return name; } void Session::saveSession(KConfigGroup& group) { group.writePathEntry("WorkingDir", currentWorkingDirectory()); group.writeEntry("LocalTab", tabTitleFormat(LocalTabTitle)); group.writeEntry("RemoteTab", tabTitleFormat(RemoteTabTitle)); group.writeEntry("SessionGuid", _uniqueIdentifier.toString()); group.writeEntry("Encoding", QString::fromUtf8(codec())); } void Session::restoreSession(KConfigGroup& group) { QString value; value = group.readPathEntry("WorkingDir", QString()); if (!value.isEmpty()) { setInitialWorkingDirectory(value); } value = group.readEntry("LocalTab"); if (!value.isEmpty()) { setTabTitleFormat(LocalTabTitle, value); } value = group.readEntry("RemoteTab"); if (!value.isEmpty()) { setTabTitleFormat(RemoteTabTitle, value); } value = group.readEntry("SessionGuid"); if (!value.isEmpty()) { _uniqueIdentifier = QUuid(value); } value = group.readEntry("Encoding"); if (!value.isEmpty()) { setCodec(value.toUtf8()); } } QString Session::validDirectory(const QString& dir) const { QString validDir = dir; if (validDir.isEmpty()) { validDir = QDir::currentPath(); } const QFileInfo fi(validDir); if (!fi.exists() || !fi.isDir()) { validDir = QDir::homePath(); } return validDir; } bool Session::isReadOnly() const { return _readOnly; } void Session::setReadOnly(bool readOnly) { if (_readOnly != readOnly) { _readOnly = readOnly; // Needed to update the tab icons and all // attached views. emit readOnlyChanged(); } } SessionGroup::SessionGroup(QObject* parent) : QObject(parent), _masterMode(0) { } SessionGroup::~SessionGroup() = default; int SessionGroup::masterMode() const { return _masterMode; } QList SessionGroup::sessions() const { return _sessions.keys(); } bool SessionGroup::masterStatus(Session* session) const { return _sessions[session]; } void SessionGroup::addSession(Session* session) { connect(session, &Konsole::Session::finished, this, &Konsole::SessionGroup::sessionFinished); _sessions.insert(session, false); } void SessionGroup::removeSession(Session* session) { disconnect(session, &Konsole::Session::finished, this, &Konsole::SessionGroup::sessionFinished); setMasterStatus(session, false); _sessions.remove(session); } void SessionGroup::sessionFinished() { Session* session = qobject_cast(sender()); Q_ASSERT(session); removeSession(session); } void SessionGroup::setMasterMode(int mode) { _masterMode = mode; } QList SessionGroup::masters() const { return _sessions.keys(true); } void SessionGroup::setMasterStatus(Session* session , bool master) { const bool wasMaster = _sessions[session]; if (wasMaster == master) { // No status change -> nothing to do. return; } _sessions[session] = master; if (master) { connect(session->emulation(), &Konsole::Emulation::sendData, this, &Konsole::SessionGroup::forwardData); } else { disconnect(session->emulation(), &Konsole::Emulation::sendData, this, &Konsole::SessionGroup::forwardData); } } void SessionGroup::forwardData(const QByteArray& data) { static bool _inForwardData = false; if (_inForwardData) { // Avoid recursive calls among session groups! // A recursive call happens when a master in group A calls forwardData() // in group B. If one of the destination sessions in group B is also a // master of a group including the master session of group A, this would // again call forwardData() in group A, and so on. return; } _inForwardData = true; const QList sessionsKeys = _sessions.keys(); foreach(Session* other, sessionsKeys) { if (!_sessions[other]) { other->emulation()->sendString(data); } } _inForwardData = false; } diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp index 8686a95c..27667280 100644 --- a/src/TerminalDisplay.cpp +++ b/src/TerminalDisplay.cpp @@ -1,3934 +1,3932 @@ /* This file is part of Konsole, a terminal emulator for KDE. Copyright 2006-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle 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 #include "TerminalDisplay.h" // Config #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include // Konsole #include "Filter.h" #include "konsoledebug.h" #include "konsole_wcwidth.h" #include "TerminalCharacterDecoder.h" #include "Screen.h" #include "LineFont.h" #include "SessionController.h" #include "ExtendedCharTable.h" #include "TerminalDisplayAccessible.h" #include "SessionManager.h" #include "Session.h" #include "WindowSystemInfo.h" using namespace Konsole; #define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefgjijklmnopqrstuvwxyz" \ "0123456789./+@" // we use this to force QPainter to display text in LTR mode // more information can be found in: http://unicode.org/reports/tr9/ const QChar LTR_OVERRIDE_CHAR(0x202D); inline int TerminalDisplay::loc(int x, int y) const { 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; } /* ------------------------------------------------------------------------- */ /* */ /* 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 */ ScreenWindow* TerminalDisplay::screenWindow() const { return _screenWindow; } void TerminalDisplay::setScreenWindow(ScreenWindow* window) { // disconnect existing screen window if any if (!_screenWindow.isNull()) { disconnect(_screenWindow , nullptr , this , nullptr); } _screenWindow = window; if (!_screenWindow.isNull()) { 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); connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() { _filterUpdateRequired = true; }); connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() { _filterUpdateRequired = true; }); _screenWindow->setWindowLines(_lines); } } const ColorEntry* TerminalDisplay::colorTable() const { return _colorTable; } void TerminalDisplay::updateScrollBarPalette() { QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR]; backgroundColor.setAlphaF(_opacity); QPalette p = palette(); p.setColor(QPalette::Window, backgroundColor); //this is a workaround to add some readability to old themes like Fusion //changing the light value for button a bit makes themes like fusion, windows and oxygen way more readable and pleasing QColor buttonColor; buttonColor.setHsvF(backgroundColor.hueF(), backgroundColor.saturationF(), backgroundColor.valueF() + (backgroundColor.valueF() < 0.5 ? 0.2 : -0.2)); p.setColor(QPalette::Button, buttonColor); p.setColor(QPalette::WindowText, _colorTable[DEFAULT_FORE_COLOR]); p.setColor(QPalette::ButtonText, _colorTable[DEFAULT_FORE_COLOR]); _scrollBar->setPalette(p); } void TerminalDisplay::setBackgroundColor(const QColor& color) { _colorTable[DEFAULT_BACK_COLOR] = color; QPalette p = palette(); p.setColor(backgroundRole(), color); setPalette(p); updateScrollBarPalette(); update(); } QColor TerminalDisplay::getBackgroundColor() const { QPalette p = palette(); return p.color(backgroundRole()); } void TerminalDisplay::setForegroundColor(const QColor& color) { _colorTable[DEFAULT_FORE_COLOR] = color; updateScrollBarPalette(); update(); } void TerminalDisplay::setColorTable(const ColorEntry table[]) { for (int i = 0; i < TABLE_COLORS; i++) { _colorTable[i] = table[i]; } setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]); } /* ------------------------------------------------------------------------- */ /* */ /* Font */ /* */ /* ------------------------------------------------------------------------- */ static inline bool isLineCharString(const QString& string) { if (string.length() == 0) { return false; } return isSupportedLineChar(string.at(0).unicode()); } void TerminalDisplay::fontChange(const QFont&) { QFontMetrics fm(font()); _fontHeight = fm.height() + _lineSpacing; // waba TerminalDisplay 1.123: // "Base character width on widest ASCII character. This prevents too wide // characters in the presence of double wide (e.g. Japanese) characters." // Get the width from representative normal width characters _fontWidth = qRound((static_cast(fm.width(QStringLiteral(REPCHAR))) / static_cast(qstrlen(REPCHAR)))); _fixedFont = true; const int fw = fm.width(QLatin1Char(REPCHAR[0])); for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) { if (fw != fm.width(QLatin1Char(REPCHAR[i]))) { _fixedFont = false; break; } } if (_fontWidth < 1) { _fontWidth = 1; } _fontAscent = fm.ascent(); emit changedFontMetricSignal(_fontHeight, _fontWidth); propagateSize(); update(); } void TerminalDisplay::setVTFont(const QFont& f) { QFont newFont(f); // In case the provided font doesn't have some specific characters it should // fall back to a Monospace fonts. newFont.setStyleHint(QFont::TypeWriter); QFontMetrics fontMetrics(newFont); // This check seems extreme and semi-random if ((fontMetrics.height() > height()) || (fontMetrics.maxWidth() > width())) { return; } // hint that text should be drawn without anti-aliasing. // depending on the user's font configuration, this may not be respected if (!_antialiasText) { newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::NoAntialias)); } // experimental optimization. Konsole assumes that the terminal is using a // mono-spaced font, in which case kerning information should have an effect. // Disabling kerning saves some computation when rendering text. newFont.setKerning(false); // Konsole cannot handle non-integer font metrics newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::ForceIntegerMetrics)); QFontInfo fontInfo(newFont); // if (!fontInfo.fixedPitch()) { // qWarning() << "Using a variable-width font - this might cause display problems"; // } // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch() // related? https://bugreports.qt.io/browse/QTBUG-34082 if (!fontInfo.exactMatch()) { const QChar comma(QLatin1Char(',')); QString nonMatching = fontInfo.family() % comma % QString::number(fontInfo.pointSizeF()) % comma % QString::number(fontInfo.pixelSize()) % comma % QString::number(static_cast(fontInfo.styleHint())) % comma % QString::number(fontInfo.weight()) % comma % QString::number(static_cast(fontInfo.style())) % comma % QString::number(static_cast(fontInfo.underline())) % comma % QString::number(static_cast(fontInfo.strikeOut())) % comma % QString::number(static_cast(fontInfo.fixedPitch())) % comma % QString::number(static_cast(fontInfo.rawMode())); qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system."; qCDebug(KonsoleDebug)<<" Selected: "<(object)) { return new TerminalDisplayAccessible(display); } return nullptr; } #endif } /* ------------------------------------------------------------------------- */ /* */ /* Constructor / Destructor */ /* */ /* ------------------------------------------------------------------------- */ TerminalDisplay::TerminalDisplay(QWidget* parent) : QWidget(parent) , _screenWindow(nullptr) , _bellMasked(false) , _verticalLayout(new QVBoxLayout(this)) , _fixedFont(true) , _fontHeight(1) , _fontWidth(1) , _fontAscent(1) , _boldIntense(true) , _lines(1) , _columns(1) , _usedLines(1) , _usedColumns(1) , _contentRect(QRect()) , _image(nullptr) , _imageSize(0) , _lineProperties(QVector()) , _randomSeed(0) , _resizing(false) , _showTerminalSizeHint(true) , _bidiEnabled(false) - , _mouseMarks(false) + , _usesMouseTracking(false) , _alternateScrolling(true) , _isPrimaryScreen(true) , _bracketedPasteMode(false) , _iPntSel(QPoint()) , _pntSel(QPoint()) , _tripleSelBegin(QPoint()) , _actSel(0) , _wordSelectionMode(false) , _lineSelectionMode(false) , _preserveLineBreaks(true) , _columnSelectionMode(false) , _autoCopySelectedText(false) , _copyTextAsHTML(true) , _middleClickPasteMode(Enum::PasteFromX11Selection) , _scrollBar(nullptr) , _scrollbarLocation(Enum::ScrollBarRight) , _scrollFullPage(false) , _wordCharacters(QStringLiteral(":@-./_~")) , _bellMode(Enum::NotifyBell) , _allowBlinkingText(true) , _allowBlinkingCursor(false) , _textBlinking(false) , _cursorBlinking(false) , _hasTextBlinker(false) , _urlHintsModifiers(Qt::NoModifier) , _showUrlHint(false) , _openLinksByDirectClick(false) , _ctrlRequiredForDrag(true) , _dropUrlsAsText(false) , _tripleClickMode(Enum::SelectWholeLine) , _possibleTripleClick(false) , _resizeWidget(nullptr) , _resizeTimer(nullptr) , _flowControlWarningEnabled(false) , _outputSuspendedMessageWidget(nullptr) , _lineSpacing(0) , _size(QSize()) , _blendColor(qRgba(0, 0, 0, 0xff)) , _wallpaper(nullptr) , _filterChain(new TerminalImageFilterChain()) , _mouseOverHotspotArea(QRegion()) , _filterUpdateRequired(true) , _cursorShape(Enum::BlockCursor) , _cursorColor(QColor()) , _antialiasText(true) , _useFontLineCharacters(false) , _printerFriendly(false) , _sessionController(nullptr) , _trimLeadingSpaces(false) , _trimTrailingSpaces(false) , _mouseWheelZoom(false) , _margin(1) , _centerContents(false) , _readOnlyMessageWidget(nullptr) , _readOnly(false) , _opacity(1.0) , _scrollWheelState(ScrollState()) { // terminal applications are not designed with Right-To-Left in mind, // so the layout is forced to Left-To-Right setLayoutDirection(Qt::LeftToRight); _contentRect = QRect(_margin, _margin, 1, 1); // create scroll bar for scrolling output up and down _scrollBar = new QScrollBar(this); _scrollBar->setAutoFillBackground(false); // set the scroll bar's slider to occupy the whole area of the scroll bar initially setScroll(0, 0); _scrollBar->setCursor(Qt::ArrowCursor); connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged); connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser); // setup timers for blinking text _blinkTextTimer = new QTimer(this); _blinkTextTimer->setInterval(TEXT_BLINK_DELAY); connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent); // setup timers for blinking cursor _blinkCursorTimer = new QTimer(this); _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2); connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent); // hide mouse cursor on keystroke or idle KCursor::setAutoHideCursor(this, true); setMouseTracking(true); - setUsesMouse(true); + setUsesMouseTracking(false); setBracketedPasteMode(false); setColorTable(ColorScheme::defaultTable); // 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); // Add the stretch item once, the KMessageWidgets are inserted at index 0. _verticalLayout->addStretch(); _verticalLayout->setSpacing(0); setLayout(_verticalLayout); // Take the scrollbar into account and add a margin to the layout. Without the timer the scrollbar width // is garbage. QTimer::singleShot(0, this, [this]() { const int scrollBarWidth = _scrollBar->isVisible() ? geometry().intersected(_scrollBar->geometry()).width() : 0; _verticalLayout->setContentsMargins(0, 0, scrollBarWidth, 0); }); new AutoScrollHandler(this); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(Konsole::accessibleInterfaceFactory); #endif } TerminalDisplay::~TerminalDisplay() { disconnect(_blinkTextTimer); disconnect(_blinkCursorTimer); delete _readOnlyMessageWidget; delete _outputSuspendedMessageWidget; delete[] _image; delete _filterChain; _readOnlyMessageWidget = nullptr; _outputSuspendedMessageWidget = nullptr; } /* ------------------------------------------------------------------------- */ /* */ /* Display Operations */ /* */ /* ------------------------------------------------------------------------- */ /** A table for emulating the simple (single width) unicode drawing chars. It represents the 250x - 257x glyphs. If it's zero, we can't use it. if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. Then, the pixels basically have the following interpretation: _|||_ -...- -...- -...- _|||_ where _ = none | = vertical line. - = horizontal line. */ enum LineEncode { TopL = (1 << 1), TopC = (1 << 2), TopR = (1 << 3), LeftT = (1 << 5), Int11 = (1 << 6), Int12 = (1 << 7), Int13 = (1 << 8), RightT = (1 << 9), LeftC = (1 << 10), Int21 = (1 << 11), Int22 = (1 << 12), Int23 = (1 << 13), RightC = (1 << 14), LeftB = (1 << 15), Int31 = (1 << 16), Int32 = (1 << 17), Int33 = (1 << 18), RightB = (1 << 19), BotL = (1 << 21), BotC = (1 << 22), BotR = (1 << 23) }; static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code) { //Calculate cell midpoints, end points. const int cx = x + w / 2; const int cy = y + h / 2; const int ex = x + w - 1; const int ey = y + h - 1; const quint32 toDraw = LineChars[code]; //Top _lines: if ((toDraw & TopL) != 0u) { paint.drawLine(cx - 1, y, cx - 1, cy - 2); } if ((toDraw & TopC) != 0u) { paint.drawLine(cx, y, cx, cy - 2); } if ((toDraw & TopR) != 0u) { paint.drawLine(cx + 1, y, cx + 1, cy - 2); } //Bot _lines: if ((toDraw & BotL) != 0u) { paint.drawLine(cx - 1, cy + 2, cx - 1, ey); } if ((toDraw & BotC) != 0u) { paint.drawLine(cx, cy + 2, cx, ey); } if ((toDraw & BotR) != 0u) { paint.drawLine(cx + 1, cy + 2, cx + 1, ey); } //Left _lines: if ((toDraw & LeftT) != 0u) { paint.drawLine(x, cy - 1, cx - 2, cy - 1); } if ((toDraw & LeftC) != 0u) { paint.drawLine(x, cy, cx - 2, cy); } if ((toDraw & LeftB) != 0u) { paint.drawLine(x, cy + 1, cx - 2, cy + 1); } //Right _lines: if ((toDraw & RightT) != 0u) { paint.drawLine(cx + 2, cy - 1, ex, cy - 1); } if ((toDraw & RightC) != 0u) { paint.drawLine(cx + 2, cy, ex, cy); } if ((toDraw & RightB) != 0u) { paint.drawLine(cx + 2, cy + 1, ex, cy + 1); } //Intersection points. if ((toDraw & Int11) != 0u) { paint.drawPoint(cx - 1, cy - 1); } if ((toDraw & Int12) != 0u) { paint.drawPoint(cx, cy - 1); } if ((toDraw & Int13) != 0u) { paint.drawPoint(cx + 1, cy - 1); } if ((toDraw & Int21) != 0u) { paint.drawPoint(cx - 1, cy); } if ((toDraw & Int22) != 0u) { paint.drawPoint(cx, cy); } if ((toDraw & Int23) != 0u) { paint.drawPoint(cx + 1, cy); } if ((toDraw & Int31) != 0u) { paint.drawPoint(cx - 1, cy + 1); } if ((toDraw & Int32) != 0u) { paint.drawPoint(cx, cy + 1); } if ((toDraw & Int33) != 0u) { paint.drawPoint(cx + 1, cy + 1); } } static void drawOtherChar(QPainter& paint, int x, int y, int w, int h, uchar code) { //Calculate cell midpoints, end points. const int cx = x + w / 2; const int cy = y + h / 2; const int ex = x + w - 1; const int ey = y + h - 1; // Double dashes if (0x4C <= code && code <= 0x4F) { const int xHalfGap = qMax(w / 15, 1); const int yHalfGap = qMax(h / 15, 1); switch (code) { case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1); paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1); paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1); paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1); // No break! #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); #endif case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL paint.drawLine(x, cy, cx - xHalfGap - 1, cy); paint.drawLine(cx + xHalfGap, cy, ex, cy); break; case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1); paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1); paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey); paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey); // No break! #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) Q_FALLTHROUGH(); #endif case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL paint.drawLine(cx, y, cx, cy - yHalfGap - 1); paint.drawLine(cx, cy + yHalfGap, cx, ey); break; } } // Rounded corner characters else if (0x6D <= code && code <= 0x70) { const int r = w * 3 / 8; const int d = 2 * r; switch (code) { case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT paint.drawLine(cx, cy + r, cx, ey); paint.drawLine(cx + r, cy, ex, cy); paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16); break; case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT paint.drawLine(cx, cy + r, cx, ey); paint.drawLine(x, cy, cx - r, cy); paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16); break; case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT paint.drawLine(cx, y, cx, cy - r); paint.drawLine(x, cy, cx - r, cy); paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16); break; case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT paint.drawLine(cx, y, cx, cy - r); paint.drawLine(cx + r, cy, ex, cy); paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16); break; } } // Diagonals else if (0x71 <= code && code <= 0x73) { switch (code) { case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT paint.drawLine(ex, y, x, ey); break; case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT paint.drawLine(x, y, ex, ey); break; case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS paint.drawLine(ex, y, x, ey); paint.drawLine(x, y, ex, ey); break; } } } void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str, const Character* attributes) { const QPen& originalPen = painter.pen(); if (((attributes->rendition & RE_BOLD) != 0) && _boldIntense) { QPen boldPen(originalPen); boldPen.setWidth(3); painter.setPen(boldPen); } for (int i = 0 ; i < str.length(); i++) { const uchar code = str[i].cell(); if (LineChars[code] != 0u) { drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code); } else { drawOtherChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code); } } painter.setPen(originalPen); } void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape) { _cursorShape = shape; } Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const { return _cursorShape; } 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() { if (sessionController() != nullptr) { Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(sessionController()->session()); if (currentProfile != nullptr) { Enum::CursorShapeEnum shape = static_cast(currentProfile->property(Profile::CursorShape)); setKeyboardCursorShape(shape); setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled()); } } } void TerminalDisplay::setKeyboardCursorColor(const QColor& color) { _cursorColor = color; } QColor TerminalDisplay::keyboardCursorColor() const { return _cursorColor; } void TerminalDisplay::setOpacity(qreal opacity) { QColor color(_blendColor); color.setAlphaF(opacity); _opacity = opacity; // enable automatic background filling to prevent the display // flickering if there is no transparency /*if ( color.alpha() == 255 ) { setAutoFillBackground(true); } else { setAutoFillBackground(false); }*/ _blendColor = color.rgba(); updateScrollBarPalette(); } void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p) { _wallpaper = p; } void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting) { // the area of the widget showing the contents of the terminal display is drawn // using the background color from the color scheme set with setColorTable() // // the area of the widget behind the scroll-bar is drawn using the background // brush from the scroll-bar's palette, to give the effect of the scroll-bar // being outside of the terminal display and visual consistency with other KDE // applications. if (useOpacitySetting && !_wallpaper->isNull() && _wallpaper->draw(painter, rect, _opacity)) { } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) { #if defined(Q_OS_MACOS) // TODO - On MacOS, using CompositionMode doesn't work. Altering the // transparency in the color scheme alters the brightness. painter.fillRect(rect, backgroundColor); #else QColor color(backgroundColor); color.setAlpha(qAlpha(_blendColor)); painter.save(); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(rect, color); painter.restore(); #endif } else { painter.fillRect(rect, backgroundColor); } } void TerminalDisplay::drawCursor(QPainter& painter, const QRect& rect, const QColor& foregroundColor, const QColor& /*backgroundColor*/, bool& invertCharacterColor) { // don't draw cursor which is currently blinking if (_cursorBlinking) { return; } // shift rectangle top down one pixel to leave some space // between top and bottom QRect cursorRect = rect.adjusted(0, 1, 0, 0); QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor; painter.setPen(cursorColor); if (_cursorShape == Enum::BlockCursor) { // draw the cursor outline, adjusting the area so that // it is draw entirely inside 'rect' int penWidth = qMax(1, painter.pen().width()); painter.drawRect(cursorRect.adjusted(penWidth / 2, penWidth / 2, - penWidth / 2 - penWidth % 2, - penWidth / 2 - penWidth % 2)); // draw the cursor body only when the widget has focus if (hasFocus()) { painter.fillRect(cursorRect, cursorColor); if (!_cursorColor.isValid()) { // invert the color used to draw the text to ensure that the character at // the cursor position is readable invertCharacterColor = true; } } } else if (_cursorShape == Enum::UnderlineCursor) { painter.drawLine(cursorRect.left(), cursorRect.bottom(), cursorRect.right(), cursorRect.bottom()); } else if (_cursorShape == Enum::IBeamCursor) { painter.drawLine(cursorRect.left(), cursorRect.top(), cursorRect.left(), cursorRect.bottom()); } } void TerminalDisplay::drawCharacters(QPainter& painter, const QRect& rect, const QString& text, const Character* style, bool invertCharacterColor) { // don't draw text which is currently blinking if (_textBlinking && ((style->rendition & RE_BLINK) != 0)) { return; } // don't draw concealed characters if ((style->rendition & RE_CONCEAL) != 0) { return; } // setup bold and underline bool useBold = (((style->rendition & RE_BOLD) != 0) && _boldIntense) || font().bold(); const bool useUnderline = ((style->rendition & RE_UNDERLINE) != 0) || font().underline(); const bool useItalic = ((style->rendition & RE_ITALIC) != 0) || font().italic(); const bool useStrikeOut = ((style->rendition & RE_STRIKEOUT) != 0) || font().strikeOut(); const bool useOverline = ((style->rendition & RE_OVERLINE) != 0) || font().overline(); QFont font = painter.font(); if (font.bold() != useBold || font.underline() != useUnderline || font.italic() != useItalic || font.strikeOut() != useStrikeOut || font.overline() != useOverline) { font.setBold(useBold); font.setUnderline(useUnderline); font.setItalic(useItalic); font.setStrikeOut(useStrikeOut); font.setOverline(useOverline); painter.setFont(font); } // setup pen const CharacterColor& textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor); const QColor color = textColor.color(_colorTable); QPen pen = painter.pen(); if (pen.color() != color) { pen.setColor(color); painter.setPen(color); } // draw text if (isLineCharString(text) && !_useFontLineCharacters) { drawLineCharString(painter, rect.x(), rect.y(), text, style); } else { // Force using LTR as the document layout for the terminal area, because // there is no use cases for RTL emulator and RTL terminal application. // // This still allows RTL characters to be rendered in the RTL way. painter.setLayoutDirection(Qt::LeftToRight); painter.setClipRect(rect); if (_bidiEnabled) { painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text); } else { painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text); } painter.setClipping(false); } } void TerminalDisplay::drawTextFragment(QPainter& painter , const QRect& rect, const QString& text, const Character* style) { painter.save(); // setup painter const QColor foregroundColor = style->foregroundColor.color(_colorTable); const QColor backgroundColor = style->backgroundColor.color(_colorTable); // draw background if different from the display's background color if (backgroundColor != palette().background().color()) { drawBackground(painter, rect, backgroundColor, false /* do not use transparency */); } // draw cursor shape if the current character is the cursor // this may alter the foreground and background colors bool invertCharacterColor = false; if ((style->rendition & RE_CURSOR) != 0) { drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor); } // draw text drawCharacters(painter, rect, text, style, invertCharacterColor); painter.restore(); } void TerminalDisplay::drawPrinterFriendlyTextFragment(QPainter& painter, const QRect& rect, const QString& text, const Character* style) { painter.save(); // Set the colors used to draw to black foreground and white // background for printer friendly output when printing Character print_style = *style; print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000); print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF); // draw text drawCharacters(painter, rect, text, &print_style, false); painter.restore(); } void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; } uint TerminalDisplay::randomSeed() const { return _randomSeed; } // scrolls the image by 'lines', down if lines > 0 or up otherwise. // // the terminal emulation keeps track of the scrolling of the character // image as it receives input, and when the view is updated, it calls scrollImage() // with the final scroll amount. this improves performance because scrolling the // display is much cheaper than re-rendering all the text for the // part of the image which has moved up or down. // Instead only new lines have to be drawn void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion) { // return if there is nothing to do if ((lines == 0) || (_image == nullptr)) { return; } // 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()) { return; } if ((_readOnlyMessageWidget != nullptr) && _readOnlyMessageWidget->isVisible()) { return; } // constrain the region to the display // the bottom of the region is capped to the number of lines in the display's // internal image - 2, so that the height of 'region' is strictly less // than the height of the internal image. QRect region = screenWindowRegion; region.setBottom(qMin(region.bottom(), _lines - 2)); // return if there is nothing to do if (!region.isValid() || (region.top() + abs(lines)) >= region.bottom() || _lines <= region.height()) { return; } // hide terminal size label to prevent it being scrolled if ((_resizeWidget != nullptr) && _resizeWidget->isVisible()) { _resizeWidget->hide(); } // Note: With Qt 4.4 the left edge of the scrolled area must be at 0 // to get the correct (newly exposed) part of the widget repainted. // // The right edge must be before the left edge of the scroll bar to // avoid triggering a repaint of the entire widget, the distance is // given by SCROLLBAR_CONTENT_GAP // // Set the QT_FLUSH_PAINT environment variable to '1' before starting the // application to monitor repainting. // const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width(); const int SCROLLBAR_CONTENT_GAP = 1; QRect scrollRect; if (_scrollbarLocation == Enum::ScrollBarLeft) { scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP); scrollRect.setRight(width()); } else { scrollRect.setLeft(0); scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); } void* firstCharPos = &_image[ region.top() * _columns ]; void* lastCharPos = &_image[(region.top() + abs(lines)) * _columns ]; const int top = _contentRect.top() + (region.top() * _fontHeight); const int linesToMove = region.height() - abs(lines); const int bytesToMove = linesToMove * _columns * sizeof(Character); Q_ASSERT(linesToMove > 0); Q_ASSERT(bytesToMove > 0); //scroll internal image if (lines > 0) { // check that the memory areas that we are going to move are valid Q_ASSERT((char*)lastCharPos + bytesToMove < (char*)(_image + (_lines * _columns))); Q_ASSERT((lines * _columns) < _imageSize); //scroll internal image down memmove(firstCharPos , lastCharPos , bytesToMove); //set region of display to scroll scrollRect.setTop(top); } else { // check that the memory areas that we are going to move are valid Q_ASSERT((char*)firstCharPos + bytesToMove < (char*)(_image + (_lines * _columns))); //scroll internal image up memmove(lastCharPos , firstCharPos , bytesToMove); //set region of the display to scroll scrollRect.setTop(top + abs(lines) * _fontHeight); } scrollRect.setHeight(linesToMove * _fontHeight); Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty()); //scroll the display vertically to match internal _image scroll(0 , _fontHeight * (-lines) , scrollRect); } QRegion TerminalDisplay::hotSpotRegion() const { QRegion region; foreach(Filter::HotSpot * hotSpot , _filterChain->hotSpots()) { QRect r; if (hotSpot->startLine() == hotSpot->endLine()) { r.setLeft(hotSpot->startColumn()); r.setTop(hotSpot->startLine()); r.setRight(hotSpot->endColumn()); r.setBottom(hotSpot->endLine()); region |= imageToWidget(r); } else { r.setLeft(hotSpot->startColumn()); r.setTop(hotSpot->startLine()); r.setRight(_columns); r.setBottom(hotSpot->startLine()); region |= imageToWidget(r); for (int line = hotSpot->startLine() + 1 ; line < hotSpot->endLine() ; line++) { r.setLeft(0); r.setTop(line); r.setRight(_columns); r.setBottom(line); region |= imageToWidget(r); } r.setLeft(0); r.setTop(hotSpot->endLine()); r.setRight(hotSpot->endColumn()); r.setBottom(hotSpot->endLine()); region |= imageToWidget(r); } } return region; } void TerminalDisplay::processFilters() { if (_screenWindow.isNull()) { return; } if (!_filterUpdateRequired) { return; } QRegion preUpdateHotSpots = hotSpotRegion(); // 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 // updateImage() is called on the display and therefore _image is // out of date at this point _filterChain->setImage(_screenWindow->getImage(), _screenWindow->windowLines(), _screenWindow->windowColumns(), _screenWindow->getLineProperties()); _filterChain->process(); QRegion postUpdateHotSpots = hotSpotRegion(); update(preUpdateHotSpots | postUpdateHotSpots); _filterUpdateRequired = false; } void TerminalDisplay::updateImage() { if (_screenWindow.isNull()) { return; } // 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 // disable this shortcut for transparent konsole with scaled pixels, otherwise we get rendering artefacts, see BUG 350651 if (!(WindowSystemInfo::HAVE_TRANSPARENCY && (qApp->devicePixelRatio() > 1.0)) && _wallpaper->isNull()) { scrollImage(_screenWindow->scrollCount() , _screenWindow->scrollRegion()); _screenWindow->resetScrollCount(); } if (_image == nullptr) { // Create _image. // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first. updateImageSize(); } Character* const newimg = _screenWindow->getImage(); const int lines = _screenWindow->windowLines(); const int columns = _screenWindow->windowColumns(); setScroll(_screenWindow->currentLine() , _screenWindow->lineCount()); Q_ASSERT(_usedLines <= _lines); Q_ASSERT(_usedColumns <= _columns); int y, x, len; const QPoint tL = contentsRect().topLeft(); const int tLx = tL.x(); const int tLy = tL.y(); _hasTextBlinker = false; CharacterColor cf; // undefined const int linesToUpdate = qMin(_lines, qMax(0, lines)); const int columnsToUpdate = qMin(_columns, qMax(0, columns)); auto dirtyMask = new char[columnsToUpdate + 2]; QRegion dirtyRegion; // 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; for (y = 0; y < linesToUpdate; ++y) { const Character* currentLine = &_image[y * _columns]; const Character* const newLine = &newimg[y * columns]; bool updateLine = false; // The dirty mask indicates which characters need repainting. We also // mark surrounding neighbors dirty, in case the character exceeds // its cell boundaries memset(dirtyMask, 0, columnsToUpdate + 2); for (x = 0 ; x < columnsToUpdate ; ++x) { if (newLine[x] != currentLine[x]) { dirtyMask[x] = 1; } } if (!_resizing) { // not while _resizing, we're expecting a paintEvent 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. if (dirtyMask[x] != 0) { if (newLine[x + 0].character == 0u) { continue; } const bool lineDraw = newLine[x + 0].isLineChar(); const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0); const RenditionFlags cr = newLine[x].rendition; const CharacterColor clipboard = newLine[x].backgroundColor; if (newLine[x].foregroundColor != cf) { cf = newLine[x].foregroundColor; } const int lln = columnsToUpdate - x; for (len = 1; len < lln; ++len) { const Character& ch = newLine[x + len]; if (ch.character == 0u) { continue; // Skip trailing part of multi-col chars. } const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character == 0); if (ch.foregroundColor != cf || ch.backgroundColor != clipboard || (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) || (dirtyMask[x + len] == 0) || ch.isLineChar() != lineDraw || nextIsDoubleWidth != doubleWidth) { break; } } const bool saveFixedFont = _fixedFont; if (lineDraw) { _fixedFont = false; } if (doubleWidth) { _fixedFont = false; } updateLine = true; _fixedFont = saveFixedFont; x += len - 1; } } } //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. if (_lineProperties.count() > y) { updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT); } // 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 QRect dirtyRect = QRect(_contentRect.left() + tLx , _contentRect.top() + tLy + _fontHeight * y , _fontWidth * columnsToUpdate , _fontHeight); dirtyRegion |= dirtyRect; } // 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)); } // if the new _image is smaller than the previous _image, then ensure that the area // outside the new _image is cleared if (linesToUpdate < _usedLines) { dirtyRegion |= QRect(_contentRect.left() + tLx , _contentRect.top() + tLy + _fontHeight * linesToUpdate , _fontWidth * _columns , _fontHeight * (_usedLines - linesToUpdate)); } _usedLines = linesToUpdate; if (columnsToUpdate < _usedColumns) { dirtyRegion |= QRect(_contentRect.left() + tLx + columnsToUpdate * _fontWidth , _contentRect.top() + tLy , _fontWidth * (_usedColumns - columnsToUpdate) , _fontHeight * _lines); } _usedColumns = columnsToUpdate; dirtyRegion |= _inputMethodData.previousPreeditRect; // update the parts of the display which have changed update(dirtyRegion); if (_allowBlinkingText && _hasTextBlinker && !_blinkTextTimer->isActive()) { _blinkTextTimer->start(); } if (!_hasTextBlinker && _blinkTextTimer->isActive()) { _blinkTextTimer->stop(); _textBlinking = false; } delete[] dirtyMask; #ifndef QT_NO_ACCESSIBILITY QAccessibleEvent dataChangeEvent(this, QAccessible::VisibleDataChanged); QAccessible::updateAccessibility(&dataChangeEvent); QAccessibleTextCursorEvent cursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX()); QAccessible::updateAccessibility(&cursorEvent); #endif } void TerminalDisplay::showResizeNotification() { if (_showTerminalSizeHint && isVisible()) { if (_resizeWidget == nullptr) { _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this); _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("Size: XXX x XXX"))); _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height()); _resizeWidget->setAlignment(Qt::AlignCenter); _resizeWidget->setStyleSheet(QStringLiteral("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)")); _resizeTimer = new QTimer(this); _resizeTimer->setInterval(SIZE_HINT_DURATION); _resizeTimer->setSingleShot(true); connect(_resizeTimer, &QTimer::timeout, _resizeWidget, &QLabel::hide); } QString sizeStr = i18n("Size: %1 x %2", _columns, _lines); _resizeWidget->setText(sizeStr); _resizeWidget->move((width() - _resizeWidget->width()) / 2, (height() - _resizeWidget->height()) / 2 + 20); _resizeWidget->show(); _resizeTimer->start(); } } void TerminalDisplay::paintEvent(QPaintEvent* pe) { QPainter paint(this); foreach(const QRect & rect, (pe->region() & contentsRect()).rects()) { drawBackground(paint, rect, palette().background().color(), true /* use opacity setting */); drawContents(paint, rect); } drawCurrentResultRect(paint); drawInputMethodPreeditString(paint, preeditRect()); paintFilters(paint); } void TerminalDisplay::printContent(QPainter& painter, bool friendly) { // Reinitialize the font with the printers paint device so the font // measurement calculations will be done correctly QFont savedFont = getVTFont(); QFont font(savedFont, painter.device()); painter.setFont(font); setVTFont(font); QRect rect(0, 0, size().width(), size().height()); _printerFriendly = friendly; if (!friendly) { drawBackground(painter, rect, getBackgroundColor(), true /* use opacity setting */); } drawContents(painter, rect); _printerFriendly = false; setVTFont(savedFont); } QPoint TerminalDisplay::cursorPosition() const { if (!_screenWindow.isNull()) { return _screenWindow->cursorPosition(); } else { return QPoint(0, 0); } } inline bool TerminalDisplay::isCursorOnDisplay() const { return cursorPosition().x() < _columns && cursorPosition().y() < _lines; } FilterChain* TerminalDisplay::filterChain() const { return _filterChain; } void TerminalDisplay::paintFilters(QPainter& painter) { if (_filterUpdateRequired) { return; } // get color of character under mouse and use it to draw // lines for filters QPoint cursorPos = mapFromGlobal(QCursor::pos()); int cursorLine; int cursorColumn; getCharacterPosition(cursorPos, cursorLine, cursorColumn, false); Character cursorCharacter = _image[loc(qMin(cursorColumn, _columns - 1), cursorLine)]; painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable()))); // iterate over hotspots identified by the display's currently active filters // and draw appropriate visuals to indicate the presence of the hotspot int urlNumber = 0; QList spots = _filterChain->hotSpots(); foreach(Filter::HotSpot* spot, spots) { urlNumber++; QRegion region; if (spot->type() == Filter::HotSpot::Link) { QRect r; if (spot->startLine() == spot->endLine()) { r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(), spot->startLine()*_fontHeight + _contentRect.top(), (spot->endColumn())*_fontWidth + _contentRect.left() - 1, (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1); region |= r; } else { r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(), spot->startLine()*_fontHeight + _contentRect.top(), (_columns)*_fontWidth + _contentRect.left() - 1, (spot->startLine() + 1)*_fontHeight + _contentRect.top() - 1); region |= r; for (int line = spot->startLine() + 1 ; line < spot->endLine() ; line++) { r.setCoords(0 * _fontWidth + _contentRect.left(), line * _fontHeight + _contentRect.top(), (_columns)*_fontWidth + _contentRect.left() - 1, (line + 1)*_fontHeight + _contentRect.top() - 1); region |= r; } r.setCoords(0 * _fontWidth + _contentRect.left(), spot->endLine()*_fontHeight + _contentRect.top(), (spot->endColumn())*_fontWidth + _contentRect.left() - 1, (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1); region |= r; } if (_showUrlHint && urlNumber < 10) { // Position at the beginning of the URL const QVector regionRects = region.rects(); QRect hintRect(regionRects.first()); hintRect.setWidth(r.height()); painter.fillRect(hintRect, QColor(0, 0, 0, 128)); painter.setPen(Qt::white); painter.drawRect(hintRect.adjusted(0, 0, -1, -1)); painter.drawText(hintRect, Qt::AlignCenter, QString::number(urlNumber)); } } for (int line = spot->startLine() ; line <= spot->endLine() ; line++) { int startColumn = 0; int endColumn = _columns - 1; // TODO use number of _columns which are actually // occupied on this line rather than the width of the // display in _columns // Check image size so _image[] is valid (see makeImage) if (endColumn >= _columns || line >= _lines) { break; } // ignore whitespace at the end of the lines while (_image[loc(endColumn, line)].isSpace() && endColumn > 0) { endColumn--; } // increment here because the column which we want to set 'endColumn' to // is the first whitespace character at the end of the line endColumn++; if (line == spot->startLine()) { startColumn = spot->startColumn(); } if (line == spot->endLine()) { endColumn = spot->endColumn(); } // TODO: resolve this comment with the new margin/center code // subtract one pixel from // the right and bottom so that // we do not overdraw adjacent // hotspots // // subtracting one pixel from all sides also prevents an edge case where // moving the mouse outside a link could still leave it underlined // because the check below for the position of the cursor // finds it on the border of the target area QRect r; r.setCoords(startColumn * _fontWidth + _contentRect.left(), line * _fontHeight + _contentRect.top(), endColumn * _fontWidth + _contentRect.left() - 1, (line + 1)*_fontHeight + _contentRect.top() - 1); // Underline link hotspots if (spot->type() == Filter::HotSpot::Link) { QFontMetrics metrics(font()); // find the baseline (which is the invisible line that the characters in the font sit on, // with some having tails dangling below) const int baseline = r.bottom() - metrics.descent(); // find the position of the underline below that const int underlinePos = baseline + metrics.underlinePos(); if (_showUrlHint || region.contains(mapFromGlobal(QCursor::pos()))) { painter.drawLine(r.left() , underlinePos , r.right() , underlinePos); } // Marker hotspots simply have a transparent rectangular shape // drawn on top of them } else if (spot->type() == Filter::HotSpot::Marker) { //TODO - Do not use a hardcoded color for this const bool isCurrentResultLine = (_screenWindow->currentResultLine() == (spot->startLine() + _screenWindow->currentLine())); QColor color = isCurrentResultLine ? QColor(255, 255, 0, 120) : QColor(255, 0, 0, 120); painter.fillRect(r, color); } } } } inline static bool isRtl(const Character &chr) { uint c = 0; if ((chr.rendition & RE_EXTENDED_CHAR) == 0) { c = chr.character; } else { ushort extendedCharLength = 0; const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(chr.character, extendedCharLength); if (chars != nullptr) { c = chars[0]; } } switch(QChar::direction(c)) { case QChar::DirR: case QChar::DirAL: case QChar::DirRLE: case QChar::DirRLI: case QChar::DirRLO: return true; default: return false; } } void TerminalDisplay::drawContents(QPainter& paint, const QRect& rect) { const QPoint tL = contentsRect().topLeft(); const int tLx = tL.x(); const int tLy = tL.y(); const int lux = qMin(_usedColumns - 1, qMax(0, (rect.left() - tLx - _contentRect.left()) / _fontWidth)); const int luy = qMin(_usedLines - 1, qMax(0, (rect.top() - tLy - _contentRect.top()) / _fontHeight)); const int rlx = qMin(_usedColumns - 1, qMax(0, (rect.right() - tLx - _contentRect.left()) / _fontWidth)); const int rly = qMin(_usedLines - 1, qMax(0, (rect.bottom() - tLy - _contentRect.top()) / _fontHeight)); const int numberOfColumns = _usedColumns; QVector univec; univec.reserve(numberOfColumns); for (int y = luy; y <= rly; y++) { int x = lux; if ((_image[loc(lux, y)].character == 0u) && (x != 0)) { x--; // Search for start of multi-column character } for (; x <= rlx; x++) { int len = 1; int p = 0; // reset our buffer to the number of columns int bufferSize = numberOfColumns; univec.resize(bufferSize); uint *disstrU = univec.data(); // is this a single character or a sequence of characters ? if ((_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) != 0) { // sequence of characters ushort extendedCharLength = 0; const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].character, extendedCharLength); if (chars != nullptr) { Q_ASSERT(extendedCharLength > 1); bufferSize += extendedCharLength - 1; univec.resize(bufferSize); disstrU = univec.data(); for (int index = 0 ; index < extendedCharLength ; index++) { Q_ASSERT(p < bufferSize); disstrU[p++] = chars[index]; } } } else { // single character const uint c = _image[loc(x, y)].character; if (c != 0u) { Q_ASSERT(p < bufferSize); disstrU[p++] = c; } } const bool lineDraw = _image[loc(x, y)].isLineChar(); const bool doubleWidth = (_image[qMin(loc(x, y) + 1, _imageSize - 1)].character == 0); const CharacterColor currentForeground = _image[loc(x, y)].foregroundColor; const CharacterColor currentBackground = _image[loc(x, y)].backgroundColor; const RenditionFlags currentRendition = _image[loc(x, y)].rendition; const bool rtl = isRtl(_image[loc(x, y)]); if(_image[loc(x, y)].character <= 0x7e || rtl) { while (x + len <= rlx && _image[loc(x + len, y)].foregroundColor == currentForeground && _image[loc(x + len, y)].backgroundColor == currentBackground && (_image[loc(x + len, y)].rendition & ~RE_EXTENDED_CHAR) == (currentRendition & ~RE_EXTENDED_CHAR) && (_image[qMin(loc(x + len, y) + 1, _imageSize - 1)].character == 0) == doubleWidth && _image[loc(x + len, y)].isLineChar() == lineDraw && (_image[loc(x + len, y)].character <= 0x7e || rtl)) { const uint c = _image[loc(x + len, y)].character; if ((_image[loc(x + len, y)].rendition & RE_EXTENDED_CHAR) != 0) { // sequence of characters ushort extendedCharLength = 0; const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength); if (chars != nullptr) { Q_ASSERT(extendedCharLength > 1); bufferSize += extendedCharLength - 1; univec.resize(bufferSize); disstrU = univec.data(); for (int index = 0 ; index < extendedCharLength ; index++) { Q_ASSERT(p < bufferSize); disstrU[p++] = chars[index]; } } } else { // single character if (c != 0u) { Q_ASSERT(p < bufferSize); disstrU[p++] = c; } } if (doubleWidth) { // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition len++; // Skip trailing part of multi-column character } len++; } } if ((x + len < _usedColumns) && (_image[loc(x + len, y)].character == 0u)) { len++; // Adjust for trailing part of multi-column character } const bool save__fixedFont = _fixedFont; if (lineDraw) { _fixedFont = false; } if (doubleWidth) { _fixedFont = false; } univec.resize(p); // Create a text scaling matrix for double width and double height lines. QMatrix textScale; if (y < _lineProperties.size()) { if ((_lineProperties[y] & LINE_DOUBLEWIDTH) != 0) { textScale.scale(2, 1); } if ((_lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) { textScale.scale(1, 2); } } //Apply text scaling matrix. paint.setWorldMatrix(textScale, true); //calculate the area in which the text will be drawn QRect textArea = QRect(_contentRect.left() + tLx + _fontWidth * x , _contentRect.top() + tLy + _fontHeight * y , _fontWidth * len , _fontHeight); //move the calculated area to take account of scaling applied to the painter. //the position of the area from the origin (0,0) is scaled //by the opposite of whatever //transformation has been applied to the painter. this ensures that //painting does actually start from textArea.topLeft() //(instead of textArea.topLeft() * painter-scale) textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft())); QString unistr = QString::fromUcs4(univec.data(), univec.length()); //paint text fragment if (_printerFriendly) { drawPrinterFriendlyTextFragment(paint, textArea, unistr, &_image[loc(x, y)]); } else { drawTextFragment(paint, textArea, unistr, &_image[loc(x, y)]); } _fixedFont = save__fixedFont; //reset back to single-width, single-height _lines paint.setWorldMatrix(textScale.inverted(), true); if (y < _lineProperties.size() - 1) { //double-height _lines are represented by two adjacent _lines //containing the same characters //both _lines will have the LINE_DOUBLEHEIGHT attribute. //If the current line has the LINE_DOUBLEHEIGHT attribute, //we can therefore skip the next line if ((_lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) { y++; } } x += len - 1; } } } void TerminalDisplay::drawCurrentResultRect(QPainter& painter) { if(_screenWindow->currentResultLine() == -1) { return; } QRect r(0, _contentRect.top() + (_screenWindow->currentResultLine() - _screenWindow->currentLine()) * _fontHeight, contentsRect().width(), _fontHeight); painter.fillRect(r, QColor(0, 0, 255, 80)); } QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const { QRect result; result.setLeft(_contentRect.left() + _fontWidth * imageArea.left()); result.setTop(_contentRect.top() + _fontHeight * imageArea.top()); result.setWidth(_fontWidth * imageArea.width()); result.setHeight(_fontHeight * imageArea.height()); return result; } /* ------------------------------------------------------------------------- */ /* */ /* Blinking Text & Cursor */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::setBlinkingCursorEnabled(bool blink) { _allowBlinkingCursor = blink; if (blink && !_blinkCursorTimer->isActive()) { _blinkCursorTimer->start(); } if (!blink && _blinkCursorTimer->isActive()) { _blinkCursorTimer->stop(); if (_cursorBlinking) { // if cursor is blinking(hidden), blink it again to make it show blinkCursorEvent(); } Q_ASSERT(!_cursorBlinking); } } void TerminalDisplay::setBlinkingTextEnabled(bool blink) { _allowBlinkingText = blink; if (blink && !_blinkTextTimer->isActive()) { _blinkTextTimer->start(); } if (!blink && _blinkTextTimer->isActive()) { _blinkTextTimer->stop(); _textBlinking = false; } } void TerminalDisplay::focusOutEvent(QFocusEvent*) { // trigger a repaint of the cursor so that it is both: // // * visible (in case it was hidden during blinking) // * drawn in a focused out state _cursorBlinking = false; updateCursor(); // suppress further cursor blinking _blinkCursorTimer->stop(); Q_ASSERT(!_cursorBlinking); // if text is blinking (hidden), blink it again to make it shown if (_textBlinking) { blinkTextEvent(); } // suppress further text blinking _blinkTextTimer->stop(); Q_ASSERT(!_textBlinking); _showUrlHint = false; emit focusLost(); } void TerminalDisplay::focusInEvent(QFocusEvent*) { if (_allowBlinkingCursor) { _blinkCursorTimer->start(); } updateCursor(); if (_allowBlinkingText && _hasTextBlinker) { _blinkTextTimer->start(); } emit focusGained(); } void TerminalDisplay::blinkTextEvent() { Q_ASSERT(_allowBlinkingText); _textBlinking = !_textBlinking; // TODO: Optimize to only repaint the areas of the widget where there is // blinking text rather than repainting the whole widget. update(); } void TerminalDisplay::blinkCursorEvent() { Q_ASSERT(_allowBlinkingCursor); _cursorBlinking = !_cursorBlinking; updateCursor(); } void TerminalDisplay::updateCursor() { if (!isCursorOnDisplay()){ return; } const int cursorLocation = loc(cursorPosition().x(), cursorPosition().y()); Q_ASSERT(cursorLocation < _imageSize); int charWidth = konsole_wcwidth(_image[cursorLocation].character); QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(charWidth, 1))); update(cursorRect); } /* ------------------------------------------------------------------------- */ /* */ /* Geometry & Resizing */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::resizeEvent(QResizeEvent*) { if (contentsRect().isValid()) { updateImageSize(); } } void TerminalDisplay::propagateSize() { if (_image != nullptr) { updateImageSize(); } } void TerminalDisplay::updateImageSize() { Character* oldImage = _image; const int oldLines = _lines; const int oldColumns = _columns; makeImage(); if (oldImage != nullptr) { // copy the old image to reduce flicker int lines = qMin(oldLines, _lines); int columns = qMin(oldColumns, _columns); for (int line = 0; line < lines; line++) { memcpy((void*)&_image[_columns * line], (void*)&oldImage[oldColumns * line], columns * sizeof(Character)); } delete[] oldImage; } if (!_screenWindow.isNull()) { _screenWindow->setWindowLines(_lines); } _resizing = (oldLines != _lines) || (oldColumns != _columns); if (_resizing) { showResizeNotification(); emit changedContentSizeSignal(_contentRect.height(), _contentRect.width()); // expose resizeEvent } _resizing = false; } void TerminalDisplay::makeImage() { _wallpaper->load(); calcGeometry(); // confirm that array will be of non-zero size, since the painting code // assumes a non-zero array length Q_ASSERT(_lines > 0 && _columns > 0); Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns); _imageSize = _lines * _columns; _image = new Character[_imageSize]; clearImage(); } void TerminalDisplay::clearImage() { for (int i = 0; i < _imageSize; ++i) { _image[i] = Screen::DefaultChar; } } void TerminalDisplay::calcGeometry() { _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height()); _contentRect = contentsRect().adjusted(_margin, _margin, -_margin, -_margin); switch (_scrollbarLocation) { case Enum::ScrollBarHidden : break; case Enum::ScrollBarLeft : _contentRect.setLeft(_contentRect.left() + _scrollBar->width()); _scrollBar->move(contentsRect().topLeft()); break; case Enum::ScrollBarRight: _contentRect.setRight(_contentRect.right() - _scrollBar->width()); _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0)); break; } // ensure that display is always at least one column wide _columns = qMax(1, _contentRect.width() / _fontWidth); _usedColumns = qMin(_usedColumns, _columns); // ensure that display is always at least one line high _lines = qMax(1, _contentRect.height() / _fontHeight); _usedLines = qMin(_usedLines, _lines); if(_centerContents) { QSize unusedPixels = _contentRect.size() - QSize(_columns * _fontWidth, _lines * _fontHeight); _contentRect.adjust(unusedPixels.width() / 2, unusedPixels.height() / 2, 0, 0); } } // calculate the needed size, this must be synced with calcGeometry() void TerminalDisplay::setSize(int columns, int lines) { const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width(); const int horizontalMargin = _margin * 2; const int verticalMargin = _margin * 2; QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth) , verticalMargin + (lines * _fontHeight)); if (newSize != size()) { _size = newSize; updateGeometry(); } } QSize TerminalDisplay::sizeHint() const { return _size; } //showEvent and hideEvent are reimplemented here so that it appears to other classes that the //display has been resized when the display is hidden or shown. // //TODO: Perhaps it would be better to have separate signals for show and hide instead of using //the same signal as the one for a content size change void TerminalDisplay::showEvent(QShowEvent*) { emit changedContentSizeSignal(_contentRect.height(), _contentRect.width()); } void TerminalDisplay::hideEvent(QHideEvent*) { emit changedContentSizeSignal(_contentRect.height(), _contentRect.width()); } void TerminalDisplay::setMargin(int margin) { if (margin < 0) { margin = 0; } _margin = margin; updateImageSize(); } void TerminalDisplay::setCenterContents(bool enable) { _centerContents = enable; calcGeometry(); update(); } /* ------------------------------------------------------------------------- */ /* */ /* Scrollbar */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::setScrollBarPosition(Enum::ScrollBarPositionEnum position) { if (_scrollbarLocation == position) { return; } if (position == Enum::ScrollBarHidden) { _scrollBar->hide(); } else { _scrollBar->show(); } _scrollbarLocation = position; propagateSize(); update(); } void TerminalDisplay::scrollBarPositionChanged(int) { if (_screenWindow.isNull()) { return; } _screenWindow->scrollTo(_scrollBar->value()); // if the thumb has been moved to the bottom of the _scrollBar then set // the display to automatically track new output, // that is, scroll down automatically // to how new _lines as they are added const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum()); _screenWindow->setTrackOutput(atEndOfOutput); updateImage(); } void TerminalDisplay::setScroll(int cursor, int slines) { // update _scrollBar if the range or value has changed, // otherwise return // // setting the range or value of a _scrollBar will always trigger // a repaint, so it should be avoided if it is not necessary if (_scrollBar->minimum() == 0 && _scrollBar->maximum() == (slines - _lines) && _scrollBar->value() == cursor) { return; } disconnect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged); _scrollBar->setRange(0, slines - _lines); _scrollBar->setSingleStep(1); _scrollBar->setPageStep(_lines); _scrollBar->setValue(cursor); connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged); } void TerminalDisplay::setScrollFullPage(bool fullPage) { _scrollFullPage = fullPage; } bool TerminalDisplay::scrollFullPage() const { return _scrollFullPage; } /* ------------------------------------------------------------------------- */ /* */ /* Mouse */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::mousePressEvent(QMouseEvent* ev) { if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) { mouseTripleClickEvent(ev); return; } if (!contentsRect().contains(ev->pos())) { return; } if (_screenWindow.isNull()) { return; } // Ignore clicks on the message widget if (_readOnlyMessageWidget != nullptr) { if (_readOnlyMessageWidget->isVisible() && _readOnlyMessageWidget->frameGeometry().contains(ev->pos())) { return; } } if (_outputSuspendedMessageWidget != nullptr) { if (_outputSuspendedMessageWidget->isVisible() && _outputSuspendedMessageWidget->frameGeometry().contains(ev->pos())) { return; } } int charLine; int charColumn; getCharacterPosition(ev->pos(), charLine, charColumn); QPoint pos = QPoint(charColumn, charLine); if (ev->button() == Qt::LeftButton) { // request the software keyboard, if any if (qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel( style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { QEvent event(QEvent::RequestSoftwareInputPanel); QApplication::sendEvent(this, &event); } } _lineSelectionMode = false; _wordSelectionMode = false; // The user clicked inside selected text bool selected = _screenWindow->isSelected(pos.x(), pos.y()); // Drag only when the Control key is held if ((!_ctrlRequiredForDrag || ((ev->modifiers() & Qt::ControlModifier) != 0u)) && selected) { _dragInfo.state = diPending; _dragInfo.start = ev->pos(); } else { // No reason to ever start a drag event _dragInfo.state = diNone; _preserveLineBreaks = !(((ev->modifiers() & Qt::ControlModifier) != 0u) && !(ev->modifiers() & Qt::AltModifier)); _columnSelectionMode = ((ev->modifiers() & Qt::AltModifier) != 0u) && ((ev->modifiers() & Qt::ControlModifier) != 0u); - if (_mouseMarks || (ev->modifiers() == Qt::ShiftModifier)) { + if (!_usesMouseTracking || (ev->modifiers() == Qt::ShiftModifier)) { // Only extend selection for programs not interested in mouse - if (_mouseMarks && (ev->modifiers() == Qt::ShiftModifier)) { + if (!_usesMouseTracking && (ev->modifiers() == Qt::ShiftModifier)) { extendSelection(ev->pos()); } else { _screenWindow->clearSelection(); pos.ry() += _scrollBar->value(); _iPntSel = _pntSel = pos; _actSel = 1; // left mouse button pressed but nothing selected yet. } } else { if(!_readOnly) { emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0); } } if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u))) { Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn); if ((spot != nullptr) && spot->type() == Filter::HotSpot::Link) { QObject action; action.setObjectName(QStringLiteral("open-action")); spot->activate(&action); } } } } else if (ev->button() == Qt::MidButton) { processMidButtonClick(ev); } else if (ev->button() == Qt::RightButton) { - if (_mouseMarks || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) { + if (!_usesMouseTracking || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) { emit configureRequest(ev->pos()); } else { if(!_readOnly) { emit mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0); } } } } QList TerminalDisplay::filterActions(const QPoint& position) { int charLine, charColumn; getCharacterPosition(position, charLine, charColumn, false); Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn); return spot != nullptr ? spot->actions() : QList(); } void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev) { int charLine = 0; int charColumn = 0; getCharacterPosition(ev->pos(), charLine, charColumn); processFilters(); // handle filters // change link hot-spot appearance on mouse-over Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn); if ((spot != nullptr) && spot->type() == Filter::HotSpot::Link) { QRegion previousHotspotArea = _mouseOverHotspotArea; _mouseOverHotspotArea = QRegion(); QRect r; if (spot->startLine() == spot->endLine()) { r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(), spot->startLine()*_fontHeight + _contentRect.top(), (spot->endColumn())*_fontWidth + _contentRect.left() - 1, (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1); _mouseOverHotspotArea |= r; } else { r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(), spot->startLine()*_fontHeight + _contentRect.top(), (_columns)*_fontWidth + _contentRect.left() - 1, (spot->startLine() + 1)*_fontHeight + _contentRect.top() - 1); _mouseOverHotspotArea |= r; for (int line = spot->startLine() + 1 ; line < spot->endLine() ; line++) { r.setCoords(0 * _fontWidth + _contentRect.left(), line * _fontHeight + _contentRect.top(), (_columns)*_fontWidth + _contentRect.left() - 1, (line + 1)*_fontHeight + _contentRect.top() - 1); _mouseOverHotspotArea |= r; } r.setCoords(0 * _fontWidth + _contentRect.left(), spot->endLine()*_fontHeight + _contentRect.top(), (spot->endColumn())*_fontWidth + _contentRect.left() - 1, (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1); _mouseOverHotspotArea |= r; } if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u)) && (cursor().shape() != Qt::PointingHandCursor)) { setCursor(Qt::PointingHandCursor); } update(_mouseOverHotspotArea | previousHotspotArea); } else if (!_mouseOverHotspotArea.isEmpty()) { if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u)) || (cursor().shape() == Qt::PointingHandCursor)) { - setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor); + setCursor(_usesMouseTracking ? Qt::ArrowCursor : Qt::IBeamCursor); } update(_mouseOverHotspotArea); // set hotspot area to an invalid rectangle _mouseOverHotspotArea = QRegion(); } // for auto-hiding the cursor, we need mouseTracking if (ev->buttons() == Qt::NoButton) { return; } - // if the terminal is interested in mouse movements - // then emit a mouse movement signal, unless the shift - // key is being held down, which overrides this. - if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) { + // if the program running in the terminal is interested in Mouse Tracking + // evnets then emit a mouse movement signal, unless the shift key is + // being held down, which overrides this. + if (_usesMouseTracking && !(ev->modifiers() & Qt::ShiftModifier)) { int button = 3; if ((ev->buttons() & Qt::LeftButton) != 0u) { button = 0; } if ((ev->buttons() & Qt::MidButton) != 0u) { button = 1; } if ((ev->buttons() & Qt::RightButton) != 0u) { button = 2; } emit mouseSignal(button, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 1); return; } if (_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm const int distance = QApplication::startDragDistance(); if (ev->x() > _dragInfo.start.x() + distance || ev->x() < _dragInfo.start.x() - distance || ev->y() > _dragInfo.start.y() + distance || ev->y() < _dragInfo.start.y() - distance) { // we've left the drag square, we can start a real drag operation now _screenWindow->clearSelection(); doDrag(); } return; } else if (_dragInfo.state == diDragging) { // this isn't technically needed because mouseMoveEvent is suppressed during // Qt drag operations, replaced by dragMoveEvent return; } if (_actSel == 0) { return; } // don't extend selection while pasting if ((ev->buttons() & Qt::MidButton) != 0u) { return; } extendSelection(ev->pos()); } void TerminalDisplay::leaveEvent(QEvent *) { // remove underline from an active link when cursor leaves the widget area if(!_mouseOverHotspotArea.isEmpty()) { update(_mouseOverHotspotArea); _mouseOverHotspotArea = QRegion(); } } void TerminalDisplay::extendSelection(const QPoint& position) { if (_screenWindow.isNull()) { return; } //if ( !contentsRect().contains(ev->pos()) ) return; const QPoint tL = contentsRect().topLeft(); const int tLx = tL.x(); const int tLy = tL.y(); const int scroll = _scrollBar->value(); // we're in the process of moving the mouse with the left button pressed // the mouse cursor will kept caught within the bounds of the text in // this widget. int linesBeyondWidget = 0; QRect textBounds(tLx + _contentRect.left(), tLy + _contentRect.top(), _usedColumns * _fontWidth - 1, _usedLines * _fontHeight - 1); QPoint pos = position; // Adjust position within text area bounds. const QPoint oldpos = pos; pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right())); pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom())); if (oldpos.y() > textBounds.bottom()) { linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / _fontHeight; _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward } if (oldpos.y() < textBounds.top()) { linesBeyondWidget = (textBounds.top() - oldpos.y()) / _fontHeight; _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history } int charColumn = 0; int charLine = 0; getCharacterPosition(pos, charLine, charColumn); QPoint here = QPoint(charColumn, charLine); QPoint ohere; QPoint _iPntSelCorr = _iPntSel; _iPntSelCorr.ry() -= _scrollBar->value(); QPoint _pntSelCorr = _pntSel; _pntSelCorr.ry() -= _scrollBar->value(); bool swapping = false; if (_wordSelectionMode) { // Extend to word boundaries int i; QChar selClass; const bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x())); const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x())); swapping = left_not_right != old_left_not_right; // Find left (left_not_right ? from here : from start) QPoint left = left_not_right ? here : _iPntSelCorr; i = loc(qBound(0, left.x(), _columns - 1), qBound(0, left.y(), _lines - 1)); if (i >= 0 && i < _imageSize) { selClass = charClass(_image[qMin(i, _imageSize - 1)]); while (((left.x() > 0) || (left.y() > 0 && ((_lineProperties[left.y() - 1] & LINE_WRAPPED) != 0))) && charClass(_image[i - 1]) == selClass) { i--; if (left.x() > 0) { left.rx()--; } else { left.rx() = _usedColumns - 1; left.ry()--; } } } // Find left (left_not_right ? from start : from here) QPoint right = left_not_right ? _iPntSelCorr : here; i = loc(qBound(0, right.x(), _columns - 1), qBound(0, right.y(), _lines - 1)); if (i >= 0 && i < _imageSize) { selClass = charClass(_image[qMin(i, _imageSize - 1)]); while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && ((_lineProperties[right.y()] & LINE_WRAPPED) != 0))) && charClass(_image[i + 1]) == selClass) { i++; if (right.x() < _usedColumns - 1) { right.rx()++; } else { right.rx() = 0; right.ry()++; } } } // Pick which is start (ohere) and which is extension (here) if (left_not_right) { here = left; ohere = right; } else { here = right; ohere = left; } ohere.rx()++; } if (_lineSelectionMode) { // Extend to complete line const bool above_not_below = (here.y() < _iPntSelCorr.y()); if (above_not_below) { ohere = findLineEnd(_iPntSelCorr); here = findLineStart(here); } else { ohere = findLineStart(_iPntSelCorr); here = findLineEnd(here); } swapping = !(_tripleSelBegin == ohere); _tripleSelBegin = ohere; ohere.rx()++; } int offset = 0; if (!_wordSelectionMode && !_lineSelectionMode) { QChar selClass; const bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x())); const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x())); swapping = left_not_right != old_left_not_right; // Find left (left_not_right ? from here : from start) const QPoint left = left_not_right ? here : _iPntSelCorr; // Find left (left_not_right ? from start : from here) QPoint right = left_not_right ? _iPntSelCorr : here; if (right.x() > 0 && !_columnSelectionMode) { if (right.x() - 1 < _columns && right.y() < _lines) { selClass = charClass(_image[loc(right.x() - 1, right.y())]); } } // Pick which is start (ohere) and which is extension (here) if (left_not_right) { here = left; ohere = right; offset = 0; } else { here = right; ohere = left; offset = -1; } } if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) { return; // not moved } if (here == ohere) { return; // It's not left, it's not right. } if (_actSel < 2 || swapping) { if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { _screenWindow->setSelectionStart(ohere.x() , ohere.y() , true); } else { _screenWindow->setSelectionStart(ohere.x() - 1 - offset , ohere.y() , false); } } _actSel = 2; // within selection _pntSel = here; _pntSel.ry() += _scrollBar->value(); if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { _screenWindow->setSelectionEnd(here.x() , here.y()); } else { _screenWindow->setSelectionEnd(here.x() + offset , here.y()); } } void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev) { if (_screenWindow.isNull()) { return; } int charLine; int charColumn; getCharacterPosition(ev->pos(), charLine, charColumn); if (ev->button() == Qt::LeftButton) { if (_dragInfo.state == diPending) { // We had a drag event pending but never confirmed. Kill selection _screenWindow->clearSelection(); } else { if (_actSel > 1) { copyToX11Selection(); } _actSel = 0; //FIXME: emits a release event even if the mouse is // outside the range. The procedure used in `mouseMoveEvent' // applies here, too. - if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) { + if (_usesMouseTracking && !(ev->modifiers() & Qt::ShiftModifier)) { emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 2); } } _dragInfo.state = diNone; } - if (!_mouseMarks && + if (_usesMouseTracking && (ev->button() == Qt::RightButton || ev->button() == Qt::MidButton) && !(ev->modifiers() & Qt::ShiftModifier)) { emit mouseSignal(ev->button() == Qt::MidButton ? 1 : 2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 2); } } void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint, int& line, int& column, bool edge) const { // the column value returned can be equal to _usedColumns (when edge == true), // which is the position just after the last character displayed in a line. // // this is required so that the user can select characters in the right-most // column (or left-most for right-to-left input) const int columnMax = edge ? _usedColumns : _usedColumns - 1; const int xOffset = edge ? _fontWidth / 2 : 0; column = qBound(0, (widgetPoint.x() + xOffset - contentsRect().left() - _contentRect.left()) / _fontWidth, columnMax); line = qBound(0, (widgetPoint.y() - contentsRect().top() - _contentRect.top()) / _fontHeight, _usedLines - 1); } void TerminalDisplay::updateLineProperties() { if (_screenWindow.isNull()) { return; } _lineProperties = _screenWindow->getLineProperties(); } void TerminalDisplay::processMidButtonClick(QMouseEvent* ev) { - if (_mouseMarks || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) { + if (!_usesMouseTracking || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) { const bool appendEnter = (ev->modifiers() & Qt::ControlModifier) != 0u; if (_middleClickPasteMode == Enum::PasteFromX11Selection) { pasteFromX11Selection(appendEnter); } else if (_middleClickPasteMode == Enum::PasteFromClipboard) { pasteFromClipboard(appendEnter); } else { Q_ASSERT(false); } } else { if(!_readOnly) { int charLine = 0; int charColumn = 0; getCharacterPosition(ev->pos(), charLine, charColumn); emit mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0); } } } void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev) { // Yes, successive middle click can trigger this event if (ev->button() == Qt::MidButton) { processMidButtonClick(ev); return; } if (ev->button() != Qt::LeftButton) { return; } if (_screenWindow.isNull()) { return; } int charLine = 0; int charColumn = 0; getCharacterPosition(ev->pos(), charLine, charColumn); QPoint pos(qMin(charColumn, _columns - 1), qMin(charLine, _lines - 1)); // pass on double click as two clicks. - if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) { + if (_usesMouseTracking && !(ev->modifiers() & Qt::ShiftModifier)) { if(!_readOnly) { // Send just _ONE_ click event, since the first click of the double click // was already sent by the click handler emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0); // left button } return; } _screenWindow->clearSelection(); QPoint bgnSel = pos; QPoint endSel = pos; int i = loc(bgnSel.x(), bgnSel.y()); _iPntSel = bgnSel; _iPntSel.ry() += _scrollBar->value(); _wordSelectionMode = true; _actSel = 2; // within selection // find word boundaries... const QChar selClass = charClass(_image[i]); { // find the start of the word int x = bgnSel.x(); while (((x > 0) || (bgnSel.y() > 0 && ((_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED) != 0))) && charClass(_image[i - 1]) == selClass) { i--; if (x > 0) { x--; } else { x = _usedColumns - 1; bgnSel.ry()--; } } bgnSel.setX(x); _screenWindow->setSelectionStart(bgnSel.x() , bgnSel.y() , false); // find the end of the word i = loc(endSel.x(), endSel.y()); x = endSel.x(); while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && ((_lineProperties[endSel.y()] & LINE_WRAPPED) != 0))) && charClass(_image[i + 1]) == selClass) { i++; if (x < _usedColumns - 1) { x++; } else { x = 0; endSel.ry()++; } } endSel.setX(x); // In word selection mode don't select @ (64) if at end of word. if (((_image[i].rendition & RE_EXTENDED_CHAR) == 0) && (QChar(_image[i].character) == QLatin1Char('@')) && ((endSel.x() - bgnSel.x()) > 0)) { endSel.setX(x - 1); } _actSel = 2; // within selection _screenWindow->setSelectionEnd(endSel.x() , endSel.y()); copyToX11Selection(); } _possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), [this]() { _possibleTripleClick = false; }); } void TerminalDisplay::wheelEvent(QWheelEvent* ev) { // Only vertical scrolling is supported if (ev->orientation() != Qt::Vertical) { return; } const int modifiers = ev->modifiers(); // ctrl+ for zooming, like in konqueror and firefox if (((modifiers & Qt::ControlModifier) != 0u) && mouseWheelZoom()) { _scrollWheelState.addWheelEvent(ev); int steps = _scrollWheelState.consumeLegacySteps(ScrollState::DEFAULT_ANGLE_SCROLL_LINE); for (;steps > 0; --steps) { // wheel-up for increasing font size increaseFontSize(); } for (;steps < 0; ++steps) { // wheel-down for decreasing font size decreaseFontSize(); } - } else if (_mouseMarks && (_scrollBar->maximum() > 0)) { - // If the program running in the terminal is not interested in mouse events, - // send the event to the scrollbar if the slider has room to move + } else if (!_usesMouseTracking && (_scrollBar->maximum() > 0)) { + // If the program running in the terminal is not interested in Mouse + // Tracking events, send the event to the scrollbar if the slider + // has room to move _scrollWheelState.addWheelEvent(ev); _scrollBar->event(ev); _sessionController->setSearchStartToWindowCurrentLine(); _scrollWheelState.clearAll(); } else if (!_readOnly) { _scrollWheelState.addWheelEvent(ev); - if(_mouseMarks && !_isPrimaryScreen && _alternateScrolling) { + if(!_usesMouseTracking && !_isPrimaryScreen && _alternateScrolling) { // Send simulated up / down key presses to the terminal program // for the benefit of programs such as 'less' (which use the alternate screen) // assume that each Up / Down key event will cause the terminal application // to scroll by one line. // // to get a reasonable scrolling speed, scroll by one line for every 5 degrees // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees, // giving a scroll of 3 lines const int lines = _scrollWheelState.consumeSteps(static_cast(_fontHeight * qApp->devicePixelRatio()), ScrollState::degreesToAngle(5)); const int keyCode = lines > 0 ? Qt::Key_Up : Qt::Key_Down; QKeyEvent keyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier); for (int i = 0; i < abs(lines); i++) { _screenWindow->screen()->setCurrentTerminalDisplay(this); emit keyPressedSignal(&keyEvent); } - } else if (!_mouseMarks) { + } else if (_usesMouseTracking) { // terminal program wants notification of mouse activity int charLine; int charColumn; getCharacterPosition(ev->pos() , charLine , charColumn); const int steps = _scrollWheelState.consumeLegacySteps(ScrollState::DEFAULT_ANGLE_SCROLL_LINE); const int button = (steps > 0) ? 4 : 5; for (int i = 0; i < abs(steps); ++i) { emit mouseSignal(button, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0); } } } } void TerminalDisplay::viewScrolledByUser() { _sessionController->setSearchStartToWindowCurrentLine(); } /* Moving left/up from the line containing pnt, return the starting offset point which the given line is continiously wrapped (top left corner = 0,0; previous line not visible = 0,-1). */ QPoint TerminalDisplay::findLineStart(const QPoint &pnt) { const int visibleScreenLines = _lineProperties.size(); const int topVisibleLine = _screenWindow->currentLine(); Screen *screen = _screenWindow->screen(); int line = pnt.y(); int lineInHistory= line + topVisibleLine; QVector lineProperties = _lineProperties; while (lineInHistory > 0) { for (; line > 0; line--, lineInHistory--) { // Does previous line wrap around? if ((lineProperties[line - 1] & LINE_WRAPPED) == 0) { return QPoint(0, lineInHistory - topVisibleLine); } } if (lineInHistory < 1) { break; } // _lineProperties is only for the visible screen, so grab new data int newRegionStart = qMax(0, lineInHistory - visibleScreenLines); lineProperties = screen->getLineProperties(newRegionStart, lineInHistory - 1); line = lineInHistory - newRegionStart; } return QPoint(0, lineInHistory - topVisibleLine); } /* Moving right/down from the line containing pnt, return the ending offset point which the given line is continiously wrapped. */ QPoint TerminalDisplay::findLineEnd(const QPoint &pnt) { const int visibleScreenLines = _lineProperties.size(); const int topVisibleLine = _screenWindow->currentLine(); const int maxY = _screenWindow->lineCount() - 1; Screen *screen = _screenWindow->screen(); int line = pnt.y(); int lineInHistory= line + topVisibleLine; QVector lineProperties = _lineProperties; while (lineInHistory < maxY) { for (; line < lineProperties.count() && lineInHistory < maxY; line++, lineInHistory++) { // Does current line wrap around? if ((lineProperties[line] & LINE_WRAPPED) == 0) { return QPoint(_columns - 1, lineInHistory - topVisibleLine); } } line = 0; lineProperties = screen->getLineProperties(lineInHistory, qMin(lineInHistory + visibleScreenLines, maxY)); } return QPoint(_columns - 1, lineInHistory - topVisibleLine); } QPoint TerminalDisplay::findWordStart(const QPoint &pnt) { const int regSize = qMax(_screenWindow->windowLines(), 10); const int curLine = _screenWindow->currentLine(); int i = pnt.y(); int x = pnt.x(); int y = i + curLine; int j = loc(x, i); QVector lineProperties = _lineProperties; Screen *screen = _screenWindow->screen(); Character *image = _image; Character *tmp_image = nullptr; const QChar selClass = charClass(image[j]); const int imageSize = regSize * _columns; while (true) { for (;;j--, x--) { if (x > 0) { if (charClass(image[j - 1]) == selClass) { continue; } goto out; } else if (i > 0) { if (((lineProperties[i - 1] & LINE_WRAPPED) != 0) && charClass(image[j - 1]) == selClass) { x = _columns; i--; y--; continue; } goto out; } else if (y > 0) { break; } else { goto out; } } int newRegStart = qMax(0, y - regSize); lineProperties = screen->getLineProperties(newRegStart, y - 1); i = y - newRegStart; if (tmp_image == nullptr) { tmp_image = new Character[imageSize]; image = tmp_image; } screen->getImage(tmp_image, imageSize, newRegStart, y - 1); j = loc(x, i); } out: if (tmp_image != nullptr) { delete[] tmp_image; } return QPoint(x, y - curLine); } QPoint TerminalDisplay::findWordEnd(const QPoint &pnt) { const int regSize = qMax(_screenWindow->windowLines(), 10); const int curLine = _screenWindow->currentLine(); int i = pnt.y(); int x = pnt.x(); int y = i + curLine; int j = loc(x, i); QVector lineProperties = _lineProperties; Screen *screen = _screenWindow->screen(); Character *image = _image; Character *tmp_image = nullptr; const QChar selClass = charClass(image[j]); const int imageSize = regSize * _columns; const int maxY = _screenWindow->lineCount() - 1; const int maxX = _columns - 1; while (true) { const int lineCount = lineProperties.count(); for (;;j++, x++) { if (x < maxX) { if (charClass(image[j + 1]) == selClass) { continue; } goto out; } else if (i < lineCount - 1) { if (((lineProperties[i] & LINE_WRAPPED) != 0) && charClass(image[j + 1]) == selClass) { x = -1; i++; y++; continue; } goto out; } else if (y < maxY) { if (i < lineCount && ((lineProperties[i] & LINE_WRAPPED) == 0)) { goto out; } break; } else { goto out; } } int newRegEnd = qMin(y + regSize - 1, maxY); lineProperties = screen->getLineProperties(y, newRegEnd); i = 0; if (tmp_image == nullptr) { tmp_image = new Character[imageSize]; image = tmp_image; } screen->getImage(tmp_image, imageSize, y, newRegEnd); x--; j = loc(x, i); } out: y -= curLine; // In word selection mode don't select @ (64) if at end of word. if (((image[j].rendition & RE_EXTENDED_CHAR) == 0) && (QChar(image[j].character) == QLatin1Char('@')) && (y > pnt.y() || x > pnt.x())) { if (x > 0) { x--; } else { y--; } } if (tmp_image != nullptr) { delete[] tmp_image; } return QPoint(x, y); } Screen::DecodingOptions TerminalDisplay::currentDecodingOptions() { Screen::DecodingOptions decodingOptions; if (_preserveLineBreaks) { decodingOptions |= Screen::PreserveLineBreaks; } if (_trimLeadingSpaces) { decodingOptions |= Screen::TrimLeadingWhitespace; } if (_trimTrailingSpaces) { decodingOptions |= Screen::TrimTrailingWhitespace; } return decodingOptions; } void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev) { if (_screenWindow.isNull()) { return; } int charLine; int charColumn; getCharacterPosition(ev->pos(), charLine, charColumn); selectLine(QPoint(charColumn, charLine), _tripleClickMode == Enum::SelectWholeLine); } void TerminalDisplay::selectLine(QPoint pos, bool entireLine) { _iPntSel = pos; _screenWindow->clearSelection(); _lineSelectionMode = true; _wordSelectionMode = false; _actSel = 2; // within selection if (!entireLine) { // Select from cursor to end of line _tripleSelBegin = findWordStart(_iPntSel); _screenWindow->setSelectionStart(_tripleSelBegin.x(), _tripleSelBegin.y() , false); } else { _tripleSelBegin = findLineStart(_iPntSel); _screenWindow->setSelectionStart(0 , _tripleSelBegin.y() , false); } _iPntSel = findLineEnd(_iPntSel); _screenWindow->setSelectionEnd(_iPntSel.x() , _iPntSel.y()); copyToX11Selection(); _iPntSel.ry() += _scrollBar->value(); } void TerminalDisplay::selectCurrentLine() { if (_screenWindow.isNull()) { return; } selectLine(cursorPosition(), true); } void TerminalDisplay::selectAll() { if (_screenWindow.isNull()) { return; } _preserveLineBreaks = true; _screenWindow->setSelectionByLineRange(0, _screenWindow->lineCount()); copyToX11Selection(); } bool TerminalDisplay::focusNextPrevChild(bool next) { // for 'Tab', always disable focus switching among widgets // for 'Shift+Tab', leave the decision to higher level if (next) { return false; } else { return QWidget::focusNextPrevChild(next); } } QChar TerminalDisplay::charClass(const Character& ch) const { if ((ch.rendition & RE_EXTENDED_CHAR) != 0) { ushort extendedCharLength = 0; const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength); if ((chars != nullptr) && extendedCharLength > 0) { const QString s = QString::fromUcs4(chars, extendedCharLength); if (_wordCharacters.contains(s, Qt::CaseInsensitive)) { return QLatin1Char('a'); } bool letterOrNumber = false; for (int i = 0; !letterOrNumber && i < s.size(); ++i) { letterOrNumber = s.at(i).isLetterOrNumber(); } return letterOrNumber ? QLatin1Char('a') : s.at(0); } return 0; } else { const QChar qch(ch.character); if (qch.isSpace()) { return QLatin1Char(' '); } if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive)) { return QLatin1Char('a'); } return qch; } } void TerminalDisplay::setWordCharacters(const QString& wc) { _wordCharacters = wc; } -// FIXME: the actual value of _mouseMarks is the opposite of its semantic. -// When using programs not interested with mouse(shell, less), it is true. -// When using programs interested with mouse(vim,mc), it is false. -void TerminalDisplay::setUsesMouse(bool on) +void TerminalDisplay::setUsesMouseTracking(bool on) { - _mouseMarks = on; - setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor); + _usesMouseTracking = on; + setCursor(_usesMouseTracking ? Qt::ArrowCursor : Qt::IBeamCursor); } -bool TerminalDisplay::usesMouse() const +bool TerminalDisplay::usesMouseTracking() const { - return _mouseMarks; + return _usesMouseTracking; } void TerminalDisplay::setAlternateScrolling(bool enable) { _alternateScrolling = enable; } bool TerminalDisplay::alternateScrolling() const { return _alternateScrolling; } void TerminalDisplay::usingPrimaryScreen(bool use) { _isPrimaryScreen = use; } void TerminalDisplay::setBracketedPasteMode(bool on) { _bracketedPasteMode = on; } bool TerminalDisplay::bracketedPasteMode() const { return _bracketedPasteMode; } /* ------------------------------------------------------------------------- */ /* */ /* Clipboard */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::doPaste(QString text, bool appendReturn) { if (_screenWindow.isNull()) { return; } if (_readOnly) { return; } if (appendReturn) { text.append(QLatin1String("\r")); } if (text.length() > 8000) { if (KMessageBox::warningContinueCancel(window(), i18np("Are you sure you want to paste %1 character?", "Are you sure you want to paste %1 characters?", text.length()), i18n("Confirm Paste"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("ShowPasteHugeTextWarning")) == KMessageBox::Cancel) { return; } } if (!text.isEmpty()) { text.replace(QLatin1Char('\n'), QLatin1Char('\r')); if (bracketedPasteMode()) { text.remove(QLatin1String("\033")); text.prepend(QLatin1String("\033[200~")); text.append(QLatin1String("\033[201~")); } // perform paste by simulating keypress events QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text); emit keyPressedSignal(&e); } } void TerminalDisplay::setAutoCopySelectedText(bool enabled) { _autoCopySelectedText = enabled; } void TerminalDisplay::setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum mode) { _middleClickPasteMode = mode; } void TerminalDisplay::setCopyTextAsHTML(bool enabled) { _copyTextAsHTML = enabled; } void TerminalDisplay::copyToX11Selection() { if (_screenWindow.isNull()) { return; } const QString &text = _screenWindow->selectedText(currentDecodingOptions()); if (text.isEmpty()) { return; } auto mimeData = new QMimeData; mimeData->setText(text); if (_copyTextAsHTML) { mimeData->setHtml(_screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml)); } if (QApplication::clipboard()->supportsSelection()) { QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection); } if (_autoCopySelectedText) { QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } } void TerminalDisplay::copyToClipboard() { if (_screenWindow.isNull()) { return; } const QString &text = _screenWindow->selectedText(currentDecodingOptions()); if (text.isEmpty()) { return; } auto mimeData = new QMimeData; mimeData->setText(text); if (_copyTextAsHTML) { mimeData->setHtml(_screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml)); } QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); } void TerminalDisplay::pasteFromClipboard(bool appendEnter) { QString text = QApplication::clipboard()->text(QClipboard::Clipboard); doPaste(text, appendEnter); } void TerminalDisplay::pasteFromX11Selection(bool appendEnter) { if (QApplication::clipboard()->supportsSelection()) { QString text = QApplication::clipboard()->text(QClipboard::Selection); doPaste(text, appendEnter); } } /* ------------------------------------------------------------------------- */ /* */ /* Input Method */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::inputMethodEvent(QInputMethodEvent* event) { if (!event->commitString().isEmpty()) { QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString()); emit keyPressedSignal(&keyEvent); } if (!_readOnly && isCursorOnDisplay()) { _inputMethodData.preeditString = event->preeditString(); update(preeditRect() | _inputMethodData.previousPreeditRect); } event->accept(); } QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const { const QPoint cursorPos = cursorPosition(); switch (query) { case Qt::ImMicroFocus: return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1)); case Qt::ImFont: return font(); case Qt::ImCursorPosition: // return the cursor position within the current line return cursorPos.x(); case Qt::ImSurroundingText: { // return the text from the current line QString lineText; QTextStream stream(&lineText); PlainTextDecoder decoder; decoder.begin(&stream); if (isCursorOnDisplay()) { decoder.decodeLine(&_image[loc(0, cursorPos.y())], _usedColumns, LINE_DEFAULT); } decoder.end(); return lineText; } case Qt::ImCurrentSelection: return QString(); default: break; } return QVariant(); } QRect TerminalDisplay::preeditRect() const { const int preeditLength = string_width(_inputMethodData.preeditString); if (preeditLength == 0) { return QRect(); } const QRect stringRect(_contentRect.left() + _fontWidth * cursorPosition().x(), _contentRect.top() + _fontHeight * cursorPosition().y(), _fontWidth * preeditLength, _fontHeight); return stringRect.intersected(_contentRect); } void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect) { if (_inputMethodData.preeditString.isEmpty() || !isCursorOnDisplay()) { return; } const QPoint cursorPos = cursorPosition(); bool invertColors = false; const QColor background = _colorTable[DEFAULT_BACK_COLOR]; const QColor foreground = _colorTable[DEFAULT_FORE_COLOR]; const Character* style = &_image[loc(cursorPos.x(), cursorPos.y())]; drawBackground(painter, rect, background, true); drawCursor(painter, rect, foreground, background, invertColors); drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors); _inputMethodData.previousPreeditRect = rect; } /* ------------------------------------------------------------------------- */ /* */ /* Keyboard */ /* */ /* ------------------------------------------------------------------------- */ void TerminalDisplay::setFlowControlWarningEnabled(bool enable) { _flowControlWarningEnabled = enable; // if the dialog is currently visible and the flow control warning has // been disabled then hide the dialog if (!enable) { outputSuspended(false); } } void TerminalDisplay::outputSuspended(bool suspended) { //create the label when this function is first called if (_outputSuspendedMessageWidget == nullptr) { //This label includes a link to an English language website //describing the 'flow control' (Xon/Xoff) feature found in almost //all terminal emulators. //If there isn't a suitable article available in the target language the link //can simply be removed. _outputSuspendedMessageWidget = createMessageWidget(i18n("Output has been " "suspended" " by pressing Ctrl+S." " Press Ctrl+Q to resume.")); connect(_outputSuspendedMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &url) { QDesktopServices::openUrl(QUrl(url)); }); _outputSuspendedMessageWidget->setMessageType(KMessageWidget::Warning); } suspended ? _outputSuspendedMessageWidget->animatedShow() : _outputSuspendedMessageWidget->animatedHide(); } void TerminalDisplay::dismissOutputSuspendedMessage() { outputSuspended(false); } KMessageWidget* TerminalDisplay::createMessageWidget(const QString &text) { auto widget = new KMessageWidget(text); widget->setWordWrap(true); widget->setFocusProxy(this); widget->setCursor(Qt::ArrowCursor); _verticalLayout->insertWidget(0, widget); return widget; } void TerminalDisplay::updateReadOnlyState(bool readonly) { if (_readOnly == readonly) { return; } if (readonly) { // Lazy create the readonly messagewidget if (_readOnlyMessageWidget == nullptr) { _readOnlyMessageWidget = createMessageWidget(i18n("This terminal is read-only.")); _readOnlyMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); } } if (_readOnlyMessageWidget != nullptr) { readonly ? _readOnlyMessageWidget->animatedShow() : _readOnlyMessageWidget->animatedHide(); } _readOnly = readonly; } void TerminalDisplay::scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount) { _screenWindow->scrollBy(mode, amount, _scrollFullPage); _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput()); updateLineProperties(); updateImage(); viewScrolledByUser(); } void TerminalDisplay::keyPressEvent(QKeyEvent* event) { if ((_urlHintsModifiers != 0u) && event->modifiers() == _urlHintsModifiers) { int hintSelected = event->key() - 0x31; if (hintSelected >= 0 && hintSelected < 10 && hintSelected < _filterChain->hotSpots().count()) { _filterChain->hotSpots().at(hintSelected)->activate(); _showUrlHint = false; update(); return; } if (!_showUrlHint) { processFilters(); _showUrlHint = true; update(); } } _screenWindow->screen()->setCurrentTerminalDisplay(this); if (!_readOnly) { _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't // know where the current selection is. if (_allowBlinkingCursor) { _blinkCursorTimer->start(); if (_cursorBlinking) { // if cursor is blinking(hidden), blink it again to show it blinkCursorEvent(); } Q_ASSERT(!_cursorBlinking); } } emit keyPressedSignal(event); #ifndef QT_NO_ACCESSIBILITY if (!_readOnly) { QAccessibleTextCursorEvent textCursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX()); QAccessible::updateAccessibility(&textCursorEvent); } #endif event->accept(); } void TerminalDisplay::keyReleaseEvent(QKeyEvent *event) { if (_showUrlHint) { _showUrlHint = false; update(); } if (_readOnly) { event->accept(); return; } QWidget::keyReleaseEvent(event); } bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent) { const int modifiers = keyEvent->modifiers(); // When a possible shortcut combination is pressed, // emit the overrideShortcutCheck() signal to allow the host // to decide whether the terminal should override it or not. if (modifiers != Qt::NoModifier) { int modifierCount = 0; unsigned int currentModifier = Qt::ShiftModifier; while (currentModifier <= Qt::KeypadModifier) { if ((modifiers & currentModifier) != 0u) { modifierCount++; } currentModifier <<= 1; } if (modifierCount < 2) { bool override = false; emit overrideShortcutCheck(keyEvent, override); if (override) { keyEvent->accept(); return true; } } } // Override any of the following shortcuts because // they are needed by the terminal int keyCode = keyEvent->key() | modifiers; switch (keyCode) { // list is taken from the QLineEdit::event() code case Qt::Key_Tab: case Qt::Key_Delete: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_Backspace: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Slash: case Qt::Key_Period: case Qt::Key_Space: keyEvent->accept(); return true; } return false; } bool TerminalDisplay::event(QEvent* event) { bool eventHandled = false; switch (event->type()) { case QEvent::ShortcutOverride: eventHandled = handleShortcutOverrideEvent(static_cast(event)); break; case QEvent::PaletteChange: case QEvent::ApplicationPaletteChange: _scrollBar->setPalette(QApplication::palette()); break; default: break; } return eventHandled ? true : QWidget::event(event); } void TerminalDisplay::contextMenuEvent(QContextMenuEvent* event) { // the logic for the mouse case is within MousePressEvent() if (event->reason() != QContextMenuEvent::Mouse) { emit configureRequest(mapFromGlobal(QCursor::pos())); } } /* --------------------------------------------------------------------- */ /* */ /* Bell */ /* */ /* --------------------------------------------------------------------- */ void TerminalDisplay::setBellMode(int mode) { _bellMode = mode; } int TerminalDisplay::bellMode() const { return _bellMode; } void TerminalDisplay::bell(const QString& message) { if (_bellMasked) { return; } switch (_bellMode) { case Enum::SystemBeepBell: KNotification::beep(); break; case Enum::NotifyBell: // STABLE API: // Please note that these event names, "BellVisible" and "BellInvisible", // should not change and should be kept stable, because other applications // that use this code via KPart rely on these names for notifications. KNotification::event(hasFocus() ? QStringLiteral("BellVisible") : QStringLiteral("BellInvisible"), message, QPixmap(), this); break; case Enum::VisualBell: visualBell(); break; default: break; } // limit the rate at which bells can occur. // ...mainly for sound effects where rapid bells in sequence // produce a horrible noise. _bellMasked = true; QTimer::singleShot(500, [this]() { _bellMasked = false; }); } void TerminalDisplay::visualBell() { swapFGBGColors(); QTimer::singleShot(200, this, &Konsole::TerminalDisplay::swapFGBGColors); } void TerminalDisplay::swapFGBGColors() { // swap the default foreground & background color ColorEntry color = _colorTable[DEFAULT_BACK_COLOR]; _colorTable[DEFAULT_BACK_COLOR] = _colorTable[DEFAULT_FORE_COLOR]; _colorTable[DEFAULT_FORE_COLOR] = color; update(); } /* --------------------------------------------------------------------- */ /* */ /* Drag & Drop */ /* */ /* --------------------------------------------------------------------- */ void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event) { // text/plain alone is enough for KDE-apps // text/uri-list is for supporting some non-KDE apps, such as thunar // and pcmanfm // That also applies in dropEvent() const auto mimeData = event->mimeData(); if ((!_readOnly) && (mimeData != nullptr) && (mimeData->hasFormat(QStringLiteral("text/plain")) || mimeData->hasFormat(QStringLiteral("text/uri-list")))) { event->acceptProposedAction(); } } void TerminalDisplay::dropEvent(QDropEvent* event) { if (_readOnly) { event->accept(); return; } const auto mimeData = event->mimeData(); if (mimeData == nullptr) { return; } auto urls = mimeData->urls(); QString dropText; if (!urls.isEmpty()) { for (int i = 0 ; i < urls.count() ; i++) { KIO::StatJob* job = KIO::mostLocalUrl(urls[i], KIO::HideProgressInfo); bool ok = job->exec(); if (!ok) { continue; } QUrl url = job->mostLocalUrl(); QString urlText; if (url.isLocalFile()) { urlText = url.path(); } else { urlText = url.url(); } // in future it may be useful to be able to insert file names with drag-and-drop // without quoting them (this only affects paths with spaces in) urlText = KShell::quoteArg(urlText); dropText += urlText; // Each filename(including the last) should be followed by one space. dropText += QLatin1Char(' '); } // If our target is local we will open a popup - otherwise the fallback kicks // in and the URLs will simply be pasted as text. if (!_dropUrlsAsText && (_sessionController != nullptr) && _sessionController->url().isLocalFile()) { // A standard popup with Copy, Move and Link as options - // plus an additional Paste option. QAction* pasteAction = new QAction(i18n("&Paste Location"), this); pasteAction->setData(dropText); connect(pasteAction, &QAction::triggered, this, &TerminalDisplay::dropMenuPasteActionTriggered); QList additionalActions; additionalActions.append(pasteAction); if (urls.count() == 1) { KIO::StatJob* job = KIO::mostLocalUrl(urls[0], KIO::HideProgressInfo); bool ok = job->exec(); if (ok) { const QUrl url = job->mostLocalUrl(); if (url.isLocalFile()) { const QFileInfo fileInfo(url.path()); if (fileInfo.isDir()) { QAction* cdAction = new QAction(i18n("Change &Directory To"), this); dropText = QLatin1String(" cd ") + dropText + QLatin1Char('\n'); cdAction->setData(dropText); connect(cdAction, &QAction::triggered, this, &TerminalDisplay::dropMenuCdActionTriggered); additionalActions.append(cdAction); } } } } QUrl target = QUrl::fromLocalFile(_sessionController->currentDir()); KIO::DropJob* job = KIO::drop(event, target); KJobWidgets::setWindow(job, this); job->setApplicationActions(additionalActions); return; } } else { dropText = mimeData->text(); } if (mimeData->hasFormat(QStringLiteral("text/plain")) || mimeData->hasFormat(QStringLiteral("text/uri-list"))) { emit sendStringToEmu(dropText.toLocal8Bit()); } } void TerminalDisplay::dropMenuPasteActionTriggered() { if (sender() != nullptr) { const QAction* action = qobject_cast(sender()); if (action != nullptr) { emit sendStringToEmu(action->data().toString().toLocal8Bit()); } } } void TerminalDisplay::dropMenuCdActionTriggered() { if (sender() != nullptr) { const QAction* action = qobject_cast(sender()); if (action != nullptr) { emit sendStringToEmu(action->data().toString().toLocal8Bit()); } } } void TerminalDisplay::doDrag() { _dragInfo.state = diDragging; _dragInfo.dragObject = new QDrag(this); auto mimeData = new QMimeData(); mimeData->setText(QApplication::clipboard()->mimeData(QClipboard::Selection)->text()); mimeData->setHtml(QApplication::clipboard()->mimeData(QClipboard::Selection)->html()); _dragInfo.dragObject->setMimeData(mimeData); _dragInfo.dragObject->exec(Qt::CopyAction); } void TerminalDisplay::setSessionController(SessionController* controller) { _sessionController = controller; } SessionController* TerminalDisplay::sessionController() { return _sessionController; } AutoScrollHandler::AutoScrollHandler(QWidget* parent) : QObject(parent) , _timerId(0) { parent->installEventFilter(this); } void AutoScrollHandler::timerEvent(QTimerEvent* event) { if (event->timerId() != _timerId) { return; } QMouseEvent mouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(QCursor::pos()), Qt::NoButton, Qt::LeftButton, Qt::NoModifier); QApplication::sendEvent(widget(), &mouseEvent); } bool AutoScrollHandler::eventFilter(QObject* watched, QEvent* event) { Q_ASSERT(watched == parent()); Q_UNUSED(watched); switch (event->type()) { case QEvent::MouseMove: { QMouseEvent* mouseEvent = static_cast(event); bool mouseInWidget = widget()->rect().contains(mouseEvent->pos()); if (mouseInWidget) { if (_timerId != 0) { killTimer(_timerId); } _timerId = 0; } else { if ((_timerId == 0) && ((mouseEvent->buttons() & Qt::LeftButton) != 0u)) { _timerId = startTimer(100); } } break; } case QEvent::MouseButtonRelease: { QMouseEvent* mouseEvent = static_cast(event); if ((_timerId != 0) && ((mouseEvent->buttons() & ~Qt::LeftButton) != 0u)) { killTimer(_timerId); _timerId = 0; } break; } default: break; }; return false; } diff --git a/src/TerminalDisplay.h b/src/TerminalDisplay.h index 4bd390b3..6f1da299 100644 --- a/src/TerminalDisplay.h +++ b/src/TerminalDisplay.h @@ -1,1087 +1,1088 @@ /* Copyright 2007-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle 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. */ #ifndef TERMINALDISPLAY_H #define TERMINALDISPLAY_H // Qt #include #include #include // Konsole #include "Character.h" #include "konsoleprivate_export.h" #include "ScreenWindow.h" #include "ColorScheme.h" #include "Enumeration.h" #include "ScrollState.h" class QDrag; class QDragEnterEvent; class QDropEvent; class QLabel; class QTimer; class QEvent; class QVBoxLayout; class QKeyEvent; class QScrollBar; class QShowEvent; class QHideEvent; class QTimerEvent; class KMessageWidget; namespace Konsole { class FilterChain; class TerminalImageFilterChain; class SessionController; /** * A widget which displays output from a terminal emulation and sends input keypresses and mouse activity * to the terminal. * * When the terminal emulation receives new output from the program running in the terminal, * it will update the display by calling updateImage(). * * TODO More documentation */ class KONSOLEPRIVATE_EXPORT TerminalDisplay : public QWidget { Q_OBJECT public: /** Constructs a new terminal display widget with the specified parent. */ explicit TerminalDisplay(QWidget *parent = nullptr); ~TerminalDisplay() Q_DECL_OVERRIDE; /** Returns the terminal color palette used by the display. */ const ColorEntry *colorTable() const; /** Sets the terminal color palette used by the display. */ void setColorTable(const ColorEntry table[]); /** * Sets the seed used to generate random colors for the display * (in color schemes that support them). */ void setRandomSeed(uint randomSeed); /** * Returns the seed used to generate random colors for the display * (in color schemes that support them). */ uint randomSeed() const; /** Sets the opacity of the terminal display. */ void setOpacity(qreal opacity); /** Sets the background picture */ void setWallpaper(ColorSchemeWallpaper::Ptr p); /** * Specifies whether the terminal display has a vertical scroll bar, and if so whether it * is shown on the left or right side of the display. */ void setScrollBarPosition(Enum::ScrollBarPositionEnum position); Enum::ScrollBarPositionEnum scrollBarPosition() const { return _scrollbarLocation; } /** * Sets the current position and range of the display's scroll bar. * * @param cursor The position of the scroll bar's thumb. * @param slines The maximum value of the scroll bar. */ void setScroll(int cursor, int slines); void setScrollFullPage(bool fullPage); bool scrollFullPage() const; /** * Returns the display's filter chain. When the image for the display is updated, * the text is passed through each filter in the chain. Each filter can define * hotspots which correspond to certain strings (such as URLs or particular words). * Depending on the type of the hotspots created by the filter ( returned by Filter::Hotspot::type() ) * the view will draw visual cues such as underlines on mouse-over for links or translucent * rectangles for markers. * * To add a new filter to the view, call: * viewWidget->filterChain()->addFilter( filterObject ); */ FilterChain *filterChain() const; /** * Updates the filters in the display's filter chain. This will cause * the hotspots to be updated to match the current image. * * WARNING: This function can be expensive depending on the * image size and number of filters in the filterChain() * * TODO - This API does not really allow efficient usage. Revise it so * that the processing can be done in a better way. * * eg: * - Area of interest may be known ( eg. mouse cursor hovering * over an area ) */ void processFilters(); /** * Returns a list of menu actions created by the filters for the content * at the given @p position. */ QList filterActions(const QPoint &position); /** Specifies whether or not the cursor can blink. */ void setBlinkingCursorEnabled(bool blink); /** Returns true if the cursor is allowed to blink or false otherwise. */ bool blinkingCursorEnabled() const { return _allowBlinkingCursor; } /** Specifies whether or not text can blink. */ void setBlinkingTextEnabled(bool blink); void setControlDrag(bool enable) { _ctrlRequiredForDrag = enable; } bool ctrlRequiredForDrag() const { return _ctrlRequiredForDrag; } void setDropUrlsAsText(bool enable) { _dropUrlsAsText = enable; } bool getDropUrlsAsText() const { return _dropUrlsAsText; } /** Sets how the text is selected when the user triple clicks within the display. */ void setTripleClickMode(Enum::TripleClickModeEnum mode) { _tripleClickMode = mode; } /** See setTripleClickSelectionMode() */ Enum::TripleClickModeEnum tripleClickMode() const { return _tripleClickMode; } /** * Specifies whether links and email addresses should be opened when * clicked with the mouse. Defaults to false. */ void setOpenLinksByDirectClick(bool value) { _openLinksByDirectClick = value; } /** * Returns true if links and email addresses should be opened when * clicked with the mouse. */ bool getOpenLinksByDirectClick() const { return _openLinksByDirectClick; } /** * Sets whether leading spaces should be trimmed in selected text. */ void setTrimLeadingSpaces(bool enabled) { _trimLeadingSpaces = enabled; } /** * Returns true if leading spaces should be trimmed in selected text. */ bool trimLeadingSpaces() const { return _trimLeadingSpaces; } /** * Sets whether trailing spaces should be trimmed in selected text. */ void setTrimTrailingSpaces(bool enabled) { _trimTrailingSpaces = enabled; } /** * Returns true if trailing spaces should be trimmed in selected text. */ bool trimTrailingSpaces() const { return _trimTrailingSpaces; } void setLineSpacing(uint); uint lineSpacing() const; void setSessionController(SessionController *controller); SessionController *sessionController(); /** * Sets the shape of the keyboard cursor. This is the cursor drawn * at the position in the terminal where keyboard input will appear. * * In addition the terminal display widget also has a cursor for * the mouse pointer, which can be set using the QWidget::setCursor() * method. * * Defaults to BlockCursor */ void setKeyboardCursorShape(Enum::CursorShapeEnum shape); /** * Returns the shape of the keyboard cursor. See setKeyboardCursorShape() */ Enum::CursorShapeEnum keyboardCursorShape() const; /** * Sets the Cursor Style (DECSCUSR) via escape sequences * @p shape cursor shape * @p isBlinking if true, the cursor will be set to blink */ void setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking); /** * Resets the cursor style to the current profile cursor shape and * blinking settings */ void resetCursorStyle(); /** * Sets the color used to draw the keyboard cursor. * * The keyboard cursor defaults to using the foreground color of the character * underneath it. * * @param color By default, the widget uses the color of the * character under the cursor to draw the cursor, and inverts the * color of that character to make sure it is still readable. If @p * color is a valid QColor, the widget uses that color to draw the * cursor. If @p color is not an valid QColor, the widget falls back * to the default behavior. */ void setKeyboardCursorColor(const QColor &color); /** * Returns the color of the keyboard cursor, or an invalid color if the keyboard * cursor color is set to change according to the foreground color of the character * underneath it. */ QColor keyboardCursorColor() const; /** * Returns the number of lines of text which can be displayed in the widget. * * This will depend upon the height of the widget and the current font. * See fontHeight() */ int lines() const { return _lines; } /** * Returns the number of characters of text which can be displayed on * each line in the widget. * * This will depend upon the width of the widget and the current font. * See fontWidth() */ int columns() const { return _columns; } /** * Returns the height of the characters in the font used to draw the text in the display. */ int fontHeight() const { return _fontHeight; } /** * Returns the width of the characters in the display. * This assumes the use of a fixed-width font. */ int fontWidth() const { return _fontWidth; } void setSize(int columns, int lines); // reimplemented QSize sizeHint() const Q_DECL_OVERRIDE; /** * Sets which characters, in addition to letters and numbers, * are regarded as being part of a word for the purposes * of selecting words in the display by double clicking on them. * * The word boundaries occur at the first and last characters which * are either a letter, number, or a character in @p wc * * @param wc An array of characters which are to be considered parts * of a word ( in addition to letters and numbers ). */ void setWordCharacters(const QString &wc); /** * Returns the characters which are considered part of a word for the * purpose of selecting words in the display with the mouse. * * @see setWordCharacters() */ QString wordCharacters() const { return _wordCharacters; } /** * Sets the type of effect used to alert the user when a 'bell' occurs in the * terminal session. * * The terminal session can trigger the bell effect by calling bell() with * the alert message. */ void setBellMode(int mode); /** * Returns the type of effect used to alert the user when a 'bell' occurs in * the terminal session. * * See setBellMode() */ int bellMode() const; /** Play a visual bell for prompt or warning. */ void visualBell(); /** * Specified whether zoom terminal on Ctrl+mousewheel is enabled or not. * Defaults to enabled. */ void setMouseWheelZoom(bool value) { _mouseWheelZoom = value; } /** * Returns the whether zoom terminal on Ctrl+mousewheel is enabled. * * See setMouseWheelZoom() */ bool mouseWheelZoom() { return _mouseWheelZoom; } /** * Reimplemented. Has no effect. Use setVTFont() to change the font * used to draw characters in the display. */ virtual void setFont(const QFont &); /** Returns the font used to draw characters in the display */ QFont getVTFont() { return font(); } /** * Sets the font used to draw the display. Has no effect if @p font * is larger than the size of the display itself. */ void setVTFont(const QFont &f); /** Increases the font size */ void increaseFontSize(); /** Decreases the font size */ void decreaseFontSize(); /** * Specified whether anti-aliasing of text in the terminal display * is enabled or not. Defaults to enabled. */ void setAntialias(bool value) { _antialiasText = value; } /** * Returns true if anti-aliasing of text in the terminal is enabled. */ bool antialias() const { return _antialiasText; } /** * Specifies whether characters with intense colors should be rendered * as bold. Defaults to true. */ void setBoldIntense(bool value) { _boldIntense = value; } /** * Returns true if characters with intense colors are rendered in bold. */ bool getBoldIntense() const { return _boldIntense; } /** * Specifies whether line characters will be displayed using font instead * of builtin code. * as bold. Defaults to false. */ void setUseFontLineCharacters(bool value) { _useFontLineCharacters = value; } /** * Returns true if font line characters will be used. */ bool getFontLineCharacters() const { return _useFontLineCharacters; } /** * Sets whether or not the current height and width of the * terminal in lines and columns is displayed whilst the widget * is being resized. */ void setShowTerminalSizeHint(bool on) { _showTerminalSizeHint = on; } /** * Returns whether or not the current height and width of * the terminal in lines and columns is displayed whilst the widget * is being resized. */ bool showTerminalSizeHint() const { return _showTerminalSizeHint; } /** * Sets the status of the BiDi rendering inside the terminal display. * Defaults to disabled. */ void setBidiEnabled(bool set) { _bidiEnabled = set; } /** * Returns the status of the BiDi rendering in this widget. */ bool isBidiEnabled() const { return _bidiEnabled; } /** * Sets the modifiers that shows URL hints when they are pressed * Defaults to disabled. */ void setUrlHintsModifiers(int modifiers) { _urlHintsModifiers = Qt::KeyboardModifiers(modifiers); } /** * Sets the terminal screen section which is displayed in this widget. * When updateImage() is called, the display fetches the latest character image from the * the associated terminal screen window. * * In terms of the model-view paradigm, the ScreenWindow is the model which is rendered * by the TerminalDisplay. */ void setScreenWindow(ScreenWindow *window); /** Returns the terminal screen section which is displayed in this widget. See setScreenWindow() */ ScreenWindow *screenWindow() const; // Select the current line. void selectCurrentLine(); /** * Selects everything in the terminal */ void selectAll(); void printContent(QPainter &painter, bool friendly); /** * Gets the background of the display * @see setBackgroundColor(), setColorTable(), setForegroundColor() */ QColor getBackgroundColor() const; bool bracketedPasteMode() const; /** * Returns true if the flow control warning box is enabled. * See outputSuspended() and setFlowControlWarningEnabled() */ bool flowControlWarningEnabled() const { return _flowControlWarningEnabled; } - /** See setUsesMouse() */ - bool usesMouse() const; + /** See setUsesMouseTracking() */ + bool usesMouseTracking() const; /** See setAlternateScrolling() */ bool alternateScrolling() const; public Q_SLOTS: /** * Scrolls current ScreenWindow * * it's needed for proper handling scroll commands in the Vt102Emulation class */ void scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount); /** * Causes the terminal display to fetch the latest character image from the associated * terminal screen ( see setScreenWindow() ) and redraw the display. */ void updateImage(); /** * Causes the terminal display to fetch the latest line status flags from the * associated terminal screen ( see setScreenWindow() ). */ void updateLineProperties(); void setAutoCopySelectedText(bool enabled); void setCopyTextAsHTML(bool enabled); void setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum mode); /** Copies the selected text to the X11 Selection. */ void copyToX11Selection(); /** Copies the selected text to the system clipboard. */ void copyToClipboard(); /** * Pastes the content of the clipboard into the * display. */ void pasteFromClipboard(bool appendEnter = false); /** * Pastes the content of the X11 selection into the * display. */ void pasteFromX11Selection(bool appendEnter = false); /** * Changes whether the flow control warning box should be shown when the flow control * stop key (Ctrl+S) are pressed. */ void setFlowControlWarningEnabled(bool enable); /** * Causes the widget to display or hide a message informing the user that terminal * output has been suspended (by using the flow control key combination Ctrl+S) * * @param suspended True if terminal output has been suspended and the warning message should * be shown or false to indicate that terminal output has been resumed and that * the warning message should disappear. */ void outputSuspended(bool suspended); /** - * Sets whether the program whose output is being displayed in the view - * is interested in mouse events. + * Sets whether the program currently running in the terminal is interested + * in Mouse Tracking events. * - * If this is set to true, mouse signals will be emitted by the view when the user clicks, drags - * or otherwise moves the mouse inside the view. - * The user interaction needed to create selections will also change, and the user will be required - * to hold down the shift key to create a selection or perform other mouse activities inside the - * view area - since the program running in the terminal is being allowed to handle normal mouse - * events itself. + * When set to true, Konsole will send Mouse Tracking events. * - * @param on Set to true if the program running in the terminal is interested in mouse events - * or false otherwise. + * The user interaction needed to create text selections will change + * also, and the user will be required to hold down the Shift key to + * create a selection or perform other mouse activities inside the view + * area, since the program running in the terminal is being allowed + * to handle normal mouse events itself. + * + * @param on Set to true if the program running in the terminal is + * interested in Mouse Tracking events or false otherwise. */ - void setUsesMouse(bool on); + void setUsesMouseTracking(bool on); /** * Sets the AlternateScrolling profile property which controls whether * to emulate up/down key presses for mouse scroll wheel events. * For more details, check the documentation of that property in the * Profile header. * Enabled by default. */ void setAlternateScrolling(bool enable); /** * Sets _isPrimaryScreen depending on which screen is currently in * use, primary or alternate * * @param use Set to @c true if the primary screen is in use or to * @c false otherwise (i.e. the alternate screen is in use) */ void usingPrimaryScreen(bool use); void setBracketedPasteMode(bool on); /** * Shows a notification that a bell event has occurred in the terminal. * TODO: More documentation here */ void bell(const QString &message); /** * Sets the background of the display to the specified color. * @see setColorTable(), getBackgroundColor(), setForegroundColor() */ void setBackgroundColor(const QColor &color); /** * Sets the text of the display to the specified color. * @see setColorTable(), setBackgroundColor(), getBackgroundColor() */ void setForegroundColor(const QColor &color); /** * Sets the display's contents margins. */ void setMargin(int margin); /** * Sets whether the contents are centered between the margins. */ void setCenterContents(bool enable); // Used to show/hide the message widget void updateReadOnlyState(bool readonly); Q_SIGNALS: /** * Emitted when the user presses a key whilst the terminal widget has focus. */ void keyPressedSignal(QKeyEvent *event); /** * A mouse event occurred. * @param button The mouse button (0 for left button, 1 for middle button, 2 for right button, 3 for release) * @param column The character column where the event occurred * @param line The character row where the event occurred * @param eventType The type of event. 0 for a mouse press / release or 1 for mouse motion */ void mouseSignal(int button, int column, int line, int eventType); void changedFontMetricSignal(int height, int width); void changedContentSizeSignal(int height, int width); /** - * Emitted when the user right clicks on the display, or right-clicks with the Shift - * key held down if usesMouse() is true. + * Emitted when the user right clicks on the display, or right-clicks + * with the Shift key held down if usesMouseTracking() is true. * * This can be used to display a context menu. */ void configureRequest(const QPoint &position); /** * When a shortcut which is also a valid terminal key sequence is pressed while * the terminal widget has focus, this signal is emitted to allow the host to decide * whether the shortcut should be overridden. * When the shortcut is overridden, the key sequence will be sent to the terminal emulation instead * and the action associated with the shortcut will not be triggered. * * @p override is set to false by default and the shortcut will be triggered as normal. */ void overrideShortcutCheck(QKeyEvent *keyEvent, bool &override); void sendStringToEmu(const QByteArray &local8BitString); void focusLost(); void focusGained(); protected: bool event(QEvent *event) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *pe) Q_DECL_OVERRIDE; void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; void contextMenuEvent(QContextMenuEvent *event) Q_DECL_OVERRIDE; virtual void fontChange(const QFont &); void focusInEvent(QFocusEvent *event) Q_DECL_OVERRIDE; void focusOutEvent(QFocusEvent *event) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; void mouseDoubleClickEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; virtual void extendSelection(const QPoint &position); void wheelEvent(QWheelEvent *ev) Q_DECL_OVERRIDE; bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE; // drag and drop void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE; void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE; void doDrag(); enum DragState { diNone, diPending, diDragging }; struct DragInfo { DragState state; QPoint start; QDrag *dragObject; } _dragInfo; // classifies the 'ch' into one of three categories // and returns a character to indicate which category it is in // // - A space (returns ' ') // - Part of a word (returns 'a') // - Other characters (returns the input character) QChar charClass(const Character &ch) const; void clearImage(); void mouseTripleClickEvent(QMouseEvent *ev); void selectLine(QPoint pos, bool entireLine); // reimplemented void inputMethodEvent(QInputMethodEvent *event) Q_DECL_OVERRIDE; QVariant inputMethodQuery(Qt::InputMethodQuery query) const Q_DECL_OVERRIDE; void updateScrollBarPalette(); protected Q_SLOTS: void scrollBarPositionChanged(int value); void blinkTextEvent(); void blinkCursorEvent(); private Q_SLOTS: void swapFGBGColors(); void viewScrolledByUser(); /** * Called from the drag-and-drop popup. Causes the dropped URLs to be pasted as text. */ void dropMenuPasteActionTriggered(); void dropMenuCdActionTriggered(); void dismissOutputSuspendedMessage(); private: Q_DISABLE_COPY(TerminalDisplay) // -- Drawing helpers -- // divides the part of the display specified by 'rect' into // fragments according to their colors and styles and calls // drawTextFragment() or drawPrinterFriendlyTextFragment() // to draw the fragments void drawContents(QPainter &painter, const QRect &rect); // draw a transparent rectangle over the line of the current match void drawCurrentResultRect(QPainter &painter); // draws a section of text, all the text in this section // has a common color and style void drawTextFragment(QPainter &painter, const QRect &rect, const QString &text, const Character *style); void drawPrinterFriendlyTextFragment(QPainter &painter, const QRect &rect, const QString &text, const Character *style); // draws the background for a text fragment // if useOpacitySetting is true then the color's alpha value will be set to // the display's transparency (set with setOpacity()), otherwise the background // will be drawn fully opaque void drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting); // draws the cursor character void drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor, const QColor &backgroundColor, bool &invertCharacterColor); // draws the characters or line graphics in a text fragment void drawCharacters(QPainter &painter, const QRect &rect, const QString &text, const Character *style, bool invertCharacterColor); // draws a string of line graphics void drawLineCharString(QPainter &painter, int x, int y, const QString &str, const Character *attributes); // draws the preedit string for input methods void drawInputMethodPreeditString(QPainter &painter, const QRect &rect); // -- // maps an area in the character image to an area on the widget QRect imageToWidget(const QRect &imageArea) const; // maps a point on the widget to the position ( ie. line and column ) // of the character at that point. When the edge is true, it maps to // a character which left edge is closest to the point. void getCharacterPosition(const QPoint &widgetPoint, int &line, int &column, bool edge = true) const; // the area where the preedit string for input methods will be draw QRect preeditRect() const; // shows a notification window in the middle of the widget indicating the terminal's // current size in columns and lines void showResizeNotification(); // scrolls the image by a number of lines. // 'lines' may be positive ( to scroll the image down ) // or negative ( to scroll the image up ) // 'region' is the part of the image to scroll - currently only // the top, bottom and height of 'region' are taken into account, // the left and right are ignored. void scrollImage(int lines, const QRect &screenWindowRegion); void calcGeometry(); void propagateSize(); void updateImageSize(); void makeImage(); void paintFilters(QPainter &painter); // returns a region covering all of the areas of the widget which contain // a hotspot QRegion hotSpotRegion() const; // returns the position of the cursor in columns and lines QPoint cursorPosition() const; // returns true if the cursor's position is on display. bool isCursorOnDisplay() const; // redraws the cursor void updateCursor(); bool handleShortcutOverrideEvent(QKeyEvent *keyEvent); void doPaste(QString text, bool appendReturn); void processMidButtonClick(QMouseEvent *ev); QPoint findLineStart(const QPoint &pnt); QPoint findLineEnd(const QPoint &pnt); QPoint findWordStart(const QPoint &pnt); QPoint findWordEnd(const QPoint &pnt); // Uses the current settings for trimming whitespace and preserving linebreaks to create a proper flag value for Screen Screen::DecodingOptions currentDecodingOptions(); // Boilerplate setup for MessageWidget KMessageWidget* createMessageWidget(const QString &text); int loc(int x, int y) const; // the window onto the terminal screen which this display // is currently showing. QPointer _screenWindow; bool _bellMasked; QVBoxLayout *_verticalLayout; bool _fixedFont; // has fixed pitch int _fontHeight; // height int _fontWidth; // width int _fontAscent; // ascend bool _boldIntense; // Whether intense colors should be rendered with bold font int _lines; // the number of lines that can be displayed in the widget int _columns; // the number of columns that can be displayed in the widget int _usedLines; // the number of lines that are actually being used, this will be less // than 'lines' if the character image provided with setImage() is smaller // than the maximum image size which can be displayed int _usedColumns; // the number of columns that are actually being used, this will be less // than 'columns' if the character image provided with setImage() is smaller // than the maximum image size which can be displayed QRect _contentRect; Character *_image; // [lines][columns] // only the area [usedLines][usedColumns] in the image contains valid data int _imageSize; QVector _lineProperties; ColorEntry _colorTable[TABLE_COLORS]; uint _randomSeed; bool _resizing; bool _showTerminalSizeHint; bool _bidiEnabled; - bool _mouseMarks; + bool _usesMouseTracking; bool _alternateScrolling; bool _isPrimaryScreen; bool _bracketedPasteMode; QPoint _iPntSel; // initial selection point QPoint _pntSel; // current selection point QPoint _tripleSelBegin; // help avoid flicker int _actSel; // selection state bool _wordSelectionMode; bool _lineSelectionMode; bool _preserveLineBreaks; bool _columnSelectionMode; bool _autoCopySelectedText; bool _copyTextAsHTML; Enum::MiddleClickPasteModeEnum _middleClickPasteMode; QScrollBar *_scrollBar; Enum::ScrollBarPositionEnum _scrollbarLocation; bool _scrollFullPage; QString _wordCharacters; int _bellMode; bool _allowBlinkingText; // allow text to blink bool _allowBlinkingCursor; // allow cursor to blink bool _textBlinking; // text is blinking, hide it when drawing bool _cursorBlinking; // cursor is blinking, hide it when drawing bool _hasTextBlinker; // has characters to blink QTimer *_blinkTextTimer; QTimer *_blinkCursorTimer; Qt::KeyboardModifiers _urlHintsModifiers; bool _showUrlHint; bool _openLinksByDirectClick; // Open URL and hosts by single mouse click bool _ctrlRequiredForDrag; // require Ctrl key for drag selected text bool _dropUrlsAsText; // always paste URLs as text without showing copy/move menu Enum::TripleClickModeEnum _tripleClickMode; bool _possibleTripleClick; // is set in mouseDoubleClickEvent and deleted // after QApplication::doubleClickInterval() delay QLabel *_resizeWidget; QTimer *_resizeTimer; bool _flowControlWarningEnabled; //widgets related to the warning message that appears when the user presses Ctrl+S to suspend //terminal output - informing them what has happened and how to resume output KMessageWidget *_outputSuspendedMessageWidget; uint _lineSpacing; QSize _size; QRgb _blendColor; ColorSchemeWallpaper::Ptr _wallpaper; // list of filters currently applied to the display. used for links and // search highlight TerminalImageFilterChain *_filterChain; QRegion _mouseOverHotspotArea; bool _filterUpdateRequired; Enum::CursorShapeEnum _cursorShape; // cursor color. If it is invalid (by default) then the foreground // color of the character under the cursor is used QColor _cursorColor; struct InputMethodData { QString preeditString; QRect previousPreeditRect; }; InputMethodData _inputMethodData; bool _antialiasText; // do we anti-alias or not bool _useFontLineCharacters; bool _printerFriendly; // are we currently painting to a printer in black/white mode //the delay in milliseconds between redrawing blinking text static const int TEXT_BLINK_DELAY = 500; //the duration of the size hint in milliseconds static const int SIZE_HINT_DURATION = 1000; SessionController *_sessionController; bool _trimLeadingSpaces; // trim leading spaces in selected text bool _trimTrailingSpaces; // trim trailing spaces in selected text bool _mouseWheelZoom; // enable mouse wheel zooming or not int _margin; // the contents margin bool _centerContents; // center the contents between margins KMessageWidget *_readOnlyMessageWidget; // Message shown at the top when read-only mode gets activated // Needed to know whether the mode really changed between update calls bool _readOnly; qreal _opacity; ScrollState _scrollWheelState; friend class TerminalDisplayAccessible; }; class AutoScrollHandler : public QObject { Q_OBJECT public: explicit AutoScrollHandler(QWidget *parent); protected: void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE; bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; private: QWidget *widget() const { return static_cast(parent()); } int _timerId; }; } #endif // TERMINALDISPLAY_H diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index 8207ec7a..e7a70a42 100644 --- a/src/Vt102Emulation.cpp +++ b/src/Vt102Emulation.cpp @@ -1,1547 +1,1547 @@ /* This file is part of Konsole, an X terminal. Copyright 2007-2008 by Robert Knight Copyright 1997,1998 by Lars Doelle 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 #include "Vt102Emulation.h" // Standard #include #include // Qt #include #include #include // KDE #include // Konsole #include "KeyboardTranslator.h" #include "SessionController.h" #include "TerminalDisplay.h" using Konsole::Vt102Emulation; /* The VT100 has 32 special graphical characters. The usual vt100 extended xterm fonts have these at 0x00..0x1f. QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals come in here as proper unicode characters. We treat non-iso10646 fonts as VT100 extended and do the required mapping from unicode to 0x00..0x1f. The remaining translation is then left to the QCodec. */ // assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i. unsigned short Konsole::vt100_graphics[32] = { // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534, 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7 }; Vt102Emulation::Vt102Emulation() : Emulation(), _currentModes(TerminalState()), _savedModes(TerminalState()), _pendingTitleUpdates(QHash()), _titleUpdateTimer(new QTimer(this)), _reportFocusEvents(false) { _titleUpdateTimer->setSingleShot(true); QObject::connect(_titleUpdateTimer, &QTimer::timeout, this, &Konsole::Vt102Emulation::updateTitle); initTokenizer(); reset(); } Vt102Emulation::~Vt102Emulation() = default; void Vt102Emulation::clearEntireScreen() { _currentScreen->clearEntireScreen(); bufferedUpdate(); } void Vt102Emulation::reset() { // Save the current codec so we can set it later. // Ideally we would want to use the profile setting const QTextCodec *currentCodec = codec(); resetTokenizer(); resetModes(); resetCharset(0); _screen[0]->reset(); resetCharset(1); _screen[1]->reset(); if (currentCodec != nullptr) { setCodec(currentCodec); } else { setCodec(LocaleCodec); } emit resetCursorStyleRequest(); bufferedUpdate(); } /* ------------------------------------------------------------------------- */ /* */ /* Processing the incoming byte stream */ /* */ /* ------------------------------------------------------------------------- */ /* Incoming Bytes Event pipeline This section deals with decoding the incoming character stream. Decoding means here, that the stream is first separated into `tokens' which are then mapped to a `meaning' provided as operations by the `Screen' class or by the emulation class itself. The pipeline proceeds as follows: - Tokenizing the ESC codes (onReceiveChar) - VT100 code page translation of plain characters (applyCharset) - Interpretation of ESC codes (processToken) The escape codes and their meaning are described in the technical reference of this program. */ // Tokens ------------------------------------------------------------------ -- /* Since the tokens are the central notion if this section, we've put them in front. They provide the syntactical elements used to represent the terminals operations as byte sequences. They are encodes here into a single machine word, so that we can later switch over them easily. Depending on the token itself, additional argument variables are filled with parameter values. The tokens are defined below: - CHR - Printable characters (32..255 but DEL (=127)) - CTL - Control characters (0..31 but ESC (= 27), DEL) - ESC - Escape codes of the form - ESC_DE - Escape codes of the form C - CSI_PN - Escape codes of the form '[' {Pn} ';' {Pn} C - CSI_PS - Escape codes of the form '[' {Pn} ';' ... C - CSI_PR - Escape codes of the form '[' '?' {Pn} ';' ... C - CSI_PE - Escape codes of the form '[' '!' {Pn} ';' ... C - CSI_SP - Escape codes of the form '[' ' ' C (3rd field is a space) - CSI_PSP - Escape codes of the form '[' '{Pn}' ' ' C (4th field is a space) - VT52 - VT52 escape codes - - 'Y'{Pc}{Pc} - XTE_HA - Xterm window/terminal attribute commands of the form `]' {Pn} `;' {Text} (Note that these are handled differently to the other formats) The last two forms allow list of arguments. Since the elements of the lists are treated individually the same way, they are passed as individual tokens to the interpretation. Further, because the meaning of the parameters are names (although represented as numbers), they are includes within the token ('N'). */ constexpr int token_construct(int t, int a, int n) { return (((n & 0xffff) << 16) | ((a & 0xff) << 8) | (t & 0xff)); } constexpr int token_chr() { return token_construct(0, 0, 0); } constexpr int token_ctl(int a) { return token_construct(1, a, 0); } constexpr int token_esc(int a) { return token_construct(2, a, 0); } constexpr int token_esc_cs(int a, int b) { return token_construct(3, a, b); } constexpr int token_esc_de(int a) { return token_construct(4, a, 0); } constexpr int token_csi_ps(int a, int n) { return token_construct(5, a, n); } constexpr int token_csi_pn(int a) { return token_construct(6, a, 0); } constexpr int token_csi_pr(int a, int n) { return token_construct(7, a, n); } constexpr int token_vt52(int a) { return token_construct(8, a, 0); } constexpr int token_csi_pg(int a) { return token_construct(9, a, 0); } constexpr int token_csi_pe(int a) { return token_construct(10, a, 0); } constexpr int token_csi_sp(int a) { return token_construct(11, a, 0); } constexpr int token_csi_psp(int a, int n) { return token_construct(12, a, n); } const int MAX_ARGUMENT = 4096; // Tokenizer --------------------------------------------------------------- -- /* The tokenizer's state The state is represented by the buffer (tokenBuffer, tokenBufferPos), and accompanied by decoded arguments kept in (argv,argc). Note that they are kept internal in the tokenizer. */ void Vt102Emulation::resetTokenizer() { tokenBufferPos = 0; argc = 0; argv[0] = 0; argv[1] = 0; } void Vt102Emulation::addDigit(int digit) { if (argv[argc] < MAX_ARGUMENT) { argv[argc] = 10 * argv[argc] + digit; } } void Vt102Emulation::addArgument() { argc = qMin(argc + 1, MAXARGS - 1); argv[argc] = 0; } void Vt102Emulation::addToCurrentToken(int cc) { tokenBuffer[tokenBufferPos] = cc; tokenBufferPos = qMin(tokenBufferPos + 1, MAX_TOKEN_LENGTH - 1); } // Character Class flags used while decoding const int CTL = 1; // Control character const int CHR = 2; // Printable character const int CPN = 4; // TODO: Document me const int DIG = 8; // Digit const int SCS = 16; // Select Character Set const int GRP = 32; // TODO: Document me const int CPS = 64; // Character which indicates end of window resize void Vt102Emulation::initTokenizer() { int i; quint8 *s; for (i = 0; i < 256; ++i) { charClass[i] = 0; } for (i = 0; i < 32; ++i) { charClass[i] |= CTL; } for (i = 32; i < 256; ++i) { charClass[i] |= CHR; } for (s = (quint8 *)"@ABCDGHILMPSTXZbcdfry"; *s != 0u; ++s) { charClass[*s] |= CPN; } // resize = \e[8;;t for (s = (quint8 *)"t"; *s != 0u; ++s) { charClass[*s] |= CPS; } for (s = (quint8 *)"0123456789"; *s != 0u; ++s) { charClass[*s] |= DIG; } for (s = (quint8 *)"()+*%"; *s != 0u; ++s) { charClass[*s] |= SCS; } for (s = (quint8 *)"()+*#[]%"; *s != 0u; ++s) { charClass[*s] |= GRP; } resetTokenizer(); } /* Ok, here comes the nasty part of the decoder. Instead of keeping an explicit state, we deduce it from the token scanned so far. It is then immediately combined with the current character to form a scanning decision. This is done by the following defines. - P is the length of the token scanned so far. - L (often P-1) is the position on which contents we base a decision. - C is a character or a group of characters (taken from 'charClass'). - 'cc' is the current character - 's' is a pointer to the start of the token buffer - 'p' is the current position within the token buffer Note that they need to applied in proper order. */ #define lec(P,L,C) (p == (P) && s[(L)] == (C)) #define lun( ) (p == 1 && cc >= 32 ) #define les(P,L,C) (p == (P) && s[L] < 256 && (charClass[s[(L)]] & (C)) == (C)) #define eec(C) (p >= 3 && cc == (C)) #define ees(C) (p >= 3 && cc < 256 && (charClass[cc] & (C)) == (C)) #define eps(C) (p >= 3 && s[2] != '?' && s[2] != '!' && s[2] != '>' && cc < 256 && (charClass[cc] & (C)) == (C)) #define epp( ) (p >= 3 && s[2] == '?') #define epe( ) (p >= 3 && s[2] == '!') #define egt( ) (p >= 3 && s[2] == '>') #define esp( ) (p >= 4 && s[2] == SP ) #define epsp( ) (p >= 5 && s[3] == SP ) #define Xpe (tokenBufferPos >= 2 && tokenBuffer[1] == ']') #define Xte (Xpe && (cc == 7 || cc == 27)) #define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C) && !Xte) #define dcs (p >= 2 && s[0] == ESC && s[1] == 'P') #define CNTL(c) ((c)-'@') const int ESC = 27; const int DEL = 127; const int SP = 32; // process an incoming unicode character void Vt102Emulation::receiveChar(uint cc) { if (cc == DEL) { return; //VT100: ignore. } if (ces(CTL)) { // ignore control characters in the text part of Xpe (aka OSC) "ESC]" // escape sequences; this matches what XTERM docs say if (Xpe) { return; } // DEC HACK ALERT! Control Characters are allowed *within* esc sequences in VT100 // This means, they do neither a resetTokenizer() nor a pushToToken(). Some of them, do // of course. Guess this originates from a weakly layered handling of the X-on // X-off protocol, which comes really below this level. if (cc == CNTL('X') || cc == CNTL('Z') || cc == ESC) { resetTokenizer(); //VT100: CAN or SUB } if (cc != ESC) { processToken(token_ctl(cc+'@'), 0, 0); return; } } // advance the state addToCurrentToken(cc); int* s = tokenBuffer; const int p = tokenBufferPos; if (getMode(MODE_Ansi)) { if (lec(1,0,ESC)) { return; } if (lec(1,0,ESC+128)) { s[0] = ESC; receiveChar('['); return; } if (les(2,1,GRP)) { return; } if (Xte ) { processWindowAttributeRequest(); resetTokenizer(); return; } if (Xpe ) { return; } if (lec(3,2,'?')) { return; } if (lec(3,2,'>')) { return; } if (lec(3,2,'!')) { return; } if (lec(3,2,SP )) { return; } if (lec(4,3,SP )) { return; } if (lun( )) { processToken(token_chr(), applyCharset(cc), 0); resetTokenizer(); return; } if (dcs ) { return; /* TODO We don't xterm DCS, so we just eat it */ } if (lec(2,0,ESC)) { processToken(token_esc(s[1]), 0, 0); resetTokenizer(); return; } if (les(3,1,SCS)) { processToken(token_esc_cs(s[1],s[2]), 0, 0); resetTokenizer(); return; } if (lec(3,1,'#')) { processToken(token_esc_de(s[2]), 0, 0); resetTokenizer(); return; } if (eps( CPN)) { processToken(token_csi_pn(cc), argv[0],argv[1]); resetTokenizer(); return; } // resize = \e[8;;t if (eps(CPS)) { processToken(token_csi_ps(cc, argv[0]), argv[1], argv[2]); resetTokenizer(); return; } if (epe( )) { processToken(token_csi_pe(cc), 0, 0); resetTokenizer(); return; } if (esp ( )) { processToken(token_csi_sp(cc), 0, 0); resetTokenizer(); return; } if (epsp( )) { processToken(token_csi_psp(cc, argv[0]), 0, 0); resetTokenizer(); return; } if (ees(DIG)) { addDigit(cc-'0'); return; } if (eec(';')) { addArgument(); return; } for (int i = 0; i <= argc; i++) { if (epp()) { processToken(token_csi_pr(cc,argv[i]), 0, 0); } else if (egt()) { processToken(token_csi_pg(cc), 0, 0); // spec. case for ESC]>0c or ESC]>c } else if (cc == 'm' && argc - i >= 4 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 2) { // ESC[ ... 48;2;;; ... m -or- ESC[ ... 38;2;;; ... m i += 2; processToken(token_csi_ps(cc, argv[i-2]), COLOR_SPACE_RGB, (argv[i] << 16) | (argv[i+1] << 8) | argv[i+2]); i += 2; } else if (cc == 'm' && argc - i >= 2 && (argv[i] == 38 || argv[i] == 48) && argv[i+1] == 5) { // ESC[ ... 48;5; ... m -or- ESC[ ... 38;5; ... m i += 2; processToken(token_csi_ps(cc, argv[i-2]), COLOR_SPACE_256, argv[i]); } else { processToken(token_csi_ps(cc,argv[i]), 0, 0); } } resetTokenizer(); } else { // VT52 Mode if (lec(1,0,ESC)) { return; } if (les(1,0,CHR)) { processToken(token_chr(), s[0], 0); resetTokenizer(); return; } if (lec(2,1,'Y')) { return; } if (lec(3,1,'Y')) { return; } if (p < 4) { processToken(token_vt52(s[1] ), 0, 0); resetTokenizer(); return; } processToken(token_vt52(s[1]), s[2], s[3]); resetTokenizer(); return; } } void Vt102Emulation::processWindowAttributeRequest() { // Describes the window or terminal session attribute to change // See Session::UserTitleChange for possible values int attribute = 0; int i; for (i = 2; i < tokenBufferPos && tokenBuffer[i] >= '0' && tokenBuffer[i] <= '9'; i++) { attribute = 10 * attribute + (tokenBuffer[i]-'0'); } if (tokenBuffer[i] != ';') { reportDecodingError(); return; } QString value; value.reserve(tokenBufferPos-i-2); for (int j = 0; j < tokenBufferPos-i-2; j++) { value[j] = tokenBuffer[i+1+j]; } if (value == QLatin1String("?")) { emit sessionAttributeRequest(attribute); return; } _pendingTitleUpdates[attribute] = value; _titleUpdateTimer->start(20); } void Vt102Emulation::updateTitle() { QListIterator iter( _pendingTitleUpdates.keys() ); while (iter.hasNext()) { int arg = iter.next(); emit titleChanged( arg , _pendingTitleUpdates[arg] ); } _pendingTitleUpdates.clear(); } // Interpreting Codes --------------------------------------------------------- /* Now that the incoming character stream is properly tokenized, meaning is assigned to them. These are either operations of the current _screen, or of the emulation class itself. The token to be interpreted comes in as a machine word possibly accompanied by two parameters. Likewise, the operations assigned to, come with up to two arguments. One could consider to make up a proper table from the function below. The technical reference manual provides more information about this mapping. */ void Vt102Emulation::processToken(int token, int p, int q) { switch (token) { case token_chr( ) : _currentScreen->displayCharacter (p ); break; //UTF16 // 127 DEL : ignored on input case token_ctl('@' ) : /* NUL: ignored */ break; case token_ctl('A' ) : /* SOH: ignored */ break; case token_ctl('B' ) : /* STX: ignored */ break; case token_ctl('C' ) : /* ETX: ignored */ break; case token_ctl('D' ) : /* EOT: ignored */ break; case token_ctl('E' ) : reportAnswerBack ( ); break; //VT100 case token_ctl('F' ) : /* ACK: ignored */ break; case token_ctl('G' ) : emit stateSet(NOTIFYBELL); break; //VT100 case token_ctl('H' ) : _currentScreen->backspace ( ); break; //VT100 case token_ctl('I' ) : _currentScreen->tab ( ); break; //VT100 case token_ctl('J' ) : _currentScreen->newLine ( ); break; //VT100 case token_ctl('K' ) : _currentScreen->newLine ( ); break; //VT100 case token_ctl('L' ) : _currentScreen->newLine ( ); break; //VT100 case token_ctl('M' ) : _currentScreen->toStartOfLine ( ); break; //VT100 case token_ctl('N' ) : useCharset ( 1); break; //VT100 case token_ctl('O' ) : useCharset ( 0); break; //VT100 case token_ctl('P' ) : /* DLE: ignored */ break; case token_ctl('Q' ) : /* DC1: XON continue */ break; //VT100 case token_ctl('R' ) : /* DC2: ignored */ break; case token_ctl('S' ) : /* DC3: XOFF halt */ break; //VT100 case token_ctl('T' ) : /* DC4: ignored */ break; case token_ctl('U' ) : /* NAK: ignored */ break; case token_ctl('V' ) : /* SYN: ignored */ break; case token_ctl('W' ) : /* ETB: ignored */ break; case token_ctl('X' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 case token_ctl('Y' ) : /* EM : ignored */ break; case token_ctl('Z' ) : _currentScreen->displayCharacter ( 0x2592); break; //VT100 case token_ctl('[' ) : /* ESC: cannot be seen here. */ break; case token_ctl('\\' ) : /* FS : ignored */ break; case token_ctl(']' ) : /* GS : ignored */ break; case token_ctl('^' ) : /* RS : ignored */ break; case token_ctl('_' ) : /* US : ignored */ break; case token_esc('D' ) : _currentScreen->index ( ); break; //VT100 case token_esc('E' ) : _currentScreen->nextLine ( ); break; //VT100 case token_esc('H' ) : _currentScreen->changeTabStop (true ); break; //VT100 case token_esc('M' ) : _currentScreen->reverseIndex ( ); break; //VT100 case token_esc('Z' ) : reportTerminalType ( ); break; case token_esc('c' ) : reset ( ); break; case token_esc('n' ) : useCharset ( 2); break; case token_esc('o' ) : useCharset ( 3); break; case token_esc('7' ) : saveCursor ( ); break; case token_esc('8' ) : restoreCursor ( ); break; case token_esc('=' ) : setMode (MODE_AppKeyPad); break; case token_esc('>' ) : resetMode (MODE_AppKeyPad); break; case token_esc('<' ) : setMode (MODE_Ansi ); break; //VT100 case token_esc_cs('(', '0') : setCharset (0, '0'); break; //VT100 case token_esc_cs('(', 'A') : setCharset (0, 'A'); break; //VT100 case token_esc_cs('(', 'B') : setCharset (0, 'B'); break; //VT100 case token_esc_cs(')', '0') : setCharset (1, '0'); break; //VT100 case token_esc_cs(')', 'A') : setCharset (1, 'A'); break; //VT100 case token_esc_cs(')', 'B') : setCharset (1, 'B'); break; //VT100 case token_esc_cs('*', '0') : setCharset (2, '0'); break; //VT100 case token_esc_cs('*', 'A') : setCharset (2, 'A'); break; //VT100 case token_esc_cs('*', 'B') : setCharset (2, 'B'); break; //VT100 case token_esc_cs('+', '0') : setCharset (3, '0'); break; //VT100 case token_esc_cs('+', 'A') : setCharset (3, 'A'); break; //VT100 case token_esc_cs('+', 'B') : setCharset (3, 'B'); break; //VT100 case token_esc_cs('%', 'G') : setCodec (Utf8Codec ); break; //LINUX case token_esc_cs('%', '@') : setCodec (LocaleCodec ); break; //LINUX case token_esc_de('3' ) : /* Double height line, top half */ _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); break; case token_esc_de('4' ) : /* Double height line, bottom half */ _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true ); _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , true ); break; case token_esc_de('5' ) : /* Single width, single height line*/ _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , false); _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); break; case token_esc_de('6' ) : /* Double width, single height line*/ _currentScreen->setLineProperty( LINE_DOUBLEWIDTH , true); _currentScreen->setLineProperty( LINE_DOUBLEHEIGHT , false); break; case token_esc_de('8' ) : _currentScreen->helpAlign ( ); break; // resize = \e[8;;t case token_csi_ps('t', 8) : setImageSize( p /*lines */, q /* columns */ ); emit imageResizeRequest(QSize(q, p)); break; // change tab text color : \e[28;t color: 0-16,777,215 case token_csi_ps('t', 28) : emit changeTabTextColorRequest ( p ); break; case token_csi_ps('K', 0) : _currentScreen->clearToEndOfLine ( ); break; case token_csi_ps('K', 1) : _currentScreen->clearToBeginOfLine ( ); break; case token_csi_ps('K', 2) : _currentScreen->clearEntireLine ( ); break; case token_csi_ps('J', 0) : _currentScreen->clearToEndOfScreen ( ); break; case token_csi_ps('J', 1) : _currentScreen->clearToBeginOfScreen ( ); break; case token_csi_ps('J', 2) : _currentScreen->clearEntireScreen ( ); break; case token_csi_ps('J', 3) : clearHistory(); break; case token_csi_ps('g', 0) : _currentScreen->changeTabStop (false ); break; //VT100 case token_csi_ps('g', 3) : _currentScreen->clearTabStops ( ); break; //VT100 case token_csi_ps('h', 4) : _currentScreen-> setMode (MODE_Insert ); break; case token_csi_ps('h', 20) : setMode (MODE_NewLine ); break; case token_csi_ps('i', 0) : /* IGNORE: attached printer */ break; //VT100 case token_csi_ps('l', 4) : _currentScreen-> resetMode (MODE_Insert ); break; case token_csi_ps('l', 20) : resetMode (MODE_NewLine ); break; case token_csi_ps('s', 0) : saveCursor ( ); break; case token_csi_ps('u', 0) : restoreCursor ( ); break; case token_csi_ps('m', 0) : _currentScreen->setDefaultRendition ( ); break; case token_csi_ps('m', 1) : _currentScreen-> setRendition (RE_BOLD ); break; //VT100 case token_csi_ps('m', 2) : _currentScreen-> setRendition (RE_FAINT ); break; case token_csi_ps('m', 3) : _currentScreen-> setRendition (RE_ITALIC ); break; //VT100 case token_csi_ps('m', 4) : _currentScreen-> setRendition (RE_UNDERLINE); break; //VT100 case token_csi_ps('m', 5) : _currentScreen-> setRendition (RE_BLINK ); break; //VT100 case token_csi_ps('m', 7) : _currentScreen-> setRendition (RE_REVERSE ); break; case token_csi_ps('m', 8) : _currentScreen-> setRendition (RE_CONCEAL ); break; case token_csi_ps('m', 9) : _currentScreen-> setRendition (RE_STRIKEOUT); break; case token_csi_ps('m', 53) : _currentScreen-> setRendition (RE_OVERLINE ); break; case token_csi_ps('m', 10) : /* IGNORED: mapping related */ break; //LINUX case token_csi_ps('m', 11) : /* IGNORED: mapping related */ break; //LINUX case token_csi_ps('m', 12) : /* IGNORED: mapping related */ break; //LINUX case token_csi_ps('m', 21) : _currentScreen->resetRendition (RE_BOLD ); break; case token_csi_ps('m', 22) : _currentScreen->resetRendition (RE_BOLD ); _currentScreen->resetRendition (RE_FAINT ); break; case token_csi_ps('m', 23) : _currentScreen->resetRendition (RE_ITALIC ); break; //VT100 case token_csi_ps('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE); break; case token_csi_ps('m', 25) : _currentScreen->resetRendition (RE_BLINK ); break; case token_csi_ps('m', 27) : _currentScreen->resetRendition (RE_REVERSE ); break; case token_csi_ps('m', 28) : _currentScreen->resetRendition (RE_CONCEAL ); break; case token_csi_ps('m', 29) : _currentScreen->resetRendition (RE_STRIKEOUT); break; case token_csi_ps('m', 55) : _currentScreen->resetRendition (RE_OVERLINE ); break; case token_csi_ps('m', 30) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 0); break; case token_csi_ps('m', 31) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 1); break; case token_csi_ps('m', 32) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 2); break; case token_csi_ps('m', 33) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 3); break; case token_csi_ps('m', 34) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 4); break; case token_csi_ps('m', 35) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 5); break; case token_csi_ps('m', 36) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 6); break; case token_csi_ps('m', 37) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 7); break; case token_csi_ps('m', 38) : _currentScreen->setForeColor (p, q); break; case token_csi_ps('m', 39) : _currentScreen->setForeColor (COLOR_SPACE_DEFAULT, 0); break; case token_csi_ps('m', 40) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 0); break; case token_csi_ps('m', 41) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 1); break; case token_csi_ps('m', 42) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 2); break; case token_csi_ps('m', 43) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 3); break; case token_csi_ps('m', 44) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 4); break; case token_csi_ps('m', 45) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 5); break; case token_csi_ps('m', 46) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 6); break; case token_csi_ps('m', 47) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 7); break; case token_csi_ps('m', 48) : _currentScreen->setBackColor (p, q); break; case token_csi_ps('m', 49) : _currentScreen->setBackColor (COLOR_SPACE_DEFAULT, 1); break; case token_csi_ps('m', 90) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 8); break; case token_csi_ps('m', 91) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 9); break; case token_csi_ps('m', 92) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 10); break; case token_csi_ps('m', 93) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 11); break; case token_csi_ps('m', 94) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 12); break; case token_csi_ps('m', 95) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 13); break; case token_csi_ps('m', 96) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 14); break; case token_csi_ps('m', 97) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 15); break; case token_csi_ps('m', 100) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 8); break; case token_csi_ps('m', 101) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 9); break; case token_csi_ps('m', 102) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 10); break; case token_csi_ps('m', 103) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 11); break; case token_csi_ps('m', 104) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 12); break; case token_csi_ps('m', 105) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 13); break; case token_csi_ps('m', 106) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 14); break; case token_csi_ps('m', 107) : _currentScreen->setBackColor (COLOR_SPACE_SYSTEM, 15); break; case token_csi_ps('n', 5) : reportStatus ( ); break; case token_csi_ps('n', 6) : reportCursorPosition ( ); break; case token_csi_ps('q', 0) : /* IGNORED: LEDs off */ break; //VT100 case token_csi_ps('q', 1) : /* IGNORED: LED1 on */ break; //VT100 case token_csi_ps('q', 2) : /* IGNORED: LED2 on */ break; //VT100 case token_csi_ps('q', 3) : /* IGNORED: LED3 on */ break; //VT100 case token_csi_ps('q', 4) : /* IGNORED: LED4 on */ break; //VT100 case token_csi_ps('x', 0) : reportTerminalParms ( 2); break; //VT100 case token_csi_ps('x', 1) : reportTerminalParms ( 3); break; //VT100 case token_csi_pn('@' ) : _currentScreen->insertChars (p ); break; case token_csi_pn('A' ) : _currentScreen->cursorUp (p ); break; //VT100 case token_csi_pn('B' ) : _currentScreen->cursorDown (p ); break; //VT100 case token_csi_pn('C' ) : _currentScreen->cursorRight (p ); break; //VT100 case token_csi_pn('D' ) : _currentScreen->cursorLeft (p ); break; //VT100 case token_csi_pn('E' ) : /* Not implemented: cursor next p lines */ break; //VT100 case token_csi_pn('F' ) : /* Not implemented: cursor preceding p lines */ break; //VT100 case token_csi_pn('G' ) : _currentScreen->setCursorX (p ); break; //LINUX case token_csi_pn('H' ) : _currentScreen->setCursorYX (p, q); break; //VT100 case token_csi_pn('I' ) : _currentScreen->tab (p ); break; case token_csi_pn('L' ) : _currentScreen->insertLines (p ); break; case token_csi_pn('M' ) : _currentScreen->deleteLines (p ); break; case token_csi_pn('P' ) : _currentScreen->deleteChars (p ); break; case token_csi_pn('S' ) : _currentScreen->scrollUp (p ); break; case token_csi_pn('T' ) : _currentScreen->scrollDown (p ); break; case token_csi_pn('X' ) : _currentScreen->eraseChars (p ); break; case token_csi_pn('Z' ) : _currentScreen->backtab (p ); break; case token_csi_pn('b' ) : _currentScreen->repeatChars (p ); break; case token_csi_pn('c' ) : reportTerminalType ( ); break; //VT100 case token_csi_pn('d' ) : _currentScreen->setCursorY (p ); break; //LINUX case token_csi_pn('f' ) : _currentScreen->setCursorYX (p, q); break; //VT100 case token_csi_pn('r' ) : setMargins (p, q); break; //VT100 case token_csi_pn('y' ) : /* IGNORED: Confidence test */ break; //VT100 case token_csi_pr('h', 1) : setMode (MODE_AppCuKeys); break; //VT100 case token_csi_pr('l', 1) : resetMode (MODE_AppCuKeys); break; //VT100 case token_csi_pr('s', 1) : saveMode (MODE_AppCuKeys); break; //FIXME case token_csi_pr('r', 1) : restoreMode (MODE_AppCuKeys); break; //FIXME case token_csi_pr('l', 2) : resetMode (MODE_Ansi ); break; //VT100 case token_csi_pr('h', 3) : setMode (MODE_132Columns); break; //VT100 case token_csi_pr('l', 3) : resetMode (MODE_132Columns); break; //VT100 case token_csi_pr('h', 4) : /* IGNORED: soft scrolling */ break; //VT100 case token_csi_pr('l', 4) : /* IGNORED: soft scrolling */ break; //VT100 case token_csi_pr('h', 5) : _currentScreen-> setMode (MODE_Screen ); break; //VT100 case token_csi_pr('l', 5) : _currentScreen-> resetMode (MODE_Screen ); break; //VT100 case token_csi_pr('h', 6) : _currentScreen-> setMode (MODE_Origin ); break; //VT100 case token_csi_pr('l', 6) : _currentScreen-> resetMode (MODE_Origin ); break; //VT100 case token_csi_pr('s', 6) : _currentScreen-> saveMode (MODE_Origin ); break; //FIXME case token_csi_pr('r', 6) : _currentScreen->restoreMode (MODE_Origin ); break; //FIXME case token_csi_pr('h', 7) : _currentScreen-> setMode (MODE_Wrap ); break; //VT100 case token_csi_pr('l', 7) : _currentScreen-> resetMode (MODE_Wrap ); break; //VT100 case token_csi_pr('s', 7) : _currentScreen-> saveMode (MODE_Wrap ); break; //FIXME case token_csi_pr('r', 7) : _currentScreen->restoreMode (MODE_Wrap ); break; //FIXME case token_csi_pr('h', 8) : /* IGNORED: autorepeat on */ break; //VT100 case token_csi_pr('l', 8) : /* IGNORED: autorepeat off */ break; //VT100 case token_csi_pr('s', 8) : /* IGNORED: autorepeat on */ break; //VT100 case token_csi_pr('r', 8) : /* IGNORED: autorepeat off */ break; //VT100 case token_csi_pr('h', 9) : /* IGNORED: interlace */ break; //VT100 case token_csi_pr('l', 9) : /* IGNORED: interlace */ break; //VT100 case token_csi_pr('s', 9) : /* IGNORED: interlace */ break; //VT100 case token_csi_pr('r', 9) : /* IGNORED: interlace */ break; //VT100 case token_csi_pr('h', 12) : /* IGNORED: Cursor blink */ break; //att610 case token_csi_pr('l', 12) : /* IGNORED: Cursor blink */ break; //att610 case token_csi_pr('s', 12) : /* IGNORED: Cursor blink */ break; //att610 case token_csi_pr('r', 12) : /* IGNORED: Cursor blink */ break; //att610 case token_csi_pr('h', 25) : setMode (MODE_Cursor ); break; //VT100 case token_csi_pr('l', 25) : resetMode (MODE_Cursor ); break; //VT100 case token_csi_pr('s', 25) : saveMode (MODE_Cursor ); break; //VT100 case token_csi_pr('r', 25) : restoreMode (MODE_Cursor ); break; //VT100 case token_csi_pr('h', 40) : setMode(MODE_Allow132Columns ); break; // XTERM case token_csi_pr('l', 40) : resetMode(MODE_Allow132Columns ); break; // XTERM case token_csi_pr('h', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM case token_csi_pr('l', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM case token_csi_pr('s', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM case token_csi_pr('r', 41) : /* IGNORED: obsolete more(1) fix */ break; //XTERM case token_csi_pr('h', 47) : setMode (MODE_AppScreen); break; //VT100 case token_csi_pr('l', 47) : resetMode (MODE_AppScreen); break; //VT100 case token_csi_pr('s', 47) : saveMode (MODE_AppScreen); break; //XTERM case token_csi_pr('r', 47) : restoreMode (MODE_AppScreen); break; //XTERM case token_csi_pr('h', 67) : /* IGNORED: DECBKM */ break; //XTERM case token_csi_pr('l', 67) : /* IGNORED: DECBKM */ break; //XTERM case token_csi_pr('s', 67) : /* IGNORED: DECBKM */ break; //XTERM case token_csi_pr('r', 67) : /* IGNORED: DECBKM */ break; //XTERM // XTerm defines the following modes: // SET_VT200_MOUSE 1000 // SET_VT200_HIGHLIGHT_MOUSE 1001 // SET_BTN_EVENT_MOUSE 1002 // SET_ANY_EVENT_MOUSE 1003 // //Note about mouse modes: //There are four mouse modes which xterm-compatible terminals can support - 1000,1001,1002,1003 //Konsole currently supports mode 1000 (basic mouse press and release) and mode 1002 (dragging the mouse). //TODO: Implementation of mouse modes 1001 (something called hilight tracking) and //1003 (a slight variation on dragging the mouse) // case token_csi_pr('h', 1000) : setMode (MODE_Mouse1000); break; //XTERM case token_csi_pr('l', 1000) : resetMode (MODE_Mouse1000); break; //XTERM case token_csi_pr('s', 1000) : saveMode (MODE_Mouse1000); break; //XTERM case token_csi_pr('r', 1000) : restoreMode (MODE_Mouse1000); break; //XTERM case token_csi_pr('h', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM case token_csi_pr('l', 1001) : resetMode (MODE_Mouse1001); break; //XTERM case token_csi_pr('s', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM case token_csi_pr('r', 1001) : /* IGNORED: hilite mouse tracking */ break; //XTERM case token_csi_pr('h', 1002) : setMode (MODE_Mouse1002); break; //XTERM case token_csi_pr('l', 1002) : resetMode (MODE_Mouse1002); break; //XTERM case token_csi_pr('s', 1002) : saveMode (MODE_Mouse1002); break; //XTERM case token_csi_pr('r', 1002) : restoreMode (MODE_Mouse1002); break; //XTERM case token_csi_pr('h', 1003) : setMode (MODE_Mouse1003); break; //XTERM case token_csi_pr('l', 1003) : resetMode (MODE_Mouse1003); break; //XTERM case token_csi_pr('s', 1003) : saveMode (MODE_Mouse1003); break; //XTERM case token_csi_pr('r', 1003) : restoreMode (MODE_Mouse1003); break; //XTERM case token_csi_pr('h', 1004) : _reportFocusEvents = true; break; case token_csi_pr('l', 1004) : _reportFocusEvents = false; break; case token_csi_pr('h', 1005) : setMode (MODE_Mouse1005); break; //XTERM case token_csi_pr('l', 1005) : resetMode (MODE_Mouse1005); break; //XTERM case token_csi_pr('s', 1005) : saveMode (MODE_Mouse1005); break; //XTERM case token_csi_pr('r', 1005) : restoreMode (MODE_Mouse1005); break; //XTERM case token_csi_pr('h', 1006) : setMode (MODE_Mouse1006); break; //XTERM case token_csi_pr('l', 1006) : resetMode (MODE_Mouse1006); break; //XTERM case token_csi_pr('s', 1006) : saveMode (MODE_Mouse1006); break; //XTERM case token_csi_pr('r', 1006) : restoreMode (MODE_Mouse1006); break; //XTERM case token_csi_pr('h', 1007) : setMode (MODE_Mouse1007); break; //XTERM case token_csi_pr('l', 1007) : resetMode (MODE_Mouse1007); break; //XTERM case token_csi_pr('s', 1007) : saveMode (MODE_Mouse1007); break; //XTERM case token_csi_pr('r', 1007) : restoreMode (MODE_Mouse1007); break; //XTERM case token_csi_pr('h', 1015) : setMode (MODE_Mouse1015); break; //URXVT case token_csi_pr('l', 1015) : resetMode (MODE_Mouse1015); break; //URXVT case token_csi_pr('s', 1015) : saveMode (MODE_Mouse1015); break; //URXVT case token_csi_pr('r', 1015) : restoreMode (MODE_Mouse1015); break; //URXVT case token_csi_pr('h', 1034) : /* IGNORED: 8bitinput activation */ break; //XTERM case token_csi_pr('h', 1047) : setMode (MODE_AppScreen); break; //XTERM case token_csi_pr('l', 1047) : _screen[1]->clearEntireScreen(); resetMode(MODE_AppScreen); break; //XTERM case token_csi_pr('s', 1047) : saveMode (MODE_AppScreen); break; //XTERM case token_csi_pr('r', 1047) : restoreMode (MODE_AppScreen); break; //XTERM //FIXME: Unitoken: save translations case token_csi_pr('h', 1048) : saveCursor ( ); break; //XTERM case token_csi_pr('l', 1048) : restoreCursor ( ); break; //XTERM case token_csi_pr('s', 1048) : saveCursor ( ); break; //XTERM case token_csi_pr('r', 1048) : restoreCursor ( ); break; //XTERM //FIXME: every once new sequences like this pop up in xterm. // Here's a guess of what they could mean. case token_csi_pr('h', 1049) : saveCursor(); _screen[1]->clearEntireScreen(); setMode(MODE_AppScreen); break; //XTERM case token_csi_pr('l', 1049) : resetMode(MODE_AppScreen); restoreCursor(); break; //XTERM case token_csi_pr('h', 2004) : setMode (MODE_BracketedPaste); break; //XTERM case token_csi_pr('l', 2004) : resetMode (MODE_BracketedPaste); break; //XTERM case token_csi_pr('s', 2004) : saveMode (MODE_BracketedPaste); break; //XTERM case token_csi_pr('r', 2004) : restoreMode (MODE_BracketedPaste); break; //XTERM // Set Cursor Style (DECSCUSR), VT520, with the extra xterm sequences // the first one is a special case, 'ESC[ q', which mimics 'ESC[1 q' case token_csi_sp ('q' ) : emit setCursorStyleRequest(Enum::BlockCursor, true); break; case token_csi_psp('q', 0) : emit setCursorStyleRequest(Enum::BlockCursor, true); break; case token_csi_psp('q', 1) : emit setCursorStyleRequest(Enum::BlockCursor, true); break; case token_csi_psp('q', 2) : emit setCursorStyleRequest(Enum::BlockCursor, false); break; case token_csi_psp('q', 3) : emit setCursorStyleRequest(Enum::UnderlineCursor, true); break; case token_csi_psp('q', 4) : emit setCursorStyleRequest(Enum::UnderlineCursor, false); break; case token_csi_psp('q', 5) : emit setCursorStyleRequest(Enum::IBeamCursor, true); break; case token_csi_psp('q', 6) : emit setCursorStyleRequest(Enum::IBeamCursor, false); break; //FIXME: weird DEC reset sequence case token_csi_pe('p' ) : /* IGNORED: reset ( ) */ break; //FIXME: when changing between vt52 and ansi mode evtl do some resetting. case token_vt52('A' ) : _currentScreen->cursorUp ( 1); break; //VT52 case token_vt52('B' ) : _currentScreen->cursorDown ( 1); break; //VT52 case token_vt52('C' ) : _currentScreen->cursorRight ( 1); break; //VT52 case token_vt52('D' ) : _currentScreen->cursorLeft ( 1); break; //VT52 case token_vt52('F' ) : setAndUseCharset (0, '0'); break; //VT52 case token_vt52('G' ) : setAndUseCharset (0, 'B'); break; //VT52 case token_vt52('H' ) : _currentScreen->setCursorYX (1,1 ); break; //VT52 case token_vt52('I' ) : _currentScreen->reverseIndex ( ); break; //VT52 case token_vt52('J' ) : _currentScreen->clearToEndOfScreen ( ); break; //VT52 case token_vt52('K' ) : _currentScreen->clearToEndOfLine ( ); break; //VT52 case token_vt52('Y' ) : _currentScreen->setCursorYX (p-31,q-31 ); break; //VT52 case token_vt52('Z' ) : reportTerminalType ( ); break; //VT52 case token_vt52('<' ) : setMode (MODE_Ansi ); break; //VT52 case token_vt52('=' ) : setMode (MODE_AppKeyPad); break; //VT52 case token_vt52('>' ) : resetMode (MODE_AppKeyPad); break; //VT52 case token_csi_pg('c' ) : reportSecondaryAttributes( ); break; //VT100 default: reportDecodingError(); break; }; } void Vt102Emulation::clearScreenAndSetColumns(int columnCount) { setImageSize(_currentScreen->getLines(),columnCount); clearEntireScreen(); setDefaultMargins(); _currentScreen->setCursorYX(0,0); } void Vt102Emulation::sendString(const QByteArray& s) { emit sendData(s); } void Vt102Emulation::reportCursorPosition() { char tmp[30]; snprintf(tmp, sizeof(tmp), "\033[%d;%dR", _currentScreen->getCursorY()+1, _currentScreen->getCursorX()+1); sendString(tmp); } void Vt102Emulation::reportTerminalType() { // Primary device attribute response (Request was: ^[[0c or ^[[c (from TT321 Users Guide)) // VT220: ^[[?63;1;2;3;6;7;8c (list deps on emul. capabilities) // VT100: ^[[?1;2c // VT101: ^[[?1;0c // VT102: ^[[?6v if (getMode(MODE_Ansi)) { sendString("\033[?1;2c"); // I'm a VT100 } else { sendString("\033/Z"); // I'm a VT52 } } void Vt102Emulation::reportSecondaryAttributes() { // Secondary device attribute response (Request was: ^[[>0c or ^[[>c) if (getMode(MODE_Ansi)) { sendString("\033[>0;115;0c"); // Why 115? ;) } else { sendString("\033/Z"); // FIXME I don't think VT52 knows about it but kept for } // konsoles backward compatibility. } /* DECREPTPARM – Report Terminal Parameters ESC [ ; ; ; ; ; ; x http://vt100.net/docs/vt100-ug/chapter3.html */ void Vt102Emulation::reportTerminalParms(int p) { char tmp[100]; /* sol=1: This message is a request; report in response to a request. par=1: No parity set nbits=1: 8 bits per character xspeed=112: 9600 rspeed=112: 9600 clkmul=1: The bit rate multiplier is 16. flags=0: None */ snprintf(tmp, sizeof(tmp), "\033[%d;1;1;112;112;1;0x", p); // not really true. sendString(tmp); } void Vt102Emulation::reportStatus() { sendString("\033[0n"); //VT100. Device status report. 0 = Ready. } void Vt102Emulation::reportAnswerBack() { // FIXME - Test this with VTTEST // This is really obsolete VT100 stuff. const char *ANSWER_BACK = ""; sendString(ANSWER_BACK); } /*! `cx',`cy' are 1-based. `cb' indicates the button pressed or released (0-2) or scroll event (4-5). eventType represents the kind of mouse action that occurred: 0 = Mouse button press 1 = Mouse drag 2 = Mouse button release */ void Vt102Emulation::sendMouseEvent(int cb, int cx, int cy, int eventType) { if (cx < 1 || cy < 1) { return; } // With the exception of the 1006 mode, button release is encoded in cb. // Note that if multiple extensions are enabled, the 1006 is used, so it's okay to check for only that. if (eventType == 2 && !getMode(MODE_Mouse1006)) { cb = 3; } // normal buttons are passed as 0x20 + button, // mouse wheel (buttons 4,5) as 0x5c + button if (cb >= 4) { cb += 0x3c; } //Mouse motion handling if ((getMode(MODE_Mouse1002) || getMode(MODE_Mouse1003)) && eventType == 1) { cb += 0x20; //add 32 to signify motion event } char command[40]; command[0] = '\0'; // Check the extensions in decreasing order of preference. Encoding the release event above assumes that 1006 comes first. if (getMode(MODE_Mouse1006)) { snprintf(command, sizeof(command), "\033[<%d;%d;%d%c", cb, cx, cy, eventType == 2 ? 'm' : 'M'); } else if (getMode(MODE_Mouse1015)) { snprintf(command, sizeof(command), "\033[%d;%d;%dM", cb + 0x20, cx, cy); } else if (getMode(MODE_Mouse1005)) { if (cx <= 2015 && cy <= 2015) { // The xterm extension uses UTF-8 (up to 2 bytes) to encode // coordinate+32, no matter what the locale is. We could easily // convert manually, but QString can also do it for us. QChar coords[2]; coords[0] = cx + 0x20; coords[1] = cy + 0x20; QString coordsStr = QString(coords, 2); QByteArray utf8 = coordsStr.toUtf8(); snprintf(command, sizeof(command), "\033[M%c%s", cb + 0x20, utf8.constData()); } } else if (cx <= 223 && cy <= 223) { snprintf(command, sizeof(command), "\033[M%c%c%c", cb + 0x20, cx + 0x20, cy + 0x20); } sendString(command); } /** * The focus lost event can be used by Vim (or other terminal applications) * to recognize that the konsole window has lost focus. * The escape sequence is also used by iTerm2. * Vim needs the following plugin to be installed to convert the escape * sequence into the FocusLost autocmd: https://github.com/sjl/vitality.vim */ void Vt102Emulation::focusLost() { if (_reportFocusEvents) { sendString("\033[O"); } } /** * The focus gained event can be used by Vim (or other terminal applications) * to recognize that the konsole window has gained focus again. * The escape sequence is also used by iTerm2. * Vim needs the following plugin to be installed to convert the escape * sequence into the FocusGained autocmd: https://github.com/sjl/vitality.vim */ void Vt102Emulation::focusGained() { if (_reportFocusEvents) { sendString("\033[I"); } } void Vt102Emulation::sendText(const QString &text) { if (!text.isEmpty()) { QKeyEvent event(QEvent::KeyPress, 0, Qt::NoModifier, text); sendKeyEvent(&event); // expose as a big fat keypress event } } void Vt102Emulation::sendKeyEvent(QKeyEvent *event) { const Qt::KeyboardModifiers modifiers = event->modifiers(); KeyboardTranslator::States states = KeyboardTranslator::NoState; TerminalDisplay * currentView = _currentScreen->currentTerminalDisplay(); bool isReadOnly = false; if (currentView != nullptr && currentView->sessionController() != nullptr) { isReadOnly = currentView->sessionController()->isReadOnly(); } // get current states if (getMode(MODE_NewLine)) { states |= KeyboardTranslator::NewLineState; } if (getMode(MODE_Ansi)) { states |= KeyboardTranslator::AnsiState; } if (getMode(MODE_AppCuKeys)) { states |= KeyboardTranslator::CursorKeysState; } if (getMode(MODE_AppScreen)) { states |= KeyboardTranslator::AlternateScreenState; } if (getMode(MODE_AppKeyPad) && ((modifiers &Qt::KeypadModifier) != 0u)) { states |= KeyboardTranslator::ApplicationKeypadState; } if (!isReadOnly) { // check flow control state if ((modifiers &Qt::ControlModifier) != 0u) { switch (event->key()) { case Qt::Key_S: emit flowControlKeyPressed(true); break; case Qt::Key_Q: case Qt::Key_C: // cancel flow control emit flowControlKeyPressed(false); break; } } } // look up key binding if (_keyTranslator != nullptr) { KeyboardTranslator::Entry entry = _keyTranslator->findEntry( event->key(), modifiers, states); // send result to terminal QByteArray textToSend; // special handling for the Alt (aka. Meta) modifier. pressing // Alt+[Character] results in Esc+[Character] being sent // (unless there is an entry defined for this particular combination // in the keyboard modifier) const bool wantsAltModifier = ((entry.modifiers() & entry.modifierMask() & Qt::AltModifier) != 0u); const bool wantsMetaModifier = ((entry.modifiers() & entry.modifierMask() & Qt::MetaModifier) != 0u); const bool wantsAnyModifier = ((entry.state() & entry.stateMask() & KeyboardTranslator::AnyModifierState) != 0); if ( ((modifiers & Qt::AltModifier) != 0u) && !(wantsAltModifier || wantsAnyModifier) && !event->text().isEmpty() ) { textToSend.prepend("\033"); } if ( ((modifiers & Qt::MetaModifier) != 0u) && !(wantsMetaModifier || wantsAnyModifier) && !event->text().isEmpty() ) { textToSend.prepend("\030@s"); } if ( entry.command() != KeyboardTranslator::NoCommand ) { if ((entry.command() & KeyboardTranslator::EraseCommand) != 0) { textToSend += eraseChar(); } if (currentView != nullptr) { if ((entry.command() & KeyboardTranslator::ScrollPageUpCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollPages, -1); } else if ((entry.command() & KeyboardTranslator::ScrollPageDownCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollPages, 1); } else if ((entry.command() & KeyboardTranslator::ScrollLineUpCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollLines, -1); } else if ((entry.command() & KeyboardTranslator::ScrollLineDownCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollLines, 1); } else if ((entry.command() & KeyboardTranslator::ScrollUpToTopCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollLines, - currentView->screenWindow()->currentLine()); } else if ((entry.command() & KeyboardTranslator::ScrollDownToBottomCommand) != 0) { currentView->scrollScreenWindow(ScreenWindow::ScrollLines, lineCount()); } } } else if (!entry.text().isEmpty()) { textToSend += entry.text(true,modifiers); } else { textToSend += _codec->fromUnicode(event->text()); } if (!isReadOnly) { emit sendData(textToSend); } } else { if (!isReadOnly) { // print an error message to the terminal if no key translator has been // set QString translatorError = i18n("No keyboard translator available. " "The information needed to convert key presses " "into characters to send to the terminal " "is missing."); reset(); receiveData(translatorError.toLatin1().constData(), translatorError.count()); } } } /* ------------------------------------------------------------------------- */ /* */ /* VT100 Charsets */ /* */ /* ------------------------------------------------------------------------- */ // Character Set Conversion ------------------------------------------------ -- /* The processing contains a VT100 specific code translation layer. It's still in use and mainly responsible for the line drawing graphics. These and some other glyphs are assigned to codes (0x5f-0xfe) normally occupied by the latin letters. Since this codes also appear within control sequences, the extra code conversion does not permute with the tokenizer and is placed behind it in the pipeline. It only applies to tokens, which represent plain characters. This conversion it eventually continued in TerminalDisplay.C, since it might involve VT100 enhanced fonts, which have these particular glyphs allocated in (0x00-0x1f) in their code page. */ #define CHARSET _charset[_currentScreen == _screen[1]] // Apply current character map. unsigned int Vt102Emulation::applyCharset(uint c) { if (CHARSET.graphic && 0x5f <= c && c <= 0x7e) { return vt100_graphics[c - 0x5f]; } if (CHARSET.pound && c == '#') { return 0xa3; //This mode is obsolete } return c; } /* "Charset" related part of the emulation state. This configures the VT100 charset filter. While most operation work on the current _screen, the following two are different. */ void Vt102Emulation::resetCharset(int scrno) { _charset[scrno].cu_cs = 0; qstrncpy(_charset[scrno].charset, "BBBB", 4); _charset[scrno].sa_graphic = false; _charset[scrno].sa_pound = false; _charset[scrno].graphic = false; _charset[scrno].pound = false; } void Vt102Emulation::setCharset(int n, int cs) // on both screens. { _charset[0].charset[n & 3] = cs; useCharset(_charset[0].cu_cs); _charset[1].charset[n & 3] = cs; useCharset(_charset[1].cu_cs); } void Vt102Emulation::setAndUseCharset(int n, int cs) { CHARSET.charset[n & 3] = cs; useCharset(n & 3); } void Vt102Emulation::useCharset(int n) { CHARSET.cu_cs = n & 3; CHARSET.graphic = (CHARSET.charset[n & 3] == '0'); CHARSET.pound = (CHARSET.charset[n & 3] == 'A'); //This mode is obsolete } void Vt102Emulation::setDefaultMargins() { _screen[0]->setDefaultMargins(); _screen[1]->setDefaultMargins(); } void Vt102Emulation::setMargins(int t, int b) { _screen[0]->setMargins(t, b); _screen[1]->setMargins(t, b); } void Vt102Emulation::saveCursor() { CHARSET.sa_graphic = CHARSET.graphic; CHARSET.sa_pound = CHARSET.pound; //This mode is obsolete // we are not clear about these //sa_charset = charsets[cScreen->_charset]; //sa_charset_num = cScreen->_charset; _currentScreen->saveCursor(); } void Vt102Emulation::restoreCursor() { CHARSET.graphic = CHARSET.sa_graphic; CHARSET.pound = CHARSET.sa_pound; //This mode is obsolete _currentScreen->restoreCursor(); } /* ------------------------------------------------------------------------- */ /* */ /* Mode Operations */ /* */ /* ------------------------------------------------------------------------- */ /* Some of the emulations state is either added to the state of the screens. This causes some scoping problems, since different emulations choose to located the mode either to the current _screen or to both. For strange reasons, the extend of the rendition attributes ranges over all screens and not over the actual _screen. We decided on the precise precise extend, somehow. */ // "Mode" related part of the state. These are all booleans. void Vt102Emulation::resetModes() { // MODE_Allow132Columns is not reset here // to match Xterm's behavior (see Xterm's VTReset() function) resetMode(MODE_132Columns); saveMode(MODE_132Columns); resetMode(MODE_Mouse1000); saveMode(MODE_Mouse1000); resetMode(MODE_Mouse1001); saveMode(MODE_Mouse1001); resetMode(MODE_Mouse1002); saveMode(MODE_Mouse1002); resetMode(MODE_Mouse1003); saveMode(MODE_Mouse1003); resetMode(MODE_Mouse1005); saveMode(MODE_Mouse1005); resetMode(MODE_Mouse1006); saveMode(MODE_Mouse1006); resetMode(MODE_Mouse1007); saveMode(MODE_Mouse1007); resetMode(MODE_Mouse1015); saveMode(MODE_Mouse1015); resetMode(MODE_BracketedPaste); saveMode(MODE_BracketedPaste); resetMode(MODE_AppScreen); saveMode(MODE_AppScreen); resetMode(MODE_AppCuKeys); saveMode(MODE_AppCuKeys); resetMode(MODE_AppKeyPad); saveMode(MODE_AppKeyPad); resetMode(MODE_NewLine); setMode(MODE_Ansi); } void Vt102Emulation::setMode(int m) { _currentModes.mode[m] = true; switch (m) { case MODE_132Columns: if (getMode(MODE_Allow132Columns)) { clearScreenAndSetColumns(132); } else { _currentModes.mode[m] = false; } break; case MODE_Mouse1000: case MODE_Mouse1001: case MODE_Mouse1002: case MODE_Mouse1003: - emit programUsesMouseChanged(false); + emit programRequestsMouseTracking(true); break; case MODE_Mouse1007: emit enableAlternateScrolling(true); break; case MODE_BracketedPaste: emit programBracketedPasteModeChanged(true); break; case MODE_AppScreen: _screen[1]->setDefaultRendition(); _screen[1]->clearSelection(); setScreen(1); break; } // FIXME: Currently this has a redundant condition as MODES_SCREEN is 6 // and MODE_NewLine is 5 if (m < MODES_SCREEN || m == MODE_NewLine) { _screen[0]->setMode(m); _screen[1]->setMode(m); } } void Vt102Emulation::resetMode(int m) { _currentModes.mode[m] = false; switch (m) { case MODE_132Columns: if (getMode(MODE_Allow132Columns)) { clearScreenAndSetColumns(80); } break; case MODE_Mouse1000: case MODE_Mouse1001: case MODE_Mouse1002: case MODE_Mouse1003: - emit programUsesMouseChanged(true); + emit programRequestsMouseTracking(false); break; case MODE_Mouse1007: emit enableAlternateScrolling(false); break; case MODE_BracketedPaste: emit programBracketedPasteModeChanged(false); break; case MODE_AppScreen: _screen[0]->clearSelection(); setScreen(0); break; } // FIXME: Currently this has a redundant condition as MODES_SCREEN is 6 // and MODE_NewLine is 5 if (m < MODES_SCREEN || m == MODE_NewLine) { _screen[0]->resetMode(m); _screen[1]->resetMode(m); } } void Vt102Emulation::saveMode(int m) { _savedModes.mode[m] = _currentModes.mode[m]; } void Vt102Emulation::restoreMode(int m) { if (_savedModes.mode[m]) { setMode(m); } else { resetMode(m); } } bool Vt102Emulation::getMode(int m) { return _currentModes.mode[m]; } char Vt102Emulation::eraseChar() const { KeyboardTranslator::Entry entry = _keyTranslator->findEntry( Qt::Key_Backspace, nullptr, nullptr); if (entry.text().count() > 0) { return entry.text().at(0); } else { return '\b'; } } #if 0 // print contents of the scan buffer static void hexdump(int *s, int len) { int i; for (i = 0; i < len; i++) { if (s[i] == '\\') { printf("\\\\"); } else if ((s[i]) > 32 && s[i] < 127) { printf("%c", s[i]); } else { printf("\\%04x(hex)", s[i]); } } } #endif // return contents of the scan buffer static QString hexdump2(int *s, int len) { int i; char dump[128]; QString returnDump; for (i = 0; i < len; i++) { if (s[i] == '\\') { snprintf(dump, sizeof(dump), "%s", "\\\\"); } else if ((s[i]) > 32 && s[i] < 127) { snprintf(dump, sizeof(dump), "%c", s[i]); } else { snprintf(dump, sizeof(dump), "\\%04x(hex)", s[i]); } returnDump.append(QLatin1String(dump)); } return returnDump; } void Vt102Emulation::reportDecodingError() { if (tokenBufferPos == 0 || (tokenBufferPos == 1 && (tokenBuffer[0] & 0xff) >= 32)) { return; } // printf("Undecodable sequence: "); // hexdump(tokenBuffer, tokenBufferPos); // printf("\n"); QString outputError = QStringLiteral("Undecodable sequence: "); outputError.append(hexdump2(tokenBuffer, tokenBufferPos)); //qDebug() << outputError; }