diff --git a/src/Emulation.h b/src/Emulation.h index 78eba56b..e8c71b89 100644 --- a/src/Emulation.h +++ b/src/Emulation.h @@ -1,527 +1,525 @@ /* 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 is interested in Mouse * Tracking events. * * 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 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 (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 usesMouseTracking This will be true if the program is interested * in Mouse Tracking events or false otherwise. */ 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. + * Emitted when the program running in the terminal wishes to update + * certain session attributes. This allows terminal programs to customize + * certain 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. + * is received in an input string, where ARG is a number specifying + * what should be changed 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. + * @param attribute Specifies which session attribute 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. + *
  • 0 - Set window icon text and session title to @p newValue
  • + *
  • 1 - Set window icon text to @p newValue
  • + *
  • 2 - Set session title to @p newValue
  • + *
  • 11 - Set the session's default background color to @p newValue, + * where @p newValue can be an HTML-style string ("#RRGGBB") or a + * named color (e.g. 'red', 'blue'). For more details see: + * https://doc.qt.io/qt-5/qcolor.html#setNamedColor + *
  • + *
  • 31 - Supposedly treats @p newValue as a URL and opens it (NOT + * IMPLEMENTED) + *
  • + *
  • 32 - Sets the icon associated with the session. @p newValue + * 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') *
  • - *
  • 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 + * @param newValue Specifies the new value of the session attribute */ - - void titleChanged(int title, const QString &newTitle); + void sessionAttributeChanged(int attribute, const QString &newValue); /** * 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 setUsesMouseTracking(bool usesMouseTracking); void bracketedPasteModeChanged(bool bracketedPasteMode); private: Q_DISABLE_COPY(Emulation) bool _usesMouseTracking; bool _bracketedPasteMode; QTimer _bulkTimer1; QTimer _bulkTimer2; bool _imageSizeInitialized; }; } #endif // ifndef EMULATION_H diff --git a/src/Session.cpp b/src/Session.cpp index 26cc547e..08ec9409 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -1,1750 +1,1751 @@ /* 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::sessionAttributeChanged, this, &Konsole::Session::setSessionAttribute); 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 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->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) +void Session::setSessionAttribute(int what, const QString& caption) { - //set to true if anything is actually changed (eg. old _nameTitle != new _nameTitle ) + // set to true if anything has 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(); + emit sessionAttributeChanged(); } } 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(); + emit sessionAttributeChanged(); 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(); + emit sessionAttributeChanged(); } } 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(); + emit sessionAttributeChanged(); } } 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/Session.h b/src/Session.h index ae3aa255..dab0a486 100644 --- a/src/Session.h +++ b/src/Session.h @@ -1,888 +1,893 @@ /* This file is part of Konsole, an X terminal. Copyright 2007-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. */ #ifndef SESSION_H #define SESSION_H // Qt #include #include #include #include #include #include #include // Konsole #include "konsoleprivate_export.h" #include "config-konsole.h" //krazy:exclude=includes #include "Shortcut_p.h" class QColor; class KConfigGroup; class KProcess; namespace Konsole { class Emulation; class Pty; class ProcessInfo; class TerminalDisplay; class ZModemDialog; class HistoryType; /** * Represents a terminal session consisting of a pseudo-teletype and a terminal emulation. * The pseudo-teletype (or PTY) handles I/O between the terminal process and Konsole. * The terminal emulation ( Emulation and subclasses ) processes the output stream from the * PTY and produces a character image which is then shown on views connected to the session. * * Each Session can be connected to one or more views by using the addView() method. * The attached views can then display output from the program running in the terminal * or send input to the program in the terminal in the form of keypresses and mouse * activity. */ class KONSOLEPRIVATE_EXPORT Session : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.konsole.Session") public: Q_PROPERTY(QString name READ nameTitle) Q_PROPERTY(int processId READ processId) Q_PROPERTY(QString keyBindings READ keyBindings WRITE setKeyBindings) Q_PROPERTY(QSize size READ size WRITE setSize) /** * Constructs a new session. * * To start the terminal process, call the run() method, * after specifying the program and arguments * using setProgram() and setArguments() * * If no program or arguments are specified explicitly, the Session * falls back to using the program specified in the SHELL environment * variable. */ explicit Session(QObject *parent = nullptr); ~Session() Q_DECL_OVERRIDE; /** * Connect to an existing terminal. When a new Session() is constructed it * automatically searches for and opens a new teletype. If you want to * use an existing teletype (given its file descriptor) call this after * constructing the session. * * Calling openTeletype() while a session is running has no effect. * * @param fd The file descriptor of the pseudo-teletype master (See KPtyProcess::KPtyProcess()) */ void openTeletype(int fd); /** * Returns true if the session is currently running. This will be true * after run() has been called successfully. */ bool isRunning() const; /** * Adds a new view for this session. * * The viewing widget will display the output from the terminal and * input from the viewing widget (key presses, mouse activity etc.) * will be sent to the terminal. * * Views can be removed using removeView(). The session is automatically * closed when the last view is removed. */ void addView(TerminalDisplay *widget); /** * Removes a view from this session. When the last view is removed, * the session will be closed automatically. * * @p widget will no longer display output from or send input * to the terminal */ void removeView(TerminalDisplay *widget); /** * Returns the views connected to this session */ QList views() const; /** * Returns the terminal emulation instance being used to encode / decode * characters to / from the process. */ Emulation *emulation() const; /** Returns the unique ID for this session. */ int sessionId() const; /** * This enum describes the contexts for which separate * tab title formats may be specified. */ enum TabTitleContext { /** Default tab title format */ LocalTabTitle, /** * Tab title format used session currently contains * a connection to a remote computer (via SSH) */ RemoteTabTitle }; /** * Returns true if the session currently contains a connection to a * remote computer. It currently supports ssh. */ bool isRemote(); /** * Sets the format used by this session for tab titles. * * @param context The context whose format should be set. * @param format The tab title format. This may be a mixture * of plain text and dynamic elements denoted by a '%' character * followed by a letter. (eg. %d for directory). The dynamic * elements available depend on the @p context */ void setTabTitleFormat(TabTitleContext context, const QString &format); /** Returns the format used by this session for tab titles. */ QString tabTitleFormat(TabTitleContext context) const; /** * Returns true if the tab title has been changed by the user via the * rename-tab dialog. */ bool isTabTitleSetByUser() const; /** Returns the arguments passed to the shell process when run() is called. */ QStringList arguments() const; /** Returns the program name of the shell process started when run() is called. */ QString program() const; /** * Sets the command line arguments which the session's program will be passed when * run() is called. */ void setArguments(const QStringList &arguments); /** Sets the program to be executed when run() is called. */ void setProgram(const QString &program); /** Returns the session's current working directory. */ QString initialWorkingDirectory() { return _initialWorkingDir; } /** * Sets the initial working directory for the session when it is run * This has no effect once the session has been started. */ void setInitialWorkingDirectory(const QString &dir); /** * Returns the current directory of the foreground process in the session */ QString currentWorkingDirectory(); /** * Sets the type of history store used by this session. * Lines of output produced by the terminal are added * to the history store. The type of history store * used affects the number of lines which can be * remembered before they are lost and the storage * (in memory, on-disk etc.) used. */ void setHistoryType(const HistoryType &hType); /** * Returns the type of history store used by this session. */ const HistoryType &historyType() const; /** * Clears the history store used by this session. */ void clearHistory(); /** * Sets the key bindings used by this session. The bindings * specify how input key sequences are translated into * the character stream which is sent to the terminal. * * @param name The name of the key bindings to use. The * names of available key bindings can be determined using the * KeyboardTranslatorManager class. */ void setKeyBindings(const QString &name); /** Returns the name of the key bindings used by this session. */ QString keyBindings() const; /** * This enum describes the available title roles. */ enum TitleRole { /** The name of the session. */ NameRole, /** The title of the session which is displayed in tabs etc. */ DisplayedTitleRole }; /** * Return the session title set by the user (ie. the program running * in the terminal), or an empty string if the user has not set a custom title */ QString userTitle() const; /** Convenience method used to read the name property. Returns title(Session::NameRole). */ QString nameTitle() const { return title(Session::NameRole); } /** Returns a title generated from tab format and process information. */ QString getDynamicTitle(); /** Sets the name of the icon associated with this session. */ void setIconName(const QString &iconName); /** Returns the name of the icon associated with this session. */ QString iconName() const; /** Return URL for the session. */ QUrl getUrl(); /** Sets the text of the icon associated with this session. */ void setIconText(const QString &iconText); /** Returns the text of the icon associated with this session. */ QString iconText() const; /** Sets the session's title for the specified @p role to @p title. */ void setTitle(TitleRole role, const QString &newTitle); /** Returns the session's title for the specified @p role. */ QString title(TitleRole role) const; /** * Specifies whether a utmp entry should be created for the pty used by this session. * If true, KPty::login() is called when the session is started. */ void setAddToUtmp(bool); /** * Specifies whether to close the session automatically when the terminal * process terminates. */ void setAutoClose(bool close); /** See setAutoClose() */ bool autoClose() const; /** Returns true if the user has started a program in the session. */ bool isForegroundProcessActive(); /** Returns the name of the current foreground process. */ QString foregroundProcessName(); /** Returns the terminal session's window size in lines and columns. */ QSize size(); /** * Emits a request to resize the session to accommodate * the specified window size. * * @param size The size in lines and columns to request. */ void setSize(const QSize &size); QSize preferredSize() const; void setPreferredSize(const QSize &size); /** * Sets whether the session has a dark background or not. The session * uses this information to set the COLORFGBG variable in the process's * environment, which allows the programs running in the terminal to determine * whether the background is light or dark and use appropriate colors by default. * * This has no effect once the session is running. */ void setDarkBackground(bool darkBackground); /** * Attempts to get the shell program to redraw the current display area. * This can be used after clearing the screen, for example, to get the * shell to redraw the prompt line. */ void refresh(); void startZModem(const QString &zmodem, const QString &dir, const QStringList &list); void cancelZModem(); bool isZModemBusy() { return _zmodemBusy; } void setZModemBusy(bool busy) { _zmodemBusy = busy; } /** - * Possible values of the @p what parameter for setUserTitle() - * See "Operating System Controls" section on http://rtfm.etla.org/xterm/ctlseq.html + * Possible values of the @p what parameter for setSessionAttribute(). + * See the "Operating System Commands" section at: + * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands */ - enum UserTitleChange { + enum SessionAttributes { IconNameAndWindowTitle = 0, IconName = 1, WindowTitle = 2, CurrentDirectory = 7, // From VTE (supposedly 6 was for dir, 7 for file, but whatever) TextColor = 10, BackgroundColor = 11, SessionName = 30, // Non-standard SessionIcon = 32, // Non-standard ProfileChange = 50 // this clashes with Xterm's font change command }; // Sets the text codec used by this sessions terminal emulation. void setCodec(QTextCodec *codec); // session management void saveSession(KConfigGroup &group); void restoreSession(KConfigGroup &group); void sendSignal(int signal); void reportBackgroundColor(const QColor &c); bool isReadOnly() const; void setReadOnly(bool readOnly); public Q_SLOTS: /** * Starts the terminal session. * * This creates the terminal process and connects the teletype to it. */ void run(); /** * Returns the environment of this session as a list of strings like * VARIABLE=VALUE */ Q_SCRIPTABLE QStringList environment() const; /** * Sets the environment for this session. * @p environment should be a list of strings like * VARIABLE=VALUE */ Q_SCRIPTABLE void setEnvironment(const QStringList &environment); /** * Adds one entry for the environment of this session * @p entry should be like VARIABLE=VALUE */ void addEnvironmentEntry(const QString &entry); /** * Closes the terminal session. It kills the terminal process by calling * closeInNormalWay() and, optionally, closeInForceWay(). */ //Q_SCRIPTABLE void close(); // This cause the menu issues bko 185466 void close(); /** * Kill the terminal process in normal way. This sends a hangup signal * (SIGHUP) to the terminal process and causes the finished() signal to * be emitted. If the process does not respond to the SIGHUP signal then * the terminal connection (the pty) is closed and Konsole waits for the * process to exit. This method works most of the time, but fails with some * programs which respond to SIGHUP signal in special way, such as autossh * and irssi. */ bool closeInNormalWay(); /** * kill terminal process in force way. This send a SIGKILL signal to the * terminal process. It should be called only after closeInNormalWay() has * failed. Take it as last resort. */ bool closeInForceWay(); /** - * Changes the session title or other customizable aspects of the terminal - * emulation display. For a list of what may be changed see the - * Emulation::titleChanged() signal. + * Changes one of certain session attributes in the terminal emulation + * display. For a list of what may be changed see the + * Emulation::sessionAttributeChanged() signal. * - * @param what The feature being changed. Value is one of UserTitleChange + * @param what The session attribute being changed, it is one of the + * SessionAttributes enum * @param caption The text part of the terminal command */ - void setUserTitle(int what, const QString &caption); + void setSessionAttribute(int what, const QString &caption); /** * Enables monitoring for activity in the session. * This will cause notifySessionState() to be emitted * with the NOTIFYACTIVITY state flag when output is * received from the terminal. */ Q_SCRIPTABLE void setMonitorActivity(bool); /** Returns true if monitoring for activity is enabled. */ Q_SCRIPTABLE bool isMonitorActivity() const; /** * Enables monitoring for silence in the session. * This will cause notifySessionState() to be emitted * with the NOTIFYSILENCE state flag when output is not * received from the terminal for a certain period of * time, specified with setMonitorSilenceSeconds() */ Q_SCRIPTABLE void setMonitorSilence(bool); /** * Returns true if monitoring for inactivity (silence) * in the session is enabled. */ Q_SCRIPTABLE bool isMonitorSilence() const; /** See setMonitorSilence() */ Q_SCRIPTABLE void setMonitorSilenceSeconds(int seconds); /** * Sets whether flow control is enabled for this terminal * session. */ Q_SCRIPTABLE void setFlowControlEnabled(bool enabled); /** Returns whether flow control is enabled for this terminal session. */ Q_SCRIPTABLE bool flowControlEnabled() const; /** * @param text to send to the current foreground terminal program. * @param eol send this after @p text */ void sendTextToTerminal(const QString &text, const QChar &eol = QChar()) const; #if defined(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS) void sendText(const QString &text) const; #else Q_SCRIPTABLE void sendText(const QString &text) const; #endif /** * Sends @p command to the current foreground terminal program. */ #if defined(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS) void runCommand(const QString &command) const; #else Q_SCRIPTABLE void runCommand(const QString &command) const; #endif /** * Sends a mouse event of type @p eventType emitted by button * @p buttons on @p column/@p line to the current foreground * terminal program */ Q_SCRIPTABLE void sendMouseEvent(int buttons, int column, int line, int eventType); /** * Returns the process id of the terminal process. * This is the id used by the system API to refer to the process. */ Q_SCRIPTABLE int processId() const; /** * Returns the process id of the terminal's foreground process. * This is initially the same as processId() but can change * as the user starts other programs inside the terminal. */ Q_SCRIPTABLE int foregroundProcessId(); /** Sets the text codec used by this sessions terminal emulation. * Overloaded to accept a QByteArray for convenience since DBus * does not accept QTextCodec directly. */ Q_SCRIPTABLE bool setCodec(const QByteArray &name); /** Returns the codec used to decode incoming characters in this * terminal emulation */ Q_SCRIPTABLE QByteArray codec(); /** Sets the session's title for the specified @p role to @p title. * This is an overloaded member function for setTitle(TitleRole, QString) * provided for convenience since enum data types may not be * exported directly through DBus */ Q_SCRIPTABLE void setTitle(int role, const QString &title); /** Returns the session's title for the specified @p role. * This is an overloaded member function for setTitle(TitleRole) * provided for convenience since enum data types may not be * exported directly through DBus */ Q_SCRIPTABLE QString title(int role) const; /** Returns the "friendly" version of the QUuid of this session. * This is a QUuid with the braces and dashes removed, so it cannot be * used to construct a new QUuid. The same text appears in the * SHELL_SESSION_ID environment variable. */ Q_SCRIPTABLE QString shellSessionId() const; /** Sets the session's tab title format for the specified @p context to @p format. * This is an overloaded member function for setTabTitleFormat(TabTitleContext, QString) * provided for convenience since enum data types may not be * exported directly through DBus */ Q_SCRIPTABLE void setTabTitleFormat(int context, const QString &format); /** Returns the session's tab title format for the specified @p context. * This is an overloaded member function for tabTitleFormat(TitleRole) * provided for convenience since enum data types may not be * exported directly through DBus */ Q_SCRIPTABLE QString tabTitleFormat(int context) const; /** * Sets the history capacity of this session. * * @param lines The history capacity in unit of lines. Its value can be: *
    *
  • positive integer - fixed size history
  • *
  • 0 - no history
  • *
  • negative integer - unlimited history
  • *
*/ Q_SCRIPTABLE void setHistorySize(int lines); /** * Returns the history capacity of this session. */ Q_SCRIPTABLE int historySize() const; Q_SIGNALS: /** Emitted when the terminal process starts. */ void started(); /** * Emitted when the terminal process exits. */ void finished(); - /** Emitted when the session's title has changed. */ - void titleChanged(); + /** + * Emitted when one of certain session attributes has been changed. + * See setSessionAttribute(). + */ + void sessionAttributeChanged(); /** Emitted when the session gets locked / unlocked. */ void readOnlyChanged(); /** * Emitted when the activity state of this session changes. * * @param state The new state of the session. This may be one * of NOTIFYNORMAL, NOTIFYSILENCE or NOTIFYACTIVITY */ void stateChanged(int state); /** * Emitted when the current working directory of this session changes. * * @param dir The new current working directory of the session. */ void currentDirectoryChanged(const QString &dir); /** Emitted when a bell event occurs in the session. */ void bellRequest(const QString &message); /** * Requests that the color the text for any tabs associated with * this session should be changed; * * TODO: Document what the parameter does */ void changeTabTextColorRequest(int); /** * Requests that the background color of views on this session * should be changed. */ void changeBackgroundColorRequest(const QColor &); /** * Requests that the text color of views on this session should * be changed to @p color. */ void changeForegroundColorRequest(const QColor &); /** TODO: Document me. */ void openUrlRequest(const QString &url); /** * Emitted when the request for data transmission through ZModem * protocol is detected. */ void zmodemDownloadDetected(); void zmodemUploadDetected(); /** * Emitted when the terminal process requests a change * in the size of the terminal window. * * @param size The requested window size in terms of lines and columns. */ void resizeRequest(const QSize &size); /** * Emitted when a profile change command is received from the terminal. * * @param text The text of the command. This is a string of the form * "PropertyName=Value;PropertyName=Value ..." */ void profileChangeCommandReceived(const QString &text); /** * Emitted when the flow control state changes. * * @param enabled True if flow control is enabled or false otherwise. */ void flowControlEnabledChanged(bool enabled); /** * Emitted when the active screen is switched, to indicate whether the primary * screen is in use. * * This signal serves as a relayer of Emulation::priamyScreenInUse(bool), * making it usable for higher level component. */ void primaryScreenInUse(bool use); /** * Emitted when the text selection is changed. * * This signal serves as a relayer of Emulation::selectedText(QString), * making it usable for higher level component. */ void selectionChanged(const QString &text); /** * Emitted when background request ("\033]11;?\a") terminal code received. * Terminal is expected send "\033]11;rgb:RRRR/GGGG/BBBB\a" response. * * Originally implemented to support vim's background detection feature * (without explictly setting 'bg=dark' within local/remote vimrc) */ void getBackgroundColor(); /** * Relays the tabRenamedByUser signal from SessionController */ void tabRenamedByUser(bool renamed) const; private Q_SLOTS: void done(int, QProcess::ExitStatus); void fireZModemDownloadDetected(); void fireZModemUploadDetected(); void onReceiveBlock(const char *buf, int len); void silenceTimerDone(); void activityTimerDone(); void onViewSizeChange(int height, int width); void activityStateSet(int); //automatically detach views from sessions when view is destroyed void viewDestroyed(QObject *view); void zmodemReadStatus(); void zmodemReadAndSendBlock(); void zmodemReceiveBlock(const char *data, int len); void zmodemFinished(); void updateFlowControlState(bool suspended); void updateWindowSize(int lines, int columns); // signal relayer void onPrimaryScreenInUse(bool use); void sessionAttributeRequest(int id); void tabTitleSetByUser(bool set); private: Q_DISABLE_COPY(Session) // checks that the binary 'program' is available and can be executed // returns the binary name if available or an empty string otherwise static QString checkProgram(const QString &program); void updateTerminalSize(); WId windowId() const; bool kill(int signal); // print a warning message in the terminal. This is used // if the program fails to start, or if the shell exits in // an unsuccessful manner void terminalWarning(const QString &message); ProcessInfo *getProcessInfo(); void updateSessionProcessInfo(); bool updateForegroundProcessInfo(); void updateWorkingDirectory(); QString validDirectory(const QString &dir) const; QUuid _uniqueIdentifier; // SHELL_SESSION_ID Pty *_shellProcess; Emulation *_emulation; QList _views; // monitor activity & silence bool _monitorActivity; bool _monitorSilence; bool _notifiedActivity; int _silenceSeconds; QTimer *_silenceTimer; QTimer *_activityTimer; bool _autoClose; bool _closePerUserRequest; QString _nameTitle; QString _displayTitle; QString _userTitle; QString _localTabTitleFormat; QString _remoteTabTitleFormat; bool _tabTitleSetByUser; QString _iconName; QString _iconText; // not actually used bool _addToUtmp; bool _flowControlEnabled; QString _program; QStringList _arguments; QStringList _environment; int _sessionId; QString _initialWorkingDir; QString _currentWorkingDir; QUrl _reportedWorkingUrl; ProcessInfo *_sessionProcessInfo; ProcessInfo *_foregroundProcessInfo; int _foregroundPid; // ZModem bool _zmodemBusy; KProcess *_zmodemProc; ZModemDialog *_zmodemProgress; bool _hasDarkBackground; QSize _preferredSize; bool _readOnly; static int lastSessionId; }; /** * Provides a group of sessions which is divided into master and slave sessions. * Activity in master sessions can be propagated to all sessions within the group. * The type of activity which is propagated and method of propagation is controlled * by the masterMode() flags. */ class SessionGroup : public QObject { Q_OBJECT public: /** Constructs an empty session group. */ explicit SessionGroup(QObject *parent); /** Destroys the session group and removes all connections between master and slave sessions. */ ~SessionGroup() Q_DECL_OVERRIDE; /** Adds a session to the group. */ void addSession(Session *session); /** Removes a session from the group. */ void removeSession(Session *session); /** Returns the list of sessions currently in the group. */ QList sessions() const; /** * Sets whether a particular session is a master within the group. * Changes or activity in the group's master sessions may be propagated * to all the sessions in the group, depending on the current masterMode() * * @param session The session whose master status should be changed. * @param master True to make this session a master or false otherwise */ void setMasterStatus(Session *session, bool master); /** Returns the master status of a session. See setMasterStatus() */ bool masterStatus(Session *session) const; /** * This enum describes the options for propagating certain activity or * changes in the group's master sessions to all sessions in the group. */ enum MasterMode { /** * Any input key presses in the master sessions are sent to all * sessions in the group. */ CopyInputToAll = 1 }; /** * Specifies which activity in the group's master sessions is propagated * to all sessions in the group. * * @param mode A bitwise OR of MasterMode flags. */ void setMasterMode(int mode); /** * Returns a bitwise OR of the active MasterMode flags for this group. * See setMasterMode() */ int masterMode() const; private Q_SLOTS: void sessionFinished(); void forwardData(const QByteArray &data); private: QList masters() const; // maps sessions to their master status QHash _sessions; int _masterMode; }; } #endif diff --git a/src/SessionController.cpp b/src/SessionController.cpp index 442bcb75..02cf54e6 100644 --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -1,2135 +1,2135 @@ /* Copyright 2006-2008 by Robert Knight 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 "SessionController.h" #include "ProfileManager.h" #include "konsoledebug.h" // Qt #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Konsole #include "EditProfileDialog.h" #include "CopyInputDialog.h" #include "Emulation.h" #include "Filter.h" #include "History.h" #include "HistorySizeDialog.h" #include "IncrementalSearchBar.h" #include "RenameTabDialog.h" #include "ScreenWindow.h" #include "Session.h" #include "ProfileList.h" #include "TerminalDisplay.h" #include "SessionManager.h" #include "Enumeration.h" #include "PrintOptions.h" // for SaveHistoryTask #include #include #include "TerminalCharacterDecoder.h" // For Unix signal names #include using namespace Konsole; // TODO - Replace the icon choices below when suitable icons for silence and // activity are available Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _activityIcon, (QIcon::fromTheme(QLatin1String("dialog-information")))) Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _silenceIcon, (QIcon::fromTheme(QLatin1String("dialog-information")))) Q_GLOBAL_STATIC_WITH_ARGS(QIcon, _broadcastIcon, (QIcon::fromTheme(QLatin1String("emblem-important")))) QSet SessionController::_allControllers; int SessionController::_lastControllerId; SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent) : ViewProperties(parent) , KXMLGUIClient() , _session(session) , _view(view) , _copyToGroup(nullptr) , _profileList(nullptr) , _sessionIcon(QIcon()) , _sessionIconName(QString()) , _previousState(-1) , _searchFilter(nullptr) , _urlFilter(nullptr) , _fileFilter(nullptr) , _copyInputToAllTabsAction(nullptr) , _findAction(nullptr) , _findNextAction(nullptr) , _findPreviousAction(nullptr) , _interactionTimer(nullptr) , _searchStartLine(0) , _prevSearchResultLine(0) , _searchBar(nullptr) , _codecAction(nullptr) , _switchProfileMenu(nullptr) , _webSearchMenu(nullptr) , _listenForScreenWindowUpdates(false) , _preventClose(false) , _keepIconUntilInteraction(false) , _selectedText(QString()) , _showMenuAction(nullptr) , _bookmarkValidProgramsToClear(QStringList()) , _isSearchBarEnabled(false) , _editProfileDialog(nullptr) { Q_ASSERT(session); Q_ASSERT(view); // handle user interface related to session (menus etc.) if (isKonsolePart()) { setComponentName(QStringLiteral("konsole"), i18n("Konsole")); setXMLFile(QStringLiteral("partui.rc")); setupCommonActions(); } else { setXMLFile(QStringLiteral("sessionui.rc")); setupCommonActions(); setupExtraActions(); } actionCollection()->addAssociatedWidget(view); foreach(QAction * action, actionCollection()->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } setIdentifier(++_lastControllerId); - sessionTitleChanged(); + sessionAttributeChanged(); view->installEventFilter(this); view->setSessionController(this); // install filter on the view to highlight URLs and files updateFilterList(SessionManager::instance()->sessionProfile(_session)); // listen for changes in session, we might need to change the enabled filters connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList); // listen for session resize requests connect(_session.data(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest); // listen for popup menu requests connect(_view.data(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu); // move view to newest output when keystrokes occur connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput); // listen to activity / silence notifications from session connect(_session.data(), &Konsole::Session::stateChanged, this, &Konsole::SessionController::sessionStateChanged); // listen to title and icon changes - connect(_session.data(), &Konsole::Session::titleChanged, this, &Konsole::SessionController::sessionTitleChanged); + connect(_session.data(), &Konsole::Session::sessionAttributeChanged, this, &Konsole::SessionController::sessionAttributeChanged); connect(_session.data(), &Konsole::Session::readOnlyChanged, this, &Konsole::SessionController::sessionReadOnlyChanged); connect(this, &Konsole::SessionController::tabRenamedByUser, _session, &Konsole::Session::tabRenamedByUser); connect(_session.data() , &Konsole::Session::currentDirectoryChanged , this , &Konsole::SessionController::currentDirectoryChanged); // listen for color changes connect(_session.data(), &Konsole::Session::changeBackgroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setBackgroundColor); connect(_session.data(), &Konsole::Session::changeForegroundColorRequest, _view.data(), &Konsole::TerminalDisplay::setForegroundColor); // update the title when the session starts connect(_session.data(), &Konsole::Session::started, this, &Konsole::SessionController::snapshot); // listen for output changes to set activity flag connect(_session->emulation(), &Konsole::Emulation::outputChanged, this, &Konsole::SessionController::fireActivity); // listen for detection of ZModem transfer connect(_session.data(), &Konsole::Session::zmodemDownloadDetected, this, &Konsole::SessionController::zmodemDownload); connect(_session.data(), &Konsole::Session::zmodemUploadDetected, this, &Konsole::SessionController::zmodemUpload); // listen for flow control status changes connect(_session.data(), &Konsole::Session::flowControlEnabledChanged, _view.data(), &Konsole::TerminalDisplay::setFlowControlWarningEnabled); _view->setFlowControlWarningEnabled(_session->flowControlEnabled()); // take a snapshot of the session state every so often when // user activity occurs // // the timer is owned by the session so that it will be destroyed along // with the session _interactionTimer = new QTimer(_session); _interactionTimer->setSingleShot(true); _interactionTimer->setInterval(500); connect(_interactionTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot); connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::interactionHandler); // take a snapshot of the session state periodically in the background auto backgroundTimer = new QTimer(_session); backgroundTimer->setSingleShot(false); backgroundTimer->setInterval(2000); connect(backgroundTimer, &QTimer::timeout, this, &Konsole::SessionController::snapshot); backgroundTimer->start(); // xterm '11;?' request connect(_session.data(), &Konsole::Session::getBackgroundColor, this, &Konsole::SessionController::sendBackgroundColor); connect(_session, &Konsole::Session::primaryScreenInUse, view, &Konsole::TerminalDisplay::usingPrimaryScreen); _allControllers.insert(this); // A list of programs that accept Ctrl+C to clear command line used // before outputting bookmark. _bookmarkValidProgramsToClear << QStringLiteral("bash") << QStringLiteral("fish") << QStringLiteral("sh"); _bookmarkValidProgramsToClear << QStringLiteral("tcsh") << QStringLiteral("zsh"); } SessionController::~SessionController() { if (!_view.isNull()) { _view->setScreenWindow(nullptr); } _allControllers.remove(this); if (!_editProfileDialog.isNull()) { delete _editProfileDialog.data(); } } void SessionController::trackOutput(QKeyEvent* event) { Q_ASSERT(_view->screenWindow()); // Only jump to the bottom if the user actually typed something in, // not if the user e. g. just pressed a modifier. if (event->text().isEmpty() && (event->modifiers() != 0u)) { return; } _view->screenWindow()->setTrackOutput(true); } void SessionController::interactionHandler() { // This flag is used to make sure those special icons indicating interest // events (activity/silence/bell?) remain in the tab until user interaction // happens. Otherwise, those special icons will quickly be replaced by // normal icon when ::snapshot() is triggered _keepIconUntilInteraction = false; _interactionTimer->start(); } void SessionController::snapshot() { Q_ASSERT(!_session.isNull()); QString title = _session->getDynamicTitle(); title = title.simplified(); // Visualize that the session is broadcasting to others if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) { title.append(QLatin1Char('*')); } // use the fallback title if needed if (title.isEmpty()) { title = _session->title(Session::NameRole); } // apply new title _session->setTitle(Session::DisplayedTitleRole, title); // do not forget icon updateSessionIcon(); } QString SessionController::currentDir() const { return _session->currentWorkingDirectory(); } QUrl SessionController::url() const { return _session->getUrl(); } void SessionController::rename() { renameSession(); } void SessionController::openUrl(const QUrl& url) { // Clear shell's command line if (!_session->isForegroundProcessActive() && _bookmarkValidProgramsToClear.contains(_session->foregroundProcessName())) { _session->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C } // handle local paths if (url.isLocalFile()) { QString path = url.toLocalFile(); _session->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r')); } else if (url.scheme().isEmpty()) { // QUrl couldn't parse what the user entered into the URL field // so just dump it to the shell QString command = url.toDisplayString(); if (!command.isEmpty()) { _session->sendTextToTerminal(command, QLatin1Char('\r')); } } else if (url.scheme() == QLatin1String("ssh")) { QString sshCommand = QStringLiteral("ssh "); if (url.port() > -1) { sshCommand += QStringLiteral("-p %1 ").arg(url.port()); } if (!url.userName().isEmpty()) { sshCommand += (url.userName() + QLatin1Char('@')); } if (!url.host().isEmpty()) { sshCommand += url.host(); } _session->sendTextToTerminal(sshCommand, QLatin1Char('\r')); } else if (url.scheme() == QLatin1String("telnet")) { QString telnetCommand = QStringLiteral("telnet "); if (!url.userName().isEmpty()) { telnetCommand += QStringLiteral("-l %1 ").arg(url.userName()); } if (!url.host().isEmpty()) { telnetCommand += (url.host() + QLatin1Char(' ')); } if (url.port() > -1) { telnetCommand += QString::number(url.port()); } _session->sendTextToTerminal(telnetCommand, QLatin1Char('\r')); } else { //TODO Implement handling for other Url types KMessageBox::sorry(_view->window(), i18n("Konsole does not know how to open the bookmark: ") + url.toDisplayString()); qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know" << " how to handle the protocol " << url.scheme(); } } void SessionController::setupPrimaryScreenSpecificActions(bool use) { KActionCollection* collection = actionCollection(); QAction* clearAction = collection->action(QStringLiteral("clear-history")); QAction* resetAction = collection->action(QStringLiteral("clear-history-and-reset")); QAction* selectAllAction = collection->action(QStringLiteral("select-all")); QAction* selectLineAction = collection->action(QStringLiteral("select-line")); // these actions are meaningful only when primary screen is used. clearAction->setEnabled(use); resetAction->setEnabled(use); selectAllAction->setEnabled(use); selectLineAction->setEnabled(use); } void SessionController::selectionChanged(const QString& selectedText) { _selectedText = selectedText; updateCopyAction(selectedText); } void SessionController::updateCopyAction(const QString& selectedText) { QAction* copyAction = actionCollection()->action(QStringLiteral("edit_copy")); // copy action is meaningful only when some text is selected. copyAction->setEnabled(!selectedText.isEmpty()); } void SessionController::updateWebSearchMenu() { // reset _webSearchMenu->setVisible(false); _webSearchMenu->menu()->clear(); if (_selectedText.isEmpty()) { return; } QString searchText = _selectedText; searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified(); if (searchText.isEmpty()) { return; } KUriFilterData filterData(searchText); filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { const QStringList searchProviders = filterData.preferredSearchProviders(); if (!searchProviders.isEmpty()) { _webSearchMenu->setText(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 16))); QAction* action = nullptr; foreach(const QString& searchProvider, searchProviders) { action = new QAction(searchProvider, _webSearchMenu); action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider))); action->setData(filterData.queryForPreferredSearchProvider(searchProvider)); connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction); _webSearchMenu->addAction(action); } _webSearchMenu->addSeparator(); action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts); _webSearchMenu->addAction(action); _webSearchMenu->setVisible(true); } } } void SessionController::handleWebShortcutAction() { QAction * action = qobject_cast(sender()); if (action == nullptr) { return; } KUriFilterData filterData(action->data().toString()); if (KUriFilter::self()->filterUri(filterData, QStringList() << QStringLiteral("kurisearchfilter"))) { const QUrl& url = filterData.uri(); new KRun(url, QApplication::activeWindow()); } } void SessionController::configureWebShortcuts() { KToolInvocation::kdeinitExec(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts")); } void SessionController::sendSignal(QAction* action) { const int signal = action->data().value(); _session->sendSignal(signal); } void SessionController::sendBackgroundColor() { const QColor c = _view->getBackgroundColor(); _session->reportBackgroundColor(c); } void SessionController::toggleReadOnly() { QAction *action = qobject_cast(sender()); if (action != nullptr) { bool readonly = !isReadOnly(); _session->setReadOnly(readonly); } } bool SessionController::eventFilter(QObject* watched , QEvent* event) { if (event->type() == QEvent::FocusIn && watched == _view) { // notify the world that the view associated with this session has been focused // used by the view manager to update the title of the MainWindow widget containing the view emit focused(this); // when the view is focused, set bell events from the associated session to be delivered // by the focused view // first, disconnect any other views which are listening for bell signals from the session disconnect(_session.data(), &Konsole::Session::bellRequest, nullptr, nullptr); // second, connect the newly focused view to listen for the session's bell signal connect(_session.data(), &Konsole::Session::bellRequest, _view.data(), &Konsole::TerminalDisplay::bell); if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) { // A session with "Copy To All Tabs" has come into focus: // Ensure that newly created sessions are included in _copyToGroup! copyInputToAllTabs(); } } return Konsole::ViewProperties::eventFilter(watched, event); } void SessionController::removeSearchFilter() { if (_searchFilter == nullptr) { return; } _view->filterChain()->removeFilter(_searchFilter); delete _searchFilter; _searchFilter = nullptr; } void SessionController::setSearchBar(IncrementalSearchBar* searchBar) { // disconnect the existing search bar if (!_searchBar.isNull()) { disconnect(this, nullptr, _searchBar, nullptr); disconnect(_searchBar, nullptr, this, nullptr); } // connect new search bar _searchBar = searchBar; if (!_searchBar.isNull()) { connect(_searchBar.data(), &Konsole::IncrementalSearchBar::unhandledMovementKeyPressed, this, &Konsole::SessionController::movementKeyFromSearchBarReceived); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::closeClicked, this, &Konsole::SessionController::searchClosed); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchFromClicked, this, &Konsole::SessionController::searchFrom); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findNextClicked, this, &Konsole::SessionController::findNextInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::findPreviousClicked, this, &Konsole::SessionController::findPreviousInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::highlightMatchesToggled , this , &Konsole::SessionController::highlightMatches); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchCaseToggled, this, &Konsole::SessionController::changeSearchMatch); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::matchRegExpToggled, this, &Konsole::SessionController::changeSearchMatch); // if the search bar was previously active // then re-enter search mode enableSearchBar(_isSearchBarEnabled); } } IncrementalSearchBar* SessionController::searchBar() const { return _searchBar; } void SessionController::setShowMenuAction(QAction* action) { _showMenuAction = action; } void SessionController::setupCommonActions() { KActionCollection* collection = actionCollection(); // Close Session QAction* action = collection->addAction(QStringLiteral("close-session"), this, SLOT(closeSession())); if (isKonsolePart()) { action->setText(i18n("&Close Session")); } else { action->setText(i18n("&Close Tab")); } action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W); // Open Browser action = collection->addAction(QStringLiteral("open-browser"), this, SLOT(openBrowser())); action->setText(i18n("Open File Manager")); action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); // Copy and Paste action = KStandardAction::copy(this, SLOT(copy()), collection); #ifdef Q_OS_MACOS // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META) // TODO: check what happens if we leave it to Qt to assign the default? collection->setDefaultShortcut(action, Qt::META + Qt::Key_C); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_C); #endif // disabled at first, since nothing has been selected now action->setEnabled(false); action = KStandardAction::paste(this, SLOT(paste()), collection); QList pasteShortcut; #ifdef Q_OS_MACOS pasteShortcut.append(QKeySequence(Qt::META + Qt::Key_V)); // No Insert key on Mac keyboards #else pasteShortcut.append(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_V)); pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert)); #endif collection->setDefaultShortcuts(action, pasteShortcut); action = collection->addAction(QStringLiteral("paste-selection"), this, SLOT(pasteFromX11Selection())); action->setText(i18n("Paste Selection")); #ifdef Q_OS_MACOS collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert); #endif _webSearchMenu = new KActionMenu(i18n("Web Search"), this); _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts"))); _webSearchMenu->setVisible(false); collection->addAction(QStringLiteral("web-search"), _webSearchMenu); action = collection->addAction(QStringLiteral("select-all"), this, SLOT(selectAll())); action->setText(i18n("&Select All")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); action = collection->addAction(QStringLiteral("select-line"), this, SLOT(selectLine())); action->setText(i18n("Select &Line")); action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection); action->setText(i18n("Save Output &As...")); #ifdef Q_OS_MACOS action->setShortcut(QKeySequence(Qt::META + Qt::Key_S)); #endif action = KStandardAction::print(this, SLOT(print_screen()), collection); action->setText(i18n("&Print Screen...")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P); action = collection->addAction(QStringLiteral("adjust-history"), this, SLOT(showHistoryOptions())); action->setText(i18n("Adjust Scrollback...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); action = collection->addAction(QStringLiteral("clear-history"), this, SLOT(clearHistory())); action->setText(i18n("Clear Scrollback")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, SLOT(clearHistoryAndReset())); action->setText(i18n("Clear Scrollback and Reset")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K); // Profile Options action = collection->addAction(QStringLiteral("edit-current-profile"), this, SLOT(editCurrentProfile())); action->setText(i18n("Edit Current Profile...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this); collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu); connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu); // History _findAction = KStandardAction::find(this, SLOT(searchBarEvent()), collection); collection->setDefaultShortcut(_findAction, QKeySequence()); _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection); collection->setDefaultShortcut(_findNextAction, QKeySequence()); _findNextAction->setEnabled(false); _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection); collection->setDefaultShortcut(_findPreviousAction, QKeySequence()); _findPreviousAction->setEnabled(false); // Character Encoding _codecAction = new KCodecAction(i18n("Set &Encoding"), this); _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); collection->addAction(QStringLiteral("set-encoding"), _codecAction); connect(_codecAction->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::updateCodecAction); connect(_codecAction, static_cast(&KCodecAction::triggered), this, &Konsole::SessionController::changeCodec); // Read-only action = collection->addAction(QStringLiteral("view-readonly"), this, SLOT(toggleReadOnly())); action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only")); action->setCheckable(true); updateReadOnlyActionStates(); } void SessionController::setupExtraActions() { KActionCollection* collection = actionCollection(); // Rename Session QAction* action = collection->addAction(QStringLiteral("rename-session"), this, SLOT(renameSession())); action->setText(i18n("&Rename Tab...")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S); // Copy input to ==> all tabs KToggleAction* copyInputToAllTabsAction = collection->add(QStringLiteral("copy-input-to-all-tabs")); copyInputToAllTabsAction->setText(i18n("&All Tabs in Current Window")); copyInputToAllTabsAction->setData(CopyInputToAllTabsMode); // this action is also used in other place, so remember it _copyInputToAllTabsAction = copyInputToAllTabsAction; // Copy input to ==> selected tabs KToggleAction* copyInputToSelectedTabsAction = collection->add(QStringLiteral("copy-input-to-selected-tabs")); copyInputToSelectedTabsAction->setText(i18n("&Select Tabs...")); collection->setDefaultShortcut(copyInputToSelectedTabsAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Period); copyInputToSelectedTabsAction->setData(CopyInputToSelectedTabsMode); // Copy input to ==> none KToggleAction* copyInputToNoneAction = collection->add(QStringLiteral("copy-input-to-none")); copyInputToNoneAction->setText(i18nc("@action:inmenu Do not select any tabs", "&None")); collection->setDefaultShortcut(copyInputToNoneAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Slash); copyInputToNoneAction->setData(CopyInputToNoneMode); copyInputToNoneAction->setChecked(true); // the default state // The "Copy Input To" submenu // The above three choices are represented as combo boxes KSelectAction* copyInputActions = collection->add(QStringLiteral("copy-input-to")); copyInputActions->setText(i18n("Copy Input To")); copyInputActions->addAction(copyInputToAllTabsAction); copyInputActions->addAction(copyInputToSelectedTabsAction); copyInputActions->addAction(copyInputToNoneAction); connect(copyInputActions, static_cast(&KSelectAction::triggered), this, &Konsole::SessionController::copyInputActionsTriggered); action = collection->addAction(QStringLiteral("zmodem-upload"), this, SLOT(zmodemUpload())); action->setText(i18n("&ZModem Upload...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_U); // Monitor KToggleAction* toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_A); action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity); toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_I); action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence); // Text Size action = collection->addAction(QStringLiteral("enlarge-font"), this, SLOT(increaseFontSize())); action->setText(i18n("Enlarge Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more"))); QList enlargeFontShortcut; enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Plus)); enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Equal)); collection->setDefaultShortcuts(action, enlargeFontShortcut); action = collection->addAction(QStringLiteral("shrink-font"), this, SLOT(decreaseFontSize())); action->setText(i18n("Shrink Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_Minus); // Send signal KSelectAction* sendSignalActions = collection->add(QStringLiteral("send-signal")); sendSignalActions->setText(i18n("Send Signal")); connect(sendSignalActions, static_cast(&KSelectAction::triggered), this, &Konsole::SessionController::sendSignal); action = collection->addAction(QStringLiteral("sigstop-signal")); action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)")); action->setData(SIGSTOP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigcont-signal")); action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)")); action->setData(SIGCONT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sighup-signal")); action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)")); action->setData(SIGHUP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigint-signal")); action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)")); action->setData(SIGINT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigterm-signal")); action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)")); action->setData(SIGTERM); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigkill-signal")); action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)")); action->setData(SIGKILL); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr1-signal")); action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)")); action->setData(SIGUSR1); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr2-signal")); action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)")); action->setData(SIGUSR2); sendSignalActions->addAction(action); #ifdef Q_OS_MACOS collection->setDefaultShortcut(_findAction, Qt::META + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::META + Qt::Key_G); collection->setDefaultShortcut(_findPreviousAction, Qt::META + Qt::SHIFT + Qt::Key_G); #else collection->setDefaultShortcut(_findAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::Key_F3); collection->setDefaultShortcut(_findPreviousAction, Qt::SHIFT + Qt::Key_F3); #endif } void SessionController::switchProfile(Profile::Ptr profile) { SessionManager::instance()->setSessionProfile(_session, profile); updateFilterList(profile); } void SessionController::prepareSwitchProfileMenu() { if (_switchProfileMenu->menu()->isEmpty()) { _profileList = new ProfileList(false, this); connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile); } _switchProfileMenu->menu()->clear(); _switchProfileMenu->menu()->addActions(_profileList->actions()); } void SessionController::updateCodecAction() { _codecAction->setCurrentCodec(QString::fromUtf8(_session->codec())); } void SessionController::changeCodec(QTextCodec* codec) { _session->setCodec(codec); } EditProfileDialog* SessionController::profileDialogPointer() { return _editProfileDialog.data(); } void SessionController::editCurrentProfile() { // Searching for Edit profile dialog opened with the same profile const QList allSessionsControllers = _allControllers.values(); foreach (SessionController* session, allSessionsControllers) { if ((session->profileDialogPointer() != nullptr) && session->profileDialogPointer()->isVisible() && session->profileDialogPointer()->lookupProfile() == SessionManager::instance()->sessionProfile(_session)) { session->profileDialogPointer()->close(); } } // NOTE bug311270: For to prevent the crash, the profile must be reset. if (!_editProfileDialog.isNull()) { // exists but not visible delete _editProfileDialog.data(); } _editProfileDialog = new EditProfileDialog(QApplication::activeWindow()); _editProfileDialog.data()->setProfile(SessionManager::instance()->sessionProfile(_session)); _editProfileDialog.data()->show(); } void SessionController::renameSession() { const QString &sessionLocalTabTitleFormat = _session->tabTitleFormat(Session::LocalTabTitle); const QString &sessionRemoteTabTitleFormat = _session->tabTitleFormat(Session::RemoteTabTitle); QScopedPointer dialog(new RenameTabDialog(QApplication::activeWindow())); dialog->setTabTitleText(sessionLocalTabTitleFormat); dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat); if (_session->isRemote()) { dialog->focusRemoteTabTitleText(); } else { dialog->focusTabTitleText(); } QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result != 0) { const QString &tabTitle = dialog->tabTitleText(); const QString &remoteTabTitle = dialog->remoteTabTitleText(); if (tabTitle != sessionLocalTabTitleFormat) { _session->setTabTitleFormat(Session::LocalTabTitle, tabTitle); emit tabRenamedByUser(true); // trigger an update of the tab text snapshot(); } if(remoteTabTitle != sessionRemoteTabTitleFormat) { _session->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle); emit tabRenamedByUser(true); snapshot(); } } } bool SessionController::confirmClose() const { if (_session->isForegroundProcessActive()) { QString title = _session->foregroundProcessName(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program is currently running in this session." " Are you sure you want to close it?"); } else { question = i18n("The program '%1' is currently running in this session." " Are you sure you want to close it?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } bool SessionController::confirmForceClose() const { if (_session->isRunning()) { QString title = _session->program(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program in this session would not die." " Are you sure you want to kill it by force?"); } else { question = i18n("The program '%1' is in this session would not die." " Are you sure you want to kill it by force?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } void SessionController::closeSession() { if (_preventClose) { return; } if (confirmClose()) { if (_session->closeInNormalWay()) { return; } else if (confirmForceClose()) { if (_session->closeInForceWay()) { return; } else { qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way."; } } } } // Trying to open a remote Url may produce unexpected results. // Therefore, if a remote url, open the user's home path. // TODO consider: 1) disable menu upon remote session // 2) transform url to get the desired result (ssh -> sftp, etc) void SessionController::openBrowser() { const QUrl currentUrl = url(); if (currentUrl.isLocalFile()) { new KRun(currentUrl, QApplication::activeWindow(), true); } else { new KRun(QUrl::fromLocalFile(QDir::homePath()), QApplication::activeWindow(), true); } } void SessionController::copy() { _view->copyToClipboard(); } void SessionController::paste() { _view->pasteFromClipboard(); } void SessionController::pasteFromX11Selection() { _view->pasteFromX11Selection(); } void SessionController::selectAll() { _view->selectAll(); } void SessionController::selectLine() { _view->selectCurrentLine(); } static const KXmlGuiWindow* findWindow(const QObject* object) { // Walk up the QObject hierarchy to find a KXmlGuiWindow. while (object != nullptr) { const KXmlGuiWindow* window = qobject_cast(object); if (window != nullptr) { return(window); } object = object->parent(); } return(nullptr); } static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window) { // Iterate all TerminalDisplays of this Session ... foreach(const TerminalDisplay* terminalDisplay, session->views()) { // ... and check whether a TerminalDisplay has the same // window as given in the parameter if (window == findWindow(terminalDisplay)) { return(true); } } return(false); } void SessionController::copyInputActionsTriggered(QAction* action) { const int mode = action->data().value(); switch (mode) { case CopyInputToAllTabsMode: copyInputToAllTabs(); break; case CopyInputToSelectedTabsMode: copyInputToSelectedTabs(); break; case CopyInputToNoneMode: copyInputToNone(); break; default: Q_ASSERT(false); } } void SessionController::copyInputToAllTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); } // Find our window ... const KXmlGuiWindow* myWindow = findWindow(_view); QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto session : group) { // First, ensure that the session is removed // (necessary to avoid duplicates on addSession()!) _copyToGroup->removeSession(session); // Add current session if it is displayed our window if (hasTerminalDisplayInSameWindow(session, myWindow)) { _copyToGroup->addSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } void SessionController::copyInputToSelectedTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); _copyToGroup->addSession(_session); _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); } QPointer dialog = new CopyInputDialog(_view); dialog->setMasterSession(_session); QSet currentGroup = QSet::fromList(_copyToGroup->sessions()); currentGroup.remove(_session); dialog->setChosenSessions(currentGroup); QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result == QDialog::Accepted) { QSet newGroup = dialog->chosenSessions(); newGroup.remove(_session); QSet completeGroup = newGroup | currentGroup; foreach(Session * session, completeGroup) { if (newGroup.contains(session) && !currentGroup.contains(session)) { _copyToGroup->addSession(session); } else if (!newGroup.contains(session) && currentGroup.contains(session)) { _copyToGroup->removeSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } } void SessionController::copyInputToNone() { if (_copyToGroup == nullptr) { // No 'Copy To' is active return; } QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto iterator : group) { Session* session = iterator; if (session != _session) { _copyToGroup->removeSession(iterator); } } delete _copyToGroup; _copyToGroup = nullptr; snapshot(); } void SessionController::searchClosed() { _isSearchBarEnabled = false; searchHistory(false); } void SessionController::updateFilterList(Profile::Ptr profile) { if (profile != SessionManager::instance()->sessionProfile(_session)) { return; } bool underlineFiles = profile->underlineFilesEnabled(); if (!underlineFiles && (_fileFilter != nullptr)) { _view->filterChain()->removeFilter(_fileFilter); delete _fileFilter; _fileFilter = nullptr; } else if (underlineFiles && (_fileFilter == nullptr)) { _fileFilter = new FileFilter(_session); _view->filterChain()->addFilter(_fileFilter); } bool underlineLinks = profile->underlineLinksEnabled(); if (!underlineLinks && (_urlFilter != nullptr)) { _view->filterChain()->removeFilter(_urlFilter); delete _urlFilter; _urlFilter = nullptr; } else if (underlineLinks && (_urlFilter == nullptr)) { _urlFilter = new UrlFilter(); _view->filterChain()->addFilter(_urlFilter); } } void SessionController::setSearchStartToWindowCurrentLine() { setSearchStartTo(-1); } void SessionController::setSearchStartTo(int line) { _searchStartLine = line; _prevSearchResultLine = line; } void SessionController::listenForScreenWindowUpdates() { if (_listenForScreenWindowUpdates) { return; } connect(_view->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, _view.data(), static_cast(&Konsole::TerminalDisplay::update)); _listenForScreenWindowUpdates = true; } void SessionController::updateSearchFilter() { if ((_searchFilter != nullptr) && (!_searchBar.isNull())) { _view->processFilters(); } } void SessionController::searchBarEvent() { QString selectedText = _view->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace); if (!selectedText.isEmpty()) { _searchBar->setSearchText(selectedText); } if (_searchBar->isVisible()) { _searchBar->focusLineEdit(); } else { searchHistory(true); _isSearchBarEnabled = true; } } void SessionController::enableSearchBar(bool showSearchBar) { if (_searchBar.isNull()) { return; } if (showSearchBar && !_searchBar->isVisible()) { setSearchStartToWindowCurrentLine(); } _searchBar->setVisible(showSearchBar); if (showSearchBar) { connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); } else { disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); if ((!_view.isNull()) && (_view->screenWindow() != nullptr)) { _view->screenWindow()->setCurrentResultLine(-1); } } } bool SessionController::reverseSearchChecked() const { Q_ASSERT(_searchBar); QBitArray options = _searchBar->optionsChecked(); return options.at(IncrementalSearchBar::ReverseSearch); } QRegularExpression SessionController::regexpFromSearchBarOptions() const { QBitArray options = _searchBar->optionsChecked(); QString text(_searchBar->searchText()); QRegularExpression regExp; if (options.at(IncrementalSearchBar::RegExp)) { regExp.setPattern(text); } else { regExp.setPattern(QRegularExpression::escape(text)); } if (!options.at(IncrementalSearchBar::MatchCase)) { regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); } return regExp; } // searchHistory() may be called either as a result of clicking a menu item or // as a result of changing the search bar widget void SessionController::searchHistory(bool showSearchBar) { enableSearchBar(showSearchBar); if (!_searchBar.isNull()) { if (showSearchBar) { removeSearchFilter(); listenForScreenWindowUpdates(); _searchFilter = new RegExpFilter(); _searchFilter->setRegExp(regexpFromSearchBarOptions()); _view->filterChain()->addFilter(_searchFilter); _view->processFilters(); setFindNextPrevEnabled(true); } else { setFindNextPrevEnabled(false); removeSearchFilter(); _view->setFocus(Qt::ActiveWindowFocusReason); } } } void SessionController::setFindNextPrevEnabled(bool enabled) { _findNextAction->setEnabled(enabled); _findPreviousAction->setEnabled(enabled); } void SessionController::searchTextChanged(const QString& text) { Q_ASSERT(_view->screenWindow()); if (_searchText == text) { return; } _searchText = text; if (text.isEmpty()) { _view->screenWindow()->clearSelection(); _view->screenWindow()->scrollTo(_searchStartLine); } // update search. this is called even when the text is // empty to clear the view's filters beginSearch(text , reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::searchCompleted(bool success) { _prevSearchResultLine = _view->screenWindow()->currentResultLine(); if (!_searchBar.isNull()) { _searchBar->setFoundMatch(success); } } void SessionController::beginSearch(const QString& text, Enum::SearchDirection direction) { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); QRegularExpression regExp = regexpFromSearchBarOptions(); _searchFilter->setRegExp(regExp); if (_searchStartLine == -1) { if (direction == Enum::ForwardsSearch) { setSearchStartTo(_view->screenWindow()->currentLine()); } else { setSearchStartTo(_view->screenWindow()->currentLine() + _view->screenWindow()->windowLines()); } } if (!regExp.pattern().isEmpty()) { _view->screenWindow()->setCurrentResultLine(-1); auto task = new SearchHistoryTask(this); connect(task, &Konsole::SearchHistoryTask::completed, this, &Konsole::SessionController::searchCompleted); task->setRegExp(regExp); task->setSearchDirection(direction); task->setAutoDelete(true); task->setStartLine(_searchStartLine); task->addScreenWindow(_session , _view->screenWindow()); task->execute(); } else if (text.isEmpty()) { searchCompleted(false); } _view->processFilters(); } void SessionController::highlightMatches(bool highlight) { if (highlight) { _view->filterChain()->addFilter(_searchFilter); _view->processFilters(); } else { _view->filterChain()->removeFilter(_searchFilter); } _view->update(); } void SessionController::searchFrom() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); if (reverseSearchChecked()) { setSearchStartTo(_view->screenWindow()->lineCount()); } else { setSearchStartTo(0); } beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::findNextInHistory() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); setSearchStartTo(_prevSearchResultLine); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::findPreviousInHistory() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); setSearchStartTo(_prevSearchResultLine); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::ForwardsSearch : Enum::BackwardsSearch); } void SessionController::changeSearchMatch() { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); // reset Selection for new case match _view->screenWindow()->clearSelection(); beginSearch(_searchBar->searchText(), reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::showHistoryOptions() { QScopedPointer dialog(new HistorySizeDialog(QApplication::activeWindow())); const HistoryType& currentHistory = _session->historyType(); if (currentHistory.isEnabled()) { if (currentHistory.isUnlimited()) { dialog->setMode(Enum::UnlimitedHistory); } else { dialog->setMode(Enum::FixedSizeHistory); dialog->setLineCount(currentHistory.maximumLineCount()); } } else { dialog->setMode(Enum::NoHistory); } QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result != 0) { scrollBackOptionsChanged(dialog->mode(), dialog->lineCount()); } } void SessionController::sessionResizeRequest(const QSize& size) { ////qDebug() << "View resize requested to " << size; _view->setSize(size.width(), size.height()); } void SessionController::scrollBackOptionsChanged(int mode, int lines) { switch (mode) { case Enum::NoHistory: _session->setHistoryType(HistoryTypeNone()); break; case Enum::FixedSizeHistory: _session->setHistoryType(CompactHistoryType(lines)); break; case Enum::UnlimitedHistory: _session->setHistoryType(HistoryTypeFile()); break; } } void SessionController::print_screen() { QPrinter printer; QPointer dialog = new QPrintDialog(&printer, _view); auto options = new PrintOptions(); dialog->setOptionTabs(QList() << options); dialog->setWindowTitle(i18n("Print Shell")); connect(dialog.data(), static_cast(&QPrintDialog::accepted), options, &Konsole::PrintOptions::saveSettings); if (dialog->exec() != QDialog::Accepted) { return; } QPainter painter; painter.begin(&printer); KConfigGroup configGroup(KSharedConfig::openConfig(), "PrintOptions"); if (configGroup.readEntry("ScaleOutput", true)) { double scale = qMin(printer.pageRect().width() / static_cast(_view->width()), printer.pageRect().height() / static_cast(_view->height())); painter.scale(scale, scale); } _view->printContent(painter, configGroup.readEntry("PrinterFriendly", true)); } void SessionController::saveHistory() { SessionTask* task = new SaveHistoryTask(this); task->setAutoDelete(true); task->addSession(_session); task->execute(); } void SessionController::clearHistory() { _session->clearHistory(); _view->updateImage(); // To reset view scrollbar } void SessionController::clearHistoryAndReset() { Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session); QByteArray name = profile->defaultEncoding().toUtf8(); Emulation* emulation = _session->emulation(); emulation->reset(); _session->refresh(); _session->setCodec(QTextCodec::codecForName(name)); clearHistory(); } void SessionController::increaseFontSize() { _view->increaseFontSize(); } void SessionController::decreaseFontSize() { _view->decreaseFontSize(); } void SessionController::monitorActivity(bool monitor) { _session->setMonitorActivity(monitor); } void SessionController::monitorSilence(bool monitor) { _session->setMonitorSilence(monitor); } void SessionController::updateSessionIcon() { // Visualize that the session is broadcasting to others if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) { // Master Mode: set different icon, to warn the user to be careful setIcon(*_broadcastIcon); } else { if (!_keepIconUntilInteraction) { // Not in Master Mode: use normal icon setIcon(_sessionIcon); } } } void SessionController::updateReadOnlyActionStates() { bool readonly = isReadOnly(); QAction *readonlyAction = actionCollection()->action(QStringLiteral("view-readonly")); Q_ASSERT(readonlyAction != nullptr); readonlyAction->setIcon(QIcon::fromTheme(readonly ? QStringLiteral("object-locked") : QStringLiteral("object-unlocked"))); readonlyAction->setChecked(readonly); auto updateActionState = [this, readonly](const QString &name) { QAction *action = actionCollection()->action(name); if (action != nullptr) { action->setEnabled(!readonly); } }; updateActionState(QStringLiteral("edit_paste")); updateActionState(QStringLiteral("clear-history")); updateActionState(QStringLiteral("clear-history-and-reset")); updateActionState(QStringLiteral("edit-current-profile")); updateActionState(QStringLiteral("switch-profile")); updateActionState(QStringLiteral("adjust-history")); updateActionState(QStringLiteral("send-signal")); updateActionState(QStringLiteral("zmodem-upload")); _codecAction->setEnabled(!readonly); // Without the timer, when detaching a tab while the message widget is visible, // the size of the terminal becomes really small... QTimer::singleShot(0, this, [this, readonly]() { _view->updateReadOnlyState(readonly); }); } bool SessionController::isReadOnly() const { if (!_session.isNull()) { return _session->isReadOnly(); } else { return false; } } -void SessionController::sessionTitleChanged() +void SessionController::sessionAttributeChanged() { if (_sessionIconName != _session->iconName()) { _sessionIconName = _session->iconName(); _sessionIcon = QIcon::fromTheme(_sessionIconName); updateSessionIcon(); } QString title = _session->title(Session::DisplayedTitleRole); // special handling for the "%w" marker which is replaced with the // window title set by the shell title.replace(QLatin1String("%w"), _session->userTitle()); // special handling for the "%#" marker which is replaced with the // number of the shell title.replace(QLatin1String("%#"), QString::number(_session->sessionId())); if (title.isEmpty()) { title = _session->title(Session::NameRole); } setTitle(title); emit rawTitleChanged(); } void SessionController::sessionReadOnlyChanged() { // Trigger icon update - sessionTitleChanged(); + sessionAttributeChanged(); updateReadOnlyActionStates(); // Update all views for (TerminalDisplay* view : session()->views()) { if (view != _view.data()) { view->updateReadOnlyState(isReadOnly()); } } } void SessionController::showDisplayContextMenu(const QPoint& position) { // needed to make sure the popup menu is available, even if a hosting // application did not merge our GUI. if (factory() == nullptr) { if (clientBuilder() == nullptr) { setClientBuilder(new KXMLGUIBuilder(_view)); } auto factory = new KXMLGUIFactory(clientBuilder(), this); factory->addClient(this); ////qDebug() << "Created xmlgui factory" << factory; } QPointer popup = qobject_cast(factory()->container(QStringLiteral("session-popup-menu"), this)); if (!popup.isNull()) { updateReadOnlyActionStates(); // prepend content-specific actions such as "Open Link", "Copy Email Address" etc. QList contentActions = _view->filterActions(position); auto contentSeparator = new QAction(popup); contentSeparator->setSeparator(true); contentActions << contentSeparator; popup->insertActions(popup->actions().value(0, nullptr), contentActions); // always update this submenu before showing the context menu, // because the available search services might have changed // since the context menu is shown last time updateWebSearchMenu(); _preventClose = true; if (_showMenuAction != nullptr) { if ( _showMenuAction->isChecked() ) { popup->removeAction( _showMenuAction); } else { popup->insertAction(_switchProfileMenu, _showMenuAction); } } QAction* chosen = popup->exec(_view->mapToGlobal(position)); // check for validity of the pointer to the popup menu if (!popup.isNull()) { // Remove content-specific actions // // If the close action was chosen, the popup menu will be partially // destroyed at this point, and the rest will be destroyed later by // 'chosen->trigger()' foreach(QAction * action, contentActions) { popup->removeAction(action); } delete contentSeparator; } _preventClose = false; if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) { chosen->trigger(); } } else { qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << _session->title(Session::NameRole) << ", no GUI factory available to build the popup."; } } void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event) { QCoreApplication::sendEvent(_view, event); setSearchStartToWindowCurrentLine(); } void SessionController::sessionStateChanged(int state) { if (state == _previousState) { return; } if (state == NOTIFYACTIVITY) { setIcon(*_activityIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYSILENCE) { setIcon(*_silenceIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYNORMAL) { if (_sessionIconName != _session->iconName()) { _sessionIconName = _session->iconName(); _sessionIcon = QIcon::fromTheme(_sessionIconName); } updateSessionIcon(); } _previousState = state; } void SessionController::zmodemDownload() { QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz")); if (zmodem.isEmpty()) { zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz")); } if (!zmodem.isEmpty()) { const QString path = QFileDialog::getExistingDirectory(_view, i18n("Save ZModem Download to..."), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!path.isEmpty()) { _session->startZModem(zmodem, path, QStringList()); return; } } else { KMessageBox::error(_view, i18n("

A ZModem file transfer attempt has been detected, " "but no suitable ZModem software was found on this system.

" "

You may wish to install the 'rzsz' or 'lrzsz' package.

")); } _session->cancelZModem(); return; } void SessionController::zmodemUpload() { if (_session->isZModemBusy()) { KMessageBox::sorry(_view, i18n("

The current session already has a ZModem file transfer in progress.

")); return; } _session->setZModemBusy(true); QString zmodem = QStandardPaths::findExecutable(QStringLiteral("sz")); if (zmodem.isEmpty()) { zmodem = QStandardPaths::findExecutable(QStringLiteral("lsz")); } if (zmodem.isEmpty()) { KMessageBox::sorry(_view, i18n("

No suitable ZModem software was found on this system.

" "

You may wish to install the 'rzsz' or 'lrzsz' package.

")); return; } QStringList files = QFileDialog::getOpenFileNames(_view, i18n("Select Files for ZModem Upload"), QDir::homePath()); if (!files.isEmpty()) { _session->startZModem(zmodem, QString(), files); } } bool SessionController::isKonsolePart() const { // Check to see if we are being called from Konsole or a KPart return !(qApp->applicationName() == QLatin1String("konsole")); } SessionTask::SessionTask(QObject* parent) : QObject(parent) , _autoDelete(false) { } void SessionTask::setAutoDelete(bool enable) { _autoDelete = enable; } bool SessionTask::autoDelete() const { return _autoDelete; } void SessionTask::addSession(Session* session) { _sessions << session; } QList SessionTask::sessions() const { return _sessions; } SaveHistoryTask::SaveHistoryTask(QObject* parent) : SessionTask(parent) { } SaveHistoryTask::~SaveHistoryTask() = default; void SaveHistoryTask::execute() { // TODO - think about the UI when saving multiple history sessions, if there are more than two or // three then providing a URL for each one will be tedious // TODO - show a warning ( preferably passive ) if saving the history output fails QFileDialog* dialog = new QFileDialog(QApplication::activeWindow(), QString(), QDir::homePath()); dialog->setAcceptMode(QFileDialog::AcceptSave); QStringList mimeTypes; mimeTypes << QStringLiteral("text/plain"); mimeTypes << QStringLiteral("text/html"); dialog->setMimeTypeFilters(mimeTypes); // iterate over each session in the task and display a dialog to allow the user to choose where // to save that session's history. // then start a KIO job to transfer the data from the history to the chosen URL foreach(const SessionPtr& session, sessions()) { dialog->setWindowTitle(i18n("Save Output From %1", session->title(Session::NameRole))); int result = dialog->exec(); if (result != QDialog::Accepted) { continue; } QUrl url = (dialog->selectedUrls()).at(0); if (!url.isValid()) { // UI: Can we make this friendlier? KMessageBox::sorry(nullptr , i18n("%1 is an invalid URL, the output could not be saved.", url.url())); continue; } KIO::TransferJob* job = KIO::put(url, -1, // no special permissions // overwrite existing files // do not resume an existing transfer // show progress information only for remote // URLs KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags) // a better solution would be to show progress // information after a certain period of time // instead, since the overall speed of transfer // depends on factors other than just the protocol // used ); SaveJob jobInfo; jobInfo.session = session; jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem // lastLineFetched is used to keep track of how much of the history // has already been sent, and where the next request should continue // from. // this is set to -1 to indicate the job has just been started if (((dialog->selectedNameFilter()).contains(QLatin1String("html"), Qt::CaseInsensitive)) || ((dialog->selectedFiles()).at(0).endsWith(QLatin1String("html"), Qt::CaseInsensitive))) { jobInfo.decoder = new HTMLDecoder(); } else { jobInfo.decoder = new PlainTextDecoder(); } _jobSession.insert(job, jobInfo); connect(job, &KIO::TransferJob::dataReq, this, &Konsole::SaveHistoryTask::jobDataRequested); connect(job, &KIO::TransferJob::result, this, &Konsole::SaveHistoryTask::jobResult); } dialog->deleteLater(); } void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data) { // TODO - Report progress information for the job // PERFORMANCE: Do some tests and tweak this value to get faster saving const int LINES_PER_REQUEST = 500; SaveJob& info = _jobSession[job]; // transfer LINES_PER_REQUEST lines from the session's history // to the save location if (!info.session.isNull()) { // note: when retrieving lines from the emulation, // the first line is at index 0. int sessionLines = info.session->emulation()->lineCount(); if (sessionLines - 1 == info.lastLineFetched) { return; // if there is no more data to transfer then stop the job } int copyUpToLine = qMin(info.lastLineFetched + LINES_PER_REQUEST , sessionLines - 1); QTextStream stream(&data, QIODevice::ReadWrite); info.decoder->begin(&stream); info.session->emulation()->writeToStream(info.decoder , info.lastLineFetched + 1 , copyUpToLine); info.decoder->end(); info.lastLineFetched = copyUpToLine; } } void SaveHistoryTask::jobResult(KJob* job) { if (job->error() != 0) { KMessageBox::sorry(nullptr , i18n("A problem occurred when saving the output.\n%1", job->errorString())); } TerminalCharacterDecoder * decoder = _jobSession[job].decoder; _jobSession.remove(job); delete decoder; // notify the world that the task is done emit completed(true); if (autoDelete()) { deleteLater(); } } void SearchHistoryTask::addScreenWindow(Session* session , ScreenWindow* searchWindow) { _windows.insert(session, searchWindow); } void SearchHistoryTask::execute() { QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows); while (iter.hasNext()) { iter.next(); executeOnScreenWindow(iter.key() , iter.value()); } } void SearchHistoryTask::executeOnScreenWindow(SessionPtr session , ScreenWindowPtr window) { Q_ASSERT(session); Q_ASSERT(window); Emulation* emulation = session->emulation(); if (!_regExp.pattern().isEmpty()) { int pos = -1; const bool forwards = (_direction == Enum::ForwardsSearch); const int lastLine = window->lineCount() - 1; int startLine; if (forwards && (_startLine == lastLine)) { startLine = 0; } else if (!forwards && (_startLine == 0)) { startLine = lastLine; } else { startLine = _startLine + (forwards ? 1 : -1); } QString string; //text stream to read history into string for pattern or regular expression searching QTextStream searchStream(&string); PlainTextDecoder decoder; decoder.setRecordLinePositions(true); //setup first and last lines depending on search direction int line = startLine; //read through and search history in blocks of 10K lines. //this balances the need to retrieve lots of data from the history each time //(for efficient searching) //without using silly amounts of memory if the history is very large. const int maxDelta = qMin(window->lineCount(), 10000); int delta = forwards ? maxDelta : -maxDelta; int endLine = line; bool hasWrapped = false; // set to true when we reach the top/bottom // of the output and continue from the other // end //loop through history in blocks of lines. do { // ensure that application does not appear to hang // if searching through a lengthy output QApplication::processEvents(); // calculate lines to search in this iteration if (hasWrapped) { if (endLine == lastLine) { line = 0; } else if (endLine == 0) { line = lastLine; } endLine += delta; if (forwards) { endLine = qMin(startLine , endLine); } else { endLine = qMax(startLine , endLine); } } else { endLine += delta; if (endLine > lastLine) { hasWrapped = true; endLine = lastLine; } else if (endLine < 0) { hasWrapped = true; endLine = 0; } } decoder.begin(&searchStream); emulation->writeToStream(&decoder, qMin(endLine, line) , qMax(endLine, line)); decoder.end(); // line number search below assumes that the buffer ends with a new-line string.append(QLatin1Char('\n')); if (forwards) { pos = string.indexOf(_regExp); } else { pos = string.lastIndexOf(_regExp); } //if a match is found, position the cursor on that line and update the screen if (pos != -1) { int newLines = 0; QList linePositions = decoder.linePositions(); while (newLines < linePositions.count() && linePositions[newLines] <= pos) { newLines++; } // ignore the new line at the start of the buffer newLines--; int findPos = qMin(line, endLine) + newLines; highlightResult(window, findPos); emit completed(true); return; } //clear the current block of text and move to the next one string.clear(); line = endLine; } while (startLine != endLine); // if no match was found, clear selection to indicate this window->clearSelection(); window->notifyOutputChanged(); } emit completed(false); } void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos) { //work out how many lines into the current block of text the search result was found //- looks a little painful, but it only has to be done once per search. ////qDebug() << "Found result at line " << findPos; //update display to show area of history containing selection if ((findPos < window->currentLine()) || (findPos >= (window->currentLine() + window->windowLines()))) { int centeredScrollPos = findPos - window->windowLines() / 2; if (centeredScrollPos < 0) { centeredScrollPos = 0; } window->scrollTo(centeredScrollPos); } window->setTrackOutput(false); window->notifyOutputChanged(); window->setCurrentResultLine(findPos); } SearchHistoryTask::SearchHistoryTask(QObject* parent) : SessionTask(parent) , _direction(Enum::BackwardsSearch) , _startLine(0) { } void SearchHistoryTask::setSearchDirection(Enum::SearchDirection direction) { _direction = direction; } void SearchHistoryTask::setStartLine(int line) { _startLine = line; } Enum::SearchDirection SearchHistoryTask::searchDirection() const { return _direction; } void SearchHistoryTask::setRegExp(const QRegularExpression &expression) { _regExp = expression; } QRegularExpression SearchHistoryTask::regExp() const { return _regExp; } QString SessionController::userTitle() const { if (!_session.isNull()) { return _session->userTitle(); } else { return QString(); } } diff --git a/src/SessionController.h b/src/SessionController.h index 2e8c324f..da198e6a 100644 --- a/src/SessionController.h +++ b/src/SessionController.h @@ -1,545 +1,545 @@ /* Copyright 2006-2008 by Robert Knight 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. */ #ifndef SESSIONCONTROLLER_H #define SESSIONCONTROLLER_H // Qt #include #include #include #include #include #include // KDE #include // Konsole #include "ViewProperties.h" #include "Profile.h" #include "Enumeration.h" namespace KIO { class Job; } class QAction; class QTextCodec; class QKeyEvent; class QTimer; class QUrl; class KCodecAction; class KJob; class QAction; class KActionMenu; namespace Konsole { class Session; class SessionGroup; class ScreenWindow; class TerminalDisplay; class IncrementalSearchBar; class ProfileList; class RegExpFilter; class UrlFilter; class FileFilter; class EditProfileDialog; // SaveHistoryTask class TerminalCharacterDecoder; typedef QPointer SessionPtr; /** * Provides the menu actions to manipulate a single terminal session and view pair. * The actions provided by this class are defined in the sessionui.rc XML file. * * SessionController monitors the session and provides access to basic information * about the session such as title(), icon() and currentDir(). SessionController * provides notifications of activity in the session via the activity() signal. * * When the controlled view receives the focus, the focused() signal is emitted * with a pointer to the controller. This can be used by main application window * which contains the view to plug the controller's actions into the menu when * the view is focused. */ class KONSOLEPRIVATE_EXPORT SessionController : public ViewProperties, public KXMLGUIClient { Q_OBJECT public: enum CopyInputToEnum { /** Copy keyboard input to all the other tabs in current window */ CopyInputToAllTabsMode = 0, /** Copy keyboard input to user selected tabs in current window */ CopyInputToSelectedTabsMode = 1, /** Do not copy keyboard input to other tabs */ CopyInputToNoneMode = 2 }; /** * Constructs a new SessionController which operates on @p session and @p view. */ SessionController(Session *session, TerminalDisplay *view, QObject *parent); ~SessionController() Q_DECL_OVERRIDE; /** Returns the session associated with this controller */ QPointer session() { return _session; } /** Returns the view associated with this controller */ QPointer view() { return _view; } /** * Returns the "window title" of the associated session. */ QString userTitle() const; /** * Returns true if the controller is valid. * A valid controller is one which has a non-null session() and view(). * * Equivalent to "!session().isNull() && !view().isNull()" */ bool isValid() const; /** Set the start line from which the next search will be done **/ void setSearchStartTo(int line); /** set start line to the first or last line (depending on the reverse search * setting) in the terminal display **/ void setSearchStartToWindowCurrentLine(); /** * Sets the widget used for searches through the session's output. * * When the user clicks on the "Search Output" menu action the @p searchBar 's * show() method will be called. The SessionController will then connect to the search * bar's signals to update the search when the widget's controls are pressed. */ void setSearchBar(IncrementalSearchBar *searchBar); /** * see setSearchBar() */ IncrementalSearchBar *searchBar() const; /** * Sets the action displayed in the session's context menu to hide or * show the menu bar. */ void setShowMenuAction(QAction *action); EditProfileDialog *profileDialogPointer(); // reimplemented QUrl url() const Q_DECL_OVERRIDE; QString currentDir() const Q_DECL_OVERRIDE; void rename() Q_DECL_OVERRIDE; bool confirmClose() const Q_DECL_OVERRIDE; virtual bool confirmForceClose() const; // Reimplemented to watch for events happening to the view bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; /** Returns the set of all controllers that exist. */ static QSet allControllers() { return _allControllers; } /* Returns true if called within a KPart; false if called within Konsole. */ bool isKonsolePart() const; bool isReadOnly() const; Q_SIGNALS: /** * Emitted when the view associated with the controller is focused. * This can be used by other classes to plug the controller's actions into a window's * menus. */ void focused(SessionController *controller); void rawTitleChanged(); /** * Emitted when the current working directory of the session associated with * the controller is changed. */ void currentDirectoryChanged(const QString &dir); /** * Emitted when the user changes the tab title. */ void tabRenamedByUser(bool renamed) const; public Q_SLOTS: /** * Issues a command to the session to navigate to the specified URL. * This may not succeed if the foreground program does not understand * the command sent to it ( 'cd path' for local URLs ) or is not * responding to input. * * openUrl() currently supports urls for local paths and those * using the 'ssh' protocol ( eg. "ssh://joebloggs@hostname" ) */ void openUrl(const QUrl &url); /** * update actions which are meaningful only when primary screen is in use. */ void setupPrimaryScreenSpecificActions(bool use); /** * update actions which are closely related with the selected text. */ void selectionChanged(const QString &selectedText); /** * close the associated session. This might involve user interaction for * confirmation. */ void closeSession(); /** Increase font size */ void increaseFontSize(); /** Decrease font size */ void decreaseFontSize(); private Q_SLOTS: // menu item handlers void openBrowser(); void copy(); void paste(); void selectAll(); void selectLine(); void pasteFromX11Selection(); // shortcut only void copyInputActionsTriggered(QAction *action); void copyInputToAllTabs(); void copyInputToSelectedTabs(); void copyInputToNone(); void editCurrentProfile(); void changeCodec(QTextCodec *codec); void enableSearchBar(bool showSearchBar); void searchHistory(bool showSearchBar); void searchBarEvent(); void searchFrom(); void findNextInHistory(); void findPreviousInHistory(); void changeSearchMatch(); void print_screen(); void saveHistory(); void showHistoryOptions(); void clearHistory(); void clearHistoryAndReset(); void monitorActivity(bool monitor); void monitorSilence(bool monitor); void renameSession(); void switchProfile(Profile::Ptr profile); void handleWebShortcutAction(); void configureWebShortcuts(); void sendSignal(QAction *action); void sendBackgroundColor(); void toggleReadOnly(); // other void prepareSwitchProfileMenu(); void updateCodecAction(); void showDisplayContextMenu(const QPoint &position); void movementKeyFromSearchBarReceived(QKeyEvent *event); void sessionStateChanged(int state); - void sessionTitleChanged(); + void sessionAttributeChanged(); void sessionReadOnlyChanged(); void searchTextChanged(const QString &text); void searchCompleted(bool success); void searchClosed(); // called when the user clicks on the // history search bar's close button void updateFilterList(Profile::Ptr profile); // Called when the profile has changed, so we might need to change the list of filters void interactionHandler(); void snapshot(); // called periodically as the user types // to take a snapshot of the state of the // foreground process in the terminal void highlightMatches(bool highlight); void scrollBackOptionsChanged(int mode, int lines); void sessionResizeRequest(const QSize &size); void trackOutput(QKeyEvent *event); // move view to end of current output // when a key press occurs in the // display area void updateSearchFilter(); void zmodemDownload(); void zmodemUpload(); // update actions related with selected text void updateCopyAction(const QString &selectedText); void updateWebSearchMenu(); private: Q_DISABLE_COPY(SessionController) // begins the search // text - pattern to search for // direction - value from SearchHistoryTask::SearchDirection enum to specify // the search direction void beginSearch(const QString &text, Enum::SearchDirection direction); QRegularExpression regexpFromSearchBarOptions() const; bool reverseSearchChecked() const; void setupCommonActions(); void setupExtraActions(); void removeSearchFilter(); // remove and delete the current search filter if set void setFindNextPrevEnabled(bool enabled); void listenForScreenWindowUpdates(); private: void updateSessionIcon(); void updateReadOnlyActionStates(); QPointer _session; QPointer _view; SessionGroup *_copyToGroup; ProfileList *_profileList; QIcon _sessionIcon; QString _sessionIconName; int _previousState; RegExpFilter *_searchFilter; UrlFilter *_urlFilter; FileFilter *_fileFilter; QAction *_copyInputToAllTabsAction; QAction *_findAction; QAction *_findNextAction; QAction *_findPreviousAction; QTimer *_interactionTimer; int _searchStartLine; int _prevSearchResultLine; QPointer _searchBar; KCodecAction *_codecAction; KActionMenu *_switchProfileMenu; KActionMenu *_webSearchMenu; bool _listenForScreenWindowUpdates; bool _preventClose; bool _keepIconUntilInteraction; QString _selectedText; QAction *_showMenuAction; static QSet _allControllers; static int _lastControllerId; QStringList _bookmarkValidProgramsToClear; bool _isSearchBarEnabled; QPointer _editProfileDialog; QString _searchText; }; inline bool SessionController::isValid() const { return !_session.isNull() && !_view.isNull(); } /** * Abstract class representing a task which can be performed on a group of sessions. * * Create a new instance of the appropriate sub-class for the task you want to perform and * call the addSession() method to add each session which needs to be processed. * * Finally, call the execute() method to perform the sub-class specific action on each * of the sessions. */ class SessionTask : public QObject { Q_OBJECT public: explicit SessionTask(QObject *parent = nullptr); /** * Sets whether the task automatically deletes itself when the task has been finished. * Depending on whether the task operates synchronously or asynchronously, the deletion * may be scheduled immediately after execute() returns or it may happen some time later. */ void setAutoDelete(bool enable); /** Returns true if the task automatically deletes itself. See setAutoDelete() */ bool autoDelete() const; /** Adds a new session to the group */ void addSession(Session *session); /** * Executes the task on each of the sessions in the group. * The completed() signal is emitted when the task is finished, depending on the specific sub-class * execute() may be synchronous or asynchronous */ virtual void execute() = 0; Q_SIGNALS: /** * Emitted when the task has completed. * Depending on the task this may occur just before execute() returns, or it * may occur later * * @param success Indicates whether the task completed successfully or not */ void completed(bool success); protected: /** Returns a list of sessions in the group */ QList< SessionPtr > sessions() const; private: bool _autoDelete; QList< SessionPtr > _sessions; }; /** * A task which prompts for a URL for each session and saves that session's output * to the given URL */ class SaveHistoryTask : public SessionTask { Q_OBJECT public: /** Constructs a new task to save session output to URLs */ explicit SaveHistoryTask(QObject *parent = nullptr); ~SaveHistoryTask() Q_DECL_OVERRIDE; /** * Opens a save file dialog for each session in the group and begins saving * each session's history to the given URL. * * The data transfer is performed asynchronously and will continue after execute() returns. */ void execute() Q_DECL_OVERRIDE; private Q_SLOTS: void jobDataRequested(KIO::Job *job, QByteArray &data); void jobResult(KJob *job); private: class SaveJob // structure to keep information needed to process // incoming data requests from jobs { public: SessionPtr session; // the session associated with a history save job int lastLineFetched; // the last line processed in the previous data request // set this to -1 at the start of the save job TerminalCharacterDecoder *decoder; // decoder used to convert terminal characters // into output }; QHash _jobSession; }; //class SearchHistoryThread; /** * A task which searches through the output of sessions for matches for a given regular expression. * SearchHistoryTask operates on ScreenWindow instances rather than sessions added by addSession(). * A screen window can be added to the list to search using addScreenWindow() * * When execute() is called, the search begins in the direction specified by searchDirection(), * starting at the position of the current selection. * * FIXME - This is not a proper implementation of SessionTask, in that it ignores sessions specified * with addSession() * * TODO - Implementation requirements: * May provide progress feedback to the user when searching very large output logs. */ class SearchHistoryTask : public SessionTask { Q_OBJECT public: /** * Constructs a new search task. */ explicit SearchHistoryTask(QObject *parent = nullptr); /** Adds a screen window to the list to search when execute() is called. */ void addScreenWindow(Session *session, ScreenWindow *searchWindow); /** Sets the regular expression which is searched for when execute() is called */ void setRegExp(const QRegularExpression &expression); /** Returns the regular expression which is searched for when execute() is called */ QRegularExpression regExp() const; /** Specifies the direction to search in when execute() is called. */ void setSearchDirection(Enum::SearchDirection direction); /** Returns the current search direction. See setSearchDirection(). */ Enum::SearchDirection searchDirection() const; /** The line from which the search will be done **/ void setStartLine(int line); /** * Performs a search through the session's history, starting at the position * of the current selection, in the direction specified by setSearchDirection(). * * If it finds a match, the ScreenWindow specified in the constructor is * scrolled to the position where the match occurred and the selection * is set to the matching text. execute() then returns immediately. * * To continue the search looking for further matches, call execute() again. */ void execute() Q_DECL_OVERRIDE; private: typedef QPointer ScreenWindowPtr; void executeOnScreenWindow(SessionPtr session, ScreenWindowPtr window); void highlightResult(ScreenWindowPtr window, int position); QMap< SessionPtr, ScreenWindowPtr > _windows; QRegularExpression _regExp; Enum::SearchDirection _direction; int _startLine; //static QPointer _thread; }; } #endif //SESSIONCONTROLLER_H diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index e7a70a42..b7a5ff9c 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)), + _pendingSessionAttributesUpdates(QHash()), + _sessionAttributesUpdateTimer(new QTimer(this)), _reportFocusEvents(false) { - _titleUpdateTimer->setSingleShot(true); - QObject::connect(_titleUpdateTimer, &QTimer::timeout, this, - &Konsole::Vt102Emulation::updateTitle); + _sessionAttributesUpdateTimer->setSingleShot(true); + QObject::connect(_sessionAttributesUpdateTimer, &QTimer::timeout, this, + &Konsole::Vt102Emulation::updateSessionAttributes); 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 (Xte ) { processSessionAttributeRequest(); 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() +void Vt102Emulation::processSessionAttributeRequest() { // Describes the window or terminal session attribute to change - // See Session::UserTitleChange for possible values + // See Session::SessionAttributes 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); + _pendingSessionAttributesUpdates[attribute] = value; + _sessionAttributesUpdateTimer->start(20); } -void Vt102Emulation::updateTitle() +void Vt102Emulation::updateSessionAttributes() { - QListIterator iter( _pendingTitleUpdates.keys() ); + QListIterator iter(_pendingSessionAttributesUpdates.keys()); while (iter.hasNext()) { int arg = iter.next(); - emit titleChanged( arg , _pendingTitleUpdates[arg] ); + emit sessionAttributeChanged(arg , _pendingSessionAttributesUpdates[arg]); } - _pendingTitleUpdates.clear(); + _pendingSessionAttributesUpdates.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 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 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; } diff --git a/src/Vt102Emulation.h b/src/Vt102Emulation.h index 0136ce82..4ef89524 100644 --- a/src/Vt102Emulation.h +++ b/src/Vt102Emulation.h @@ -1,196 +1,195 @@ /* 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 VT102EMULATION_H #define VT102EMULATION_H // Qt #include // Konsole #include "Emulation.h" #include "Screen.h" class QTimer; class QKeyEvent; #define MODE_AppScreen (MODES_SCREEN+0) // Mode #1 #define MODE_AppCuKeys (MODES_SCREEN+1) // Application cursor keys (DECCKM) #define MODE_AppKeyPad (MODES_SCREEN+2) // #define MODE_Mouse1000 (MODES_SCREEN+3) // Send mouse X,Y position on press and release #define MODE_Mouse1001 (MODES_SCREEN+4) // Use Hilight mouse tracking #define MODE_Mouse1002 (MODES_SCREEN+5) // Use cell motion mouse tracking #define MODE_Mouse1003 (MODES_SCREEN+6) // Use all motion mouse tracking #define MODE_Mouse1005 (MODES_SCREEN+7) // Xterm-style extended coordinates #define MODE_Mouse1006 (MODES_SCREEN+8) // 2nd Xterm-style extended coordinates #define MODE_Mouse1007 (MODES_SCREEN+9) // XTerm Alternate Scroll mode; also check AlternateScrolling profile property #define MODE_Mouse1015 (MODES_SCREEN+10) // Urxvt-style extended coordinates #define MODE_Ansi (MODES_SCREEN+11) // Use US Ascii for character sets G0-G3 (DECANM) #define MODE_132Columns (MODES_SCREEN+12) // 80 <-> 132 column mode switch (DECCOLM) #define MODE_Allow132Columns (MODES_SCREEN+13) // Allow DECCOLM mode #define MODE_BracketedPaste (MODES_SCREEN+14) // Xterm-style bracketed paste mode #define MODE_total (MODES_SCREEN+15) namespace Konsole { extern unsigned short vt100_graphics[32]; struct CharCodes { // coding info char charset[4]; // int cu_cs; // actual charset. bool graphic; // Some VT100 tricks bool pound; // Some VT100 tricks bool sa_graphic; // saved graphic bool sa_pound; // saved pound }; /** * Provides an xterm compatible terminal emulation based on the DEC VT102 terminal. * A full description of this terminal can be found at http://vt100.net/docs/vt102-ug/ * * In addition, various additional xterm escape sequences are supported to provide * features such as mouse input handling. * See http://rtfm.etla.org/xterm/ctlseq.html for a description of xterm's escape * sequences. * */ class Vt102Emulation : public Emulation { Q_OBJECT public: /** Constructs a new emulation */ Vt102Emulation(); ~Vt102Emulation() Q_DECL_OVERRIDE; // reimplemented from Emulation void clearEntireScreen() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; char eraseChar() const Q_DECL_OVERRIDE; public Q_SLOTS: // reimplemented from Emulation void sendString(const QByteArray &string) Q_DECL_OVERRIDE; void sendText(const QString &text) Q_DECL_OVERRIDE; void sendKeyEvent(QKeyEvent *) Q_DECL_OVERRIDE; void sendMouseEvent(int buttons, int column, int line, int eventType) Q_DECL_OVERRIDE; void focusLost() Q_DECL_OVERRIDE; void focusGained() Q_DECL_OVERRIDE; protected: // reimplemented from Emulation void setMode(int mode) Q_DECL_OVERRIDE; void resetMode(int mode) Q_DECL_OVERRIDE; void receiveChar(uint cc) Q_DECL_OVERRIDE; private Q_SLOTS: - //causes changeTitle() to be emitted for each (int,QString) pair in pendingTitleUpdates - //used to buffer multiple title updates - void updateTitle(); + // Causes sessionAttributeChanged() to be emitted for each (int,QString) + // pair in _pendingSessionAttributesUpdates. + // Used to buffer multiple attribute updates in the current session + void updateSessionAttributes(); private: unsigned int applyCharset(uint c); void setCharset(int n, int cs); void useCharset(int n); void setAndUseCharset(int n, int cs); void saveCursor(); void restoreCursor(); void resetCharset(int scrno); void setMargins(int top, int bottom); //set margins for all screens back to their defaults void setDefaultMargins(); // returns true if 'mode' is set or false otherwise bool getMode(int mode); // saves the current boolean value of 'mode' void saveMode(int mode); // restores the boolean value of 'mode' void restoreMode(int mode); // resets all modes // (except MODE_Allow132Columns) void resetModes(); void resetTokenizer(); #define MAX_TOKEN_LENGTH 256 // Max length of tokens (e.g. window title) void addToCurrentToken(int cc); int tokenBuffer[MAX_TOKEN_LENGTH]; //FIXME: overflow? int tokenBufferPos; #define MAXARGS 15 void addDigit(int dig); void addArgument(); int argv[MAXARGS]; int argc; void initTokenizer(); // Set of flags for each of the ASCII characters which indicates // what category they fall into (printable character, control, digit etc.) // for the purposes of decoding terminal output int charClass[256]; void reportDecodingError(); void processToken(int code, int p, int q); - void processWindowAttributeRequest(); - void requestWindowAttribute(int); + void processSessionAttributeRequest(); void reportTerminalType(); void reportSecondaryAttributes(); void reportStatus(); void reportAnswerBack(); void reportCursorPosition(); void reportTerminalParms(int p); // clears the screen and resizes it to the specified // number of columns void clearScreenAndSetColumns(int columnCount); CharCodes _charset[2]; class TerminalState { public: // Initializes all modes to false TerminalState() { memset(&mode, 0, MODE_total * sizeof(bool)); } bool mode[MODE_total]; }; TerminalState _currentModes; TerminalState _savedModes; - //hash table and timer for buffering calls to the session instance - //to update the name of the session - //or window title. - //these calls occur when certain escape sequences are seen in the - //output from the terminal - QHash _pendingTitleUpdates; - QTimer *_titleUpdateTimer; + // Hash table and timer for buffering calls to update certain session + // attributes (e.g. the name of the session, window title). + // These calls occur when certain escape sequences are detected in the + // output from the terminal. See Emulation::sessionAttributeChanged() + QHash _pendingSessionAttributesUpdates; + QTimer *_sessionAttributesUpdateTimer; bool _reportFocusEvents; }; } #endif // VT102EMULATION_H