diff --git a/src/Emulation.cpp b/src/Emulation.cpp index 612ea350..40e036c8 100644 --- a/src/Emulation.cpp +++ b/src/Emulation.cpp @@ -1,346 +1,342 @@ /* Copyright 2007-2008 Robert Knight Copyright 1997,1998 by Lars Doelle Copyright 1996 by Matthias Ettrich This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "Emulation.h" // Qt #include // Konsole #include "KeyboardTranslator.h" #include "KeyboardTranslatorManager.h" #include "Screen.h" #include "ScreenWindow.h" using namespace Konsole; Emulation::Emulation() : _windows(QList()), _currentScreen(nullptr), _codec(nullptr), _decoder(nullptr), _keyTranslator(nullptr), _usesMouseTracking(false), _bracketedPasteMode(false), _bulkTimer1(new QTimer(this)), _bulkTimer2(new QTimer(this)), _imageSizeInitialized(false) { // create screens with a default size _screen[0] = new Screen(40, 80); _screen[1] = new Screen(40, 80); _currentScreen = _screen[0]; QObject::connect(&_bulkTimer1, &QTimer::timeout, this, &Konsole::Emulation::showBulk); QObject::connect(&_bulkTimer2, &QTimer::timeout, this, &Konsole::Emulation::showBulk); // listen for mouse status changes connect(this, &Konsole::Emulation::programRequestsMouseTracking, this, &Konsole::Emulation::setUsesMouseTracking); connect(this, &Konsole::Emulation::programBracketedPasteModeChanged, this, &Konsole::Emulation::bracketedPasteModeChanged); } bool Emulation::programUsesMouseTracking() const { return _usesMouseTracking; } void Emulation::setUsesMouseTracking(bool usesMouseTracking) { _usesMouseTracking = usesMouseTracking; } bool Emulation::programBracketedPasteMode() const { return _bracketedPasteMode; } void Emulation::bracketedPasteModeChanged(bool bracketedPasteMode) { _bracketedPasteMode = bracketedPasteMode; } ScreenWindow *Emulation::createWindow() { auto window = new ScreenWindow(_currentScreen); _windows << window; connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::bufferedUpdate); connect(window, &Konsole::ScreenWindow::selectionChanged, this, &Konsole::Emulation::checkSelectedText); connect(this, &Konsole::Emulation::outputChanged, window, &Konsole::ScreenWindow::notifyOutputChanged); return window; } void Emulation::checkScreenInUse() { emit primaryScreenInUse(_currentScreen == _screen[0]); } void Emulation::checkSelectedText() { QString text = _currentScreen->selectedText(Screen::PreserveLineBreaks); emit selectionChanged(text); } Emulation::~Emulation() { for (ScreenWindow *window : qAsConst(_windows)) { delete window; } delete _screen[0]; delete _screen[1]; delete _decoder; } void Emulation::setScreen(int index) { Screen *oldScreen = _currentScreen; _currentScreen = _screen[index & 1]; if (_currentScreen != oldScreen) { // tell all windows onto this emulation to switch to the newly active screen for (ScreenWindow *window : qAsConst(_windows)) { window->setScreen(_currentScreen); } checkScreenInUse(); checkSelectedText(); } } void Emulation::clearHistory() { _screen[0]->setScroll(_screen[0]->getScroll(), false); } void Emulation::setHistory(const HistoryType &history) { _screen[0]->setScroll(history); showBulk(); } const HistoryType &Emulation::history() const { return _screen[0]->getScroll(); } void Emulation::setCodec(const QTextCodec *codec) { if (codec != nullptr) { _codec = codec; delete _decoder; _decoder = _codec->makeDecoder(); emit useUtf8Request(utf8()); } else { setCodec(LocaleCodec); } } void Emulation::setCodec(EmulationCodec codec) { if (codec == Utf8Codec) { setCodec(QTextCodec::codecForName("utf8")); } else if (codec == LocaleCodec) { setCodec(QTextCodec::codecForLocale()); } } void Emulation::setKeyBindings(const QString &name) { _keyTranslator = KeyboardTranslatorManager::instance()->findTranslator(name); if (_keyTranslator == nullptr) { _keyTranslator = KeyboardTranslatorManager::instance()->defaultTranslator(); } } QString Emulation::keyBindings() const { return _keyTranslator->name(); } // process application unicode input to terminal // this is a trivial scanner void Emulation::receiveChar(uint c) { c &= 0xff; switch (c) { case '\b': _currentScreen->backspace(); break; case '\t': _currentScreen->tab(); break; case '\n': _currentScreen->newLine(); break; case '\r': _currentScreen->toStartOfLine(); break; case 0x07: - emit stateSet(NOTIFYBELL); + emit bell(); break; default: _currentScreen->displayCharacter(c); break; } } void Emulation::sendKeyEvent(QKeyEvent *ev) { - emit stateSet(NOTIFYNORMAL); - if (!ev->text().isEmpty()) { // A block of text // Note that the text is proper unicode. // We should do a conversion here emit sendData(ev->text().toLocal8Bit()); } } void Emulation::sendMouseEvent(int /*buttons*/, int /*column*/, int /*row*/, int /*eventType*/) { // default implementation does nothing } /* We are doing code conversion from locale to unicode first. */ void Emulation::receiveData(const char *text, int length) { - emit stateSet(NOTIFYACTIVITY); - bufferedUpdate(); QVector unicodeText = _decoder->toUnicode(text, length).toUcs4(); //send characters to terminal emulator for (auto &&i : unicodeText) { receiveChar(i); } //look for z-modem indicator //-- someone who understands more about z-modems that I do may be able to move //this check into the above for loop? for (int i = 0; i < length; i++) { if (text[i] == '\030') { if (length - i - 1 > 3) { if (qstrncmp(text + i + 1, "B00", 3) == 0) { emit zmodemDownloadDetected(); } else if (qstrncmp(text + i + 1, "B01", 3) == 0) { emit zmodemUploadDetected(); } } } } } void Emulation::writeToStream(TerminalCharacterDecoder *decoder, int startLine, int endLine) { _currentScreen->writeLinesToStream(decoder, startLine, endLine); } int Emulation::lineCount() const { // sum number of lines currently on _screen plus number of lines in history return _currentScreen->getLines() + _currentScreen->getHistLines(); } void Emulation::showBulk() { _bulkTimer1.stop(); _bulkTimer2.stop(); emit outputChanged(); _currentScreen->resetScrolledLines(); _currentScreen->resetDroppedLines(); } void Emulation::bufferedUpdate() { static const int BULK_TIMEOUT1 = 10; static const int BULK_TIMEOUT2 = 40; _bulkTimer1.setSingleShot(true); _bulkTimer1.start(BULK_TIMEOUT1); if (!_bulkTimer2.isActive()) { _bulkTimer2.setSingleShot(true); _bulkTimer2.start(BULK_TIMEOUT2); } } char Emulation::eraseChar() const { return '\b'; } void Emulation::setImageSize(int lines, int columns) { if ((lines < 1) || (columns < 1)) { return; } QSize screenSize[2] = { QSize(_screen[0]->getColumns(), _screen[0]->getLines()), QSize(_screen[1]->getColumns(), _screen[1]->getLines()) }; QSize newSize(columns, lines); if (newSize == screenSize[0] && newSize == screenSize[1]) { // If this method is called for the first time, always emit // SIGNAL(imageSizeChange()), even if the new size is the same as the // current size. See #176902 if (!_imageSizeInitialized) { emit imageSizeChanged(lines, columns); } } else { _screen[0]->resizeImage(lines, columns); _screen[1]->resizeImage(lines, columns); emit imageSizeChanged(lines, columns); bufferedUpdate(); } if (!_imageSizeInitialized) { _imageSizeInitialized = true; emit imageSizeInitialized(); } } QSize Emulation::imageSize() const { return {_currentScreen->getColumns(), _currentScreen->getLines()}; } diff --git a/src/Emulation.h b/src/Emulation.h index f745b5e5..2e218826 100644 --- a/src/Emulation.h +++ b/src/Emulation.h @@ -1,520 +1,493 @@ /* 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() 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 event to the terminal. */ virtual void focusChanged(bool focused) = 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 + * Emitted when bell appeared */ - void stateSet(int state); + void bell(); /** * 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: * https://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 * 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 an input string, where ARG is a number specifying * what should be changed and VALUE is a string specifying the new value. * * @param attribute Specifies which session attribute to change: *
    *
  • 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') *
  • *
* @param newValue Specifies the new value of the session attribute */ 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/Part.cpp b/src/Part.cpp index 97885b77..9f56e245 100644 --- a/src/Part.cpp +++ b/src/Part.cpp @@ -1,437 +1,437 @@ /* Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "Part.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include // Konsole #include "EditProfileDialog.h" #include "Emulation.h" #include "Session.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "TerminalDisplay.h" #include "ViewManager.h" #include "ViewContainer.h" #include "KonsoleSettings.h" #include "settings/PartInfo.h" #include "settings/ProfileSettings.h" using namespace Konsole; K_PLUGIN_FACTORY_WITH_JSON(KonsolePartFactory, "konsolepart.json", registerPlugin();) Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList &) : KParts::ReadOnlyPart(parent), _viewManager(nullptr), _pluggedController(nullptr) { // create view widget _viewManager = new ViewManager(this, actionCollection()); _viewManager->setNavigationMethod(ViewManager::NoNavigation); connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::Part::activeViewChanged); connect(_viewManager, &Konsole::ViewManager::empty, this, &Konsole::Part::terminalExited); connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::Part::newTab); _viewManager->widget()->setParent(parentWidget); setWidget(_viewManager->widget()); actionCollection()->addAssociatedWidget(_viewManager->widget()); const QList actionsList = actionCollection()->actions(); for (QAction *action : actionsList) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } // Enable translucency support. _viewManager->widget()->setAttribute(Qt::WA_TranslucentBackground, true); // create basic session createSession(); } Part::~Part() { ProfileManager::instance()->saveSettings(); delete _viewManager; } bool Part::openFile() { return false; } void Part::terminalExited() { deleteLater(); } void Part::newTab() { createSession(); } Session *Part::activeSession() const { if (_viewManager->activeViewController() != nullptr) { Q_ASSERT(_viewManager->activeViewController()->session()); return _viewManager->activeViewController()->session(); } else { return nullptr; } } void Part::startProgram(const QString &program, const QStringList &arguments) { Q_ASSERT(activeSession()); // do nothing if the session has already started running if (activeSession()->isRunning()) { return; } if (!program.isEmpty() && !arguments.isEmpty()) { activeSession()->setProgram(program); activeSession()->setArguments(arguments); } activeSession()->run(); } void Part::openTeletype(int ptyMasterFd) { Q_ASSERT(activeSession()); activeSession()->openTeletype(ptyMasterFd); } void Part::showShellInDir(const QString &dir) { Q_ASSERT(activeSession()); // do nothing if the session has already started running if (activeSession()->isRunning()) { return; } // All other checking is done in setInitialWorkingDirectory() if (!dir.isEmpty()) { activeSession()->setInitialWorkingDirectory(dir); } activeSession()->run(); } void Part::sendInput(const QString &text) { Q_ASSERT(activeSession()); activeSession()->sendTextToTerminal(text); } int Part::terminalProcessId() { Q_ASSERT(activeSession()); return activeSession()->processId(); } int Part::foregroundProcessId() { Q_ASSERT(activeSession()); if (activeSession()->isForegroundProcessActive()) { return activeSession()->foregroundProcessId(); } else { return -1; } } QString Part::foregroundProcessName() { Q_ASSERT(activeSession()); if (activeSession()->isForegroundProcessActive()) { return activeSession()->foregroundProcessName(); } else { return QString(); } } QString Part::currentWorkingDirectory() const { Q_ASSERT(activeSession()); return activeSession()->currentWorkingDirectory(); } #ifdef USE_TERMINALINTERFACEV2 QVariant Part::profileProperty(const QString &profileProperty) const { const auto metaEnum = QMetaEnum::fromType(); const auto value = metaEnum.keyToValue(profileProperty.toStdString().c_str()); if (value == -1) { return QString(); } const auto p = static_cast(value); return SessionManager::instance()->sessionProfile(activeSession())->property(p); } QStringList Part::availableProfiles() const { return ProfileManager::instance()->availableProfileNames(); } QString Part::currentProfileName() const { return SessionManager::instance()->sessionProfile(activeSession())->name(); } bool Part::setCurrentProfile(const QString &profileName) { Profile::Ptr profile; for(auto p : ProfileManager::instance()->allProfiles()) { if (p->name() == profileName) { profile = p; break; } } if (!profile) { profile = ProfileManager::instance()->loadProfile(profileName); } SessionManager::instance()->setSessionProfile(activeSession(), profile); return currentProfileName() == profileName; } #endif void Part::createSession(const QString &profileName, const QString &directory) { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); if (!profileName.isEmpty()) { profile = ProfileManager::instance()->loadProfile(profileName); } Q_ASSERT(profile); Session *session = SessionManager::instance()->createSession(profile); // override the default directory specified in the profile if (!directory.isEmpty() && profile->startInCurrentSessionDir()) { session->setInitialWorkingDirectory(directory); } auto newView = _viewManager->createView(session); _viewManager->activeContainer()->addView(newView); } void Part::activeViewChanged(SessionController *controller) { Q_ASSERT(controller); Q_ASSERT(controller->view()); // remove existing controller if (_pluggedController != nullptr) { removeChildClient(_pluggedController); disconnect(_pluggedController, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged); disconnect(_pluggedController, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged); } // insert new controller insertChildClient(controller); connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged); activeViewTitleChanged(controller); connect(controller, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged); const char *displaySignal = SIGNAL(overrideShortcutCheck(QKeyEvent*,bool&)); const char *partSlot = SLOT(overrideTerminalShortcut(QKeyEvent*,bool&)); disconnect(controller->view(), displaySignal, this, partSlot); connect(controller->view(), displaySignal, this, partSlot); _pluggedController = controller; } void Part::overrideTerminalShortcut(QKeyEvent *event, bool &override) { // Shift+Insert is commonly used as the alternate shortcut for // pasting in KDE apps(including konsole), so it deserves some // special treatment. if (((event->modifiers() & Qt::ShiftModifier) != 0u) && (event->key() == Qt::Key_Insert)) { override = false; return; } // override all shortcuts in the embedded terminal by default override = true; emit overrideShortcut(event, override); } void Part::activeViewTitleChanged(ViewProperties *properties) { emit setWindowCaption(properties->title()); } void Part::showManageProfilesDialog(QWidget *parent) { // Make sure this string is unique among all users of this part if (KConfigDialog::showDialog(QStringLiteral("konsolepartmanageprofiles"))) { return; } KConfigDialog *settingsDialog = new KConfigDialog(parent, QStringLiteral("konsolepartmanageprofiles"), KonsoleSettings::self()); settingsDialog->setFaceType(KPageDialog::Tabbed); auto profileSettings = new ProfileSettings(settingsDialog); settingsDialog->addPage(profileSettings, i18nc("@title Preferences page name", "Profiles"), QStringLiteral("configure")); auto partInfoSettings = new PartInfoSettings(settingsDialog); settingsDialog->addPage(partInfoSettings, i18nc("@title Preferences page name", "Part Info"), QStringLiteral("dialog-information")); settingsDialog->show(); } void Part::showEditCurrentProfileDialog(QWidget *parent) { Q_ASSERT(activeSession()); auto dialog = new EditProfileDialog(parent); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setProfile(SessionManager::instance()->sessionProfile(activeSession())); dialog->show(); } void Part::changeSessionSettings(const QString &text) { Q_ASSERT(activeSession()); // send a profile change command, the escape code format // is the same as the normal X-Term commands used to change the window title or icon, // but with a magic value of '50' for the parameter which specifies what to change QString command = QStringLiteral("\033]50;%1\a").arg(text); sendInput(command); } // Konqueror integration bool Part::openUrl(const QUrl &url) { if (KParts::ReadOnlyPart::url() == url) { emit completed(); return true; } setUrl(url); emit setWindowCaption(url.toDisplayString(QUrl::PreferLocalFile)); ////qDebug() << "Set Window Caption to " << url.pathOrUrl(); emit started(nullptr); if (url.isLocalFile()) { showShellInDir(url.path()); } else { showShellInDir(QDir::homePath()); } emit completed(); return true; } void Part::setMonitorSilenceEnabled(bool enabled) { Q_ASSERT(activeSession()); if (enabled) { activeSession()->setMonitorSilence(true); - connect(activeSession(), &Konsole::Session::stateChanged, - this, &Konsole::Part::sessionStateChanged, + connect(activeSession(), &Konsole::Session::notificationsChanged, + this, &Konsole::Part::notificationChanged, Qt::UniqueConnection); } else { activeSession()->setMonitorSilence(false); - disconnect(activeSession(), &Konsole::Session::stateChanged, - this, &Konsole::Part::sessionStateChanged); + disconnect(activeSession(), &Konsole::Session::notificationsChanged, + this, &Konsole::Part::notificationChanged); } } void Part::setMonitorActivityEnabled(bool enabled) { Q_ASSERT(activeSession()); if (enabled) { activeSession()->setMonitorActivity(true); - connect(activeSession(), &Konsole::Session::stateChanged, - this, &Konsole::Part::sessionStateChanged, + connect(activeSession(), &Konsole::Session::notificationsChanged, + this, &Konsole::Part::notificationChanged, Qt::UniqueConnection); } else { activeSession()->setMonitorActivity(false); - disconnect(activeSession(), &Konsole::Session::stateChanged, + disconnect(activeSession(), &Konsole::Session::notificationsChanged, this, - &Konsole::Part::sessionStateChanged); + &Konsole::Part::notificationChanged); } } bool Part::isBlurEnabled() { return ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(activeSession())); } -void Part::sessionStateChanged(int state) +void Part::notificationChanged(Session::Notification notification, bool enabled) { - if (state == NOTIFYSILENCE) { + if (notification == Session::Notification::Silence && enabled) { emit silenceDetected(); - } else if (state == NOTIFYACTIVITY) { + } else if (notification == Session::Notification::Activity && enabled) { emit activityDetected(); } } #include "Part.moc" diff --git a/src/Part.h b/src/Part.h index 283d7d9f..f212d18d 100644 --- a/src/Part.h +++ b/src/Part.h @@ -1,221 +1,222 @@ /* Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef PART_H #define PART_H // KDE #include #include // Qt #include // Konsole +#include "Session.h" #include "config-konsole.h" class QStringList; class QKeyEvent; namespace Konsole { class Session; class SessionController; class ViewManager; class ViewProperties; /** * A re-usable terminal emulator component using the KParts framework which can * be used to embed terminal emulators into other applications. */ #ifdef USE_TERMINALINTERFACEV2 class Part : public KParts::ReadOnlyPart, public TerminalInterfaceV2 { Q_OBJECT Q_INTERFACES(TerminalInterface TerminalInterfaceV2) #else class TerminalInterfaceV2; class Part : public KParts::ReadOnlyPart, public TerminalInterface { Q_OBJECT Q_INTERFACES(TerminalInterface) #endif public: /** Constructs a new Konsole part with the specified parent. */ explicit Part(QWidget *parentWidget, QObject *parent, const QVariantList &); ~Part() override; /** Reimplemented from TerminalInterface. */ void startProgram(const QString &program, const QStringList &arguments) override; /** Reimplemented from TerminalInterface. */ void showShellInDir(const QString &dir) override; /** Reimplemented from TerminalInterface. */ void sendInput(const QString &text) override; /** Reimplemented from TerminalInterface. */ int terminalProcessId() override; /** Reimplemented from TerminalInterface. */ int foregroundProcessId() override; /** Reimplemented from TerminalInterface. */ QString foregroundProcessName() override; /** Reimplemented from TerminalInterface. */ QString currentWorkingDirectory() const override; #ifdef USE_TERMINALINTERFACEV2 /** Reimplemented from TerminalInterfaceV2 */ QStringList availableProfiles() const override; /** Reimplemented from TerminalInterfaceV2 */ QString currentProfileName() const override; /** Reimplemented from TerminalInterfaceV2 */ bool setCurrentProfile(const QString &profileName) override; /** Reimplemented from TerminalInterfaceV2 */ QVariant profileProperty(const QString &profileProperty) const override; #endif public Q_SLOTS: /** * creates and run a session using the specified profile and directory * * @param profileName Specifies the name of the profile to create session * @param directory specifies The initial working directory of the created session * * This is highly experimental. Do not use it at the moment */ void createSession(const QString &profileName = QString(), const QString &directory = QString()); void showManageProfilesDialog(QWidget* parent); /** * Shows the dialog used to edit the profile used by the active session. The * dialog will be non-modal and will delete itself when it is closed. * * This is experimental API and not guaranteed to be present in later KDE 4 * releases. * * @param parent The parent widget of the new dialog. */ void showEditCurrentProfileDialog(QWidget *parent); /** * Sends a profile change command to the active session. This is equivalent to using * the konsoleprofile tool within the session to change its settings. The @p text string * is a semi-colon separated list of property=value pairs, eg. "colors=Linux Colors" * * See the documentation for konsoleprofile for information on the format of @p text * * This is experimental API and not guaranteed to be present in later KDE 4 releases. */ void changeSessionSettings(const QString &text); /** * Connects to an existing pseudo-teletype. See Session::openTeletype(). * This must be called before the session is started by startProgram(), * or showShellInDir() * * @param ptyMasterFd The file descriptor of the pseudo-teletype (pty) master */ void openTeletype(int ptyMasterFd); /** * Toggles monitoring for silence in the active session. If silence is detected, * the silenceDetected() signal is emitted. * * @param enabled Whether to enable or disable monitoring for silence. * */ void setMonitorSilenceEnabled(bool enabled); /** * Toggles monitoring for activity in the active session. If activity is detected, * the activityDetected() signal is emitted. * * @param enabled Whether to enable or disable monitoring for activity. * */ void setMonitorActivityEnabled(bool enabled); /** * Returns the status of the blur of the current profile. * * @return True if blur is enabled for the current active Konsole color profile. * */ bool isBlurEnabled(); Q_SIGNALS: /** * Emitted when the key sequence for a shortcut, which is also a valid terminal key sequence, * is pressed while the terminal has focus. By responding to this signal, the * controlling application can choose whether to execute the action associated with * the shortcut or ignore the shortcut and send the key * sequence to the terminal application. * * In the embedded terminal, shortcuts are overridden and sent to the terminal by default. * Set @p override to false to prevent this happening and allow the shortcut to be triggered * normally. * * overrideShortcut() is not called for shortcuts which are not valid terminal key sequences, * eg. shortcuts with two or more modifiers. * * @param event Describes the keys that were pressed. * @param override Set this to false to prevent the terminal display from overriding the shortcut */ void overrideShortcut(QKeyEvent *event, bool &override); /** * Emitted when silence has been detected in the active session. Monitoring * for silence has to be enabled first using setMonitorSilenceEnabled(). */ void silenceDetected(); /** * Emitted when activity has been detected in the active session. Monitoring * for activity has to be enabled first using setMonitorActivityEnabled(). */ void activityDetected(); /** * Emitted when the current working directory of the active session has changed. */ void currentDirectoryChanged(const QString &dir); protected: /** Reimplemented from KParts::PartBase. */ bool openFile() override; bool openUrl(const QUrl &url) override; private Q_SLOTS: void activeViewChanged(SessionController *controller); void activeViewTitleChanged(ViewProperties *properties); void terminalExited(); void newTab(); void overrideTerminalShortcut(QKeyEvent *, bool &override); - void sessionStateChanged(int state); + void notificationChanged(Session::Notification notification, bool enabled); private: Session *activeSession() const; private: ViewManager *_viewManager; SessionController *_pluggedController; }; } #endif // PART_H diff --git a/src/Session.cpp b/src/Session.cpp index 5192e94e..437084de 100644 --- a/src/Session.cpp +++ b/src/Session.cpp @@ -1,1799 +1,1813 @@ /* 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 #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" #include "SessionManager.h" #include "ProfileManager.h" #include "Profile.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) , _isPrimaryScreen(true) { _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::sessionAttributeChanged, this, &Konsole::Session::setSessionAttribute); - connect(_emulation, &Konsole::Emulation::stateSet, this, &Konsole::Session::activityStateSet); + connect(_emulation, &Konsole::Emulation::bell, this, [this]() { + emit bellRequest(i18n("Bell in session '%1'", _nameTitle)); + this->setPendingNotification(Notification::Bell); + }); 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::changeTabTextColor); 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); } 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, QOverload::of(&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 { /** * compute the windows id to use * doesn't call winId on some widget, as this might lead * to rendering artifacts as this will trigger the * creation of a native window, see https://doc.qt.io/qt-5/qwidget.html#winId * instead, use https://doc.qt.io/qt-5/qwidget.html#effectiveWinId */ QWidget* widget = _views.first(); Q_ASSERT(widget); return widget->effectiveWinId(); } } 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::compositeFocusChanged, _emulation, &Konsole::Emulation::focusChanged); connect(_emulation, &Konsole::Emulation::setCursorStyleRequest, widget, &Konsole::TerminalDisplay::setCursorStyle); connect(_emulation, &Konsole::Emulation::resetCursorStyleRequest, widget, &Konsole::TerminalDisplay::resetCursorStyle); + + connect(widget, &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::Session::resetNotifications); } void Session::viewDestroyed(QObject* view) { auto* 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 = KIO::DesktopExecParser::executablePath(exec); 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::setSessionAttribute(int what, const QString& caption) { // 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; } } /* 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 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); + setPendingNotification(Notification::Silence, false); return; } bool hasFocus = false; for (const TerminalDisplay *display : qAsConst(_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); + setPendingNotification(Notification::Silence); } void Session::activityTimerDone() { _notifiedActivity = false; } +void Session::resetNotifications() +{ + static const Notification availableNotifications[] = {Activity, Silence, Bell}; + for (auto notification: availableNotifications) { + setPendingNotification(notification, false); + } +} + void Session::updateFlowControlState(bool suspended) { if (suspended) { if (flowControlEnabled()) { for (TerminalDisplay *display : qAsConst(_views)) { if (display->flowControlWarningEnabled()) { display->outputSuspended(true); } } } } else { for (TerminalDisplay *display : qAsConst(_views)) { display->outputSuspended(false); } } } void Session::changeTabTextColor(int i) { qCDebug(KonsoleDebug) << "Changing tab text color is not implemented "<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 for (TerminalDisplay *view : qAsConst(_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::reportColor(SessionAttributes r, const QColor& c) { #define to65k(a) (QStringLiteral("%1").arg(int(((a)*0xFFFF)), 4, 16, QLatin1Char('0'))) QString msg = QStringLiteral("\033]%1;rgb:").arg(r) + to65k(c.redF()) + QLatin1Char('/') + to65k(c.greenF()) + QLatin1Char('/') + to65k(c.blueF()) + QLatin1Char('\a'); _emulation->sendString(msg.toUtf8()); #undef to65k } void Session::reportForegroundColor(const QColor& c) { reportColor(SessionAttributes::TextColor, c); } void Session::reportBackgroundColor(const QColor& c) { reportColor(SessionAttributes::BackgroundColor, c); } 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; } if (eol.isNull()) { _emulation->sendText(text); } else { _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, QOverload::of(&Konsole::Pty::finished), this, &Konsole::Session::done); if (!_autoClose) { _userTitle = i18nc("@info:shell This session is done", "Finished"); 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 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()); _sessionProcessInfo->setUserHomeDir(); } _sessionProcessInfo->update(); } bool Session::updateForegroundProcessInfo() { Q_ASSERT(_shellProcess); const int foregroundPid = _shellProcess->foregroundProcessGroup(); if (foregroundPid != _foregroundPid) { delete _foregroundProcessInfo; _foregroundProcessInfo = ProcessInfo::newInstance(foregroundPid); _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: *
    *
  • %B - User's Bourne prompt sigil ($, or # for superuser).
  • *
  • %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 int UID = process->userId(&ok); if(!ok) { title.replace(QLatin1String("%B"), QStringLiteral("-")); } else { //title.replace(QLatin1String("%I"), QString::number(UID)); if (UID == 0) { title.replace(QLatin1String("%B"), QStringLiteral("#")); } else { title.replace(QLatin1String("%B"), QStringLiteral("$")); } } title.replace(QLatin1String("%u"), process->userName()); title.replace(QLatin1String("%h"), Konsole::ProcessInfo::localHost()); title.replace(QLatin1String("%n"), process->name(&ok)); QString dir = _reportedWorkingUrl.toLocalFile(); ok = true; if (dir.isEmpty()) { // update current directory from process updateWorkingDirectory(); // Previous process may have been freed in updateSessionProcessInfo() process = getProcessInfo(); dir = process->currentDir(&ok); } if(!ok) { title.replace(QLatin1String("%d"), QStringLiteral("-")); title.replace(QLatin1String("%D"), QStringLiteral("-")); } else { // allow for shortname to have the ~ as homeDir const QString homeDir = process->userHomeDir(); if (!homeDir.isEmpty()) { if (dir.startsWith(homeDir)) { dir.remove(0, homeDir.length()); dir.prepend(QLatin1Char('~')); } } title.replace(QLatin1String("%D"), dir); 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 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); + setPendingNotification(Notification::Activity, false); } void Session::setMonitorSilence(bool monitor) { if (_monitorSilence == monitor) { return; } _monitorSilence = monitor; if (_monitorSilence) { _silenceTimer->start(_silenceSeconds * 1000); } else { _silenceTimer->stop(); } - activityStateSet(NOTIFYNORMAL); + setPendingNotification(Notification::Silence, false); } 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, QOverload::of(&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) { + handleActivity(); _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; } } QString Session::profile() { return SessionManager::instance()->sessionProfile(this)->name(); } void Session::setProfile(const QString &profileName) { const QList profiles = ProfileManager::instance()->allProfiles(); for (const Profile::Ptr &profile : profiles) { if (profile->name() == profileName) { SessionManager::instance()->setSessionProfile(this, profile); } } } 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; } +void Session::setPendingNotification(Session::Notification notification, bool enable) +{ + if(enable != _activeNotifications.testFlag(notification)) { + _activeNotifications.setFlag(notification, enable); + emit notificationsChanged(notification, enable); + } +} + +void Session::handleActivity() +{ + // TODO: should this hardcoded interval be user configurable? + const int activityMaskInSeconds = 15; + + bool hasFocus = false; + // Don't notify if the terminal is active + const auto &displays = _views; + for (const TerminalDisplay *display: displays) { + 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 (_monitorActivity) { + setPendingNotification(Notification::Activity); + } +} + 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; QList SessionGroup::sessions() const { return _sessions.keys(); } 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() { auto* session = qobject_cast(sender()); Q_ASSERT(session); removeSession(session); } void SessionGroup::setMasterMode(int mode) { _masterMode = mode; } 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(); for (Session *other : sessionsKeys) { if (!_sessions[other]) { other->emulation()->sendString(data); } } _inForwardData = false; } diff --git a/src/Session.h b/src/Session.h index c4810387..816380d0 100644 --- a/src/Session.h +++ b/src/Session.h @@ -1,903 +1,912 @@ /* 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" #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() 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 setSessionAttribute(). * See the "Operating System Commands" section at: * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands */ 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 reportColor(SessionAttributes r, const QColor &c); void reportForegroundColor(const QColor &c); void reportBackgroundColor(const QColor &c); bool isReadOnly() const; void setReadOnly(bool readOnly); // Returns true if the current screen is the secondary/alternate one // or false if it's the primary/normal buffer bool isPrimaryScreen(); void tabTitleSetByUser(bool set); + enum Notification { + NoNotification = 0, + Activity = 1, + Silence = 2, + Bell = 4, + }; + Q_DECLARE_FLAGS(Notifications, Notification) + + /** Returns active notifications. */ + Notifications activeNotifications() const { return _activeNotifications; } + 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 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 session attribute being changed, it is one of the * SessionAttributes enum * @param caption The text part of the terminal command */ 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; /** * Sets the current session's profile */ Q_SCRIPTABLE void setProfile(const QString &profileName); /** * Returns the current session's profile name */ Q_SCRIPTABLE QString profile(); Q_SIGNALS: /** Emitted when the terminal process starts. */ void started(); /** * Emitted when the terminal process exits. */ void finished(); /** * 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); + /** Emitted when @p notification state changed to @p enabled */ + void notificationsChanged(Notification notification, bool enabled); /** * 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 foreground request ("\033]10;?\a") terminal code received. * Terminal is expected send "\033]10;rgb:RRRR/GGGG/BBBB\a" response. */ void getForegroundColor(); /** * 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(); private Q_SLOTS: void done(int, QProcess::ExitStatus); void fireZModemDownloadDetected(); void fireZModemUploadDetected(); void onReceiveBlock(const char *buf, int len); void silenceTimerDone(); void activityTimerDone(); + void resetNotifications(); 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); // Relays the signal from Emulation and sets _isPrimaryScreen void onPrimaryScreenInUse(bool use); void sessionAttributeRequest(int id); /** * Requests that the color the text for any tabs associated with * this session should be changed; * * TODO: Document what the parameter does */ void changeTabTextColor(int); 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; + void setPendingNotification(Notification notification, bool enable = true); + void handleActivity(); + + Notifications _activeNotifications; + 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; bool _isPrimaryScreen; }; /** * 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() 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); /** * 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); private Q_SLOTS: void sessionFinished(); void forwardData(const QByteArray &data); private: // maps sessions to their master status QHash _sessions; int _masterMode; }; } #endif diff --git a/src/SessionController.cpp b/src/SessionController.cpp index 437747cc..2cef1dde 100644 --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -1,1828 +1,1823 @@ /* 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 #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" #include "SaveHistoryTask.h" #include "SearchHistoryTask.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, _bellIcon, (QIcon::fromTheme(QLatin1String("preferences-desktop-notification-bell")))) 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) , _codecAction(nullptr) , _switchProfileMenu(nullptr) , _webSearchMenu(nullptr) , _listenForScreenWindowUpdates(false) , _preventClose(false) , _keepIconUntilInteraction(false) , _selectedText(QString()) , _showMenuAction(nullptr) , _bookmarkValidProgramsToClear(QStringList()) , _isSearchBarEnabled(false) , _editProfileDialog(nullptr) , _searchBar(view->searchBar()) { 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); const QList actionsList = actionCollection()->actions(); for (QAction *action : actionsList) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } setIdentifier(++_lastControllerId); sessionAttributeChanged(); connect(_view, &TerminalDisplay::compositeFocusChanged, this, &SessionController::viewFocusChangeHandler); _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); + connect(_session.data(), &Konsole::Session::notificationsChanged, this, &Konsole::SessionController::sessionNotificationsChanged); // listen to title and icon changes 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::tabTitleSetByUser); 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::compositeFocusChanged, this, [this](bool focused) { if (focused) { interactionHandler(); }}); 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 '10;?' request connect(_session.data(), &Konsole::Session::getForegroundColor, this, &Konsole::SessionController::sendForegroundColor); // xterm '11;?' request connect(_session.data(), &Konsole::Session::getBackgroundColor, this, &Konsole::SessionController::sendBackgroundColor); _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"); setupSearchBar(); _searchBar->setVisible(_isSearchBarEnabled); } SessionController::~SessionController() { _allControllers.remove(this); if (!_editProfileDialog.isNull()) { delete _editProfileDialog.data(); } if(factory()) { factory()->removeClient(this); } } void SessionController::trackOutput(QKeyEvent* event) { Q_ASSERT(_view->screenWindow()); // Qt has broken something, so we can't rely on just checking if certain // keys are passed as modifiers anymore. const int key = event->key(); const bool shouldNotTriggerScroll = key == Qt::Key_Super_L || key == Qt::Key_Super_R || key == Qt::Key_Hyper_L || key == Qt::Key_Hyper_R || key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Meta || key == Qt::Key_Alt || key == Qt::Key_AltGr || key == Qt::Key_CapsLock || key == Qt::Key_NumLock || key == Qt::Key_ScrollLock; // 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) || shouldNotTriggerScroll)) { return; } _view->screenWindow()->setTrackOutput(true); } void SessionController::viewFocusChangeHandler(bool focused) { if (focused) { // notify the world that the view associated with this session has been focused // used by the view manager to update the title of the MainWindow widget containing the view emit viewFocused(this); // when the view is focused, set bell events from the associated session to be delivered // by the focused view // first, disconnect any other views which are listening for bell signals from the session 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(); } } } 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; for (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() { auto * 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 auto signal = action->data().toInt(); _session->sendSignal(signal); } void SessionController::sendForegroundColor() { const QColor c = _view->getForegroundColor(); _session->reportForegroundColor(c); } void SessionController::sendBackgroundColor() { const QColor c = _view->getBackgroundColor(); _session->reportBackgroundColor(c); } void SessionController::toggleReadOnly() { auto *action = qobject_cast(sender()); if (action != nullptr) { bool readonly = !isReadOnly(); _session->setReadOnly(readonly); } } void SessionController::removeSearchFilter() { if (_searchFilter == nullptr) { return; } _view->filterChain()->removeFilter(_searchFilter); delete _searchFilter; _searchFilter = nullptr; } void SessionController::setupSearchBar() { 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); } 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())); action->setText(i18n("&Close Session")); 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); _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection); _findNextAction->setEnabled(false); _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection); _findPreviousAction->setEnabled(false); #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 // 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, QOverload::of(&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 auto* 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 auto* 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 auto* 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 auto* copyInputActions = collection->add(QStringLiteral("copy-input-to")); copyInputActions->setText(i18n("Copy Input To")); copyInputActions->addAction(copyInputToAllTabsAction); copyInputActions->addAction(copyInputToSelectedTabsAction); copyInputActions->addAction(copyInputToNoneAction); connect(copyInputActions, QOverload::of(&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); action = collection->addAction(QStringLiteral("reset-font-size"), this, SLOT(resetFontSize())); action->setText(i18n("Reset Font Size")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_0); // Send signal auto* sendSignalActions = collection->add(QStringLiteral("send-signal")); sendSignalActions->setText(i18n("Send Signal")); connect(sendSignalActions, QOverload::of(&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); } void SessionController::switchProfile(const 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 for (SessionController *controller : qAsConst(_allControllers)) { if ( (controller->profileDialogPointer() != nullptr) && controller->profileDialogPointer()->isVisible() && (controller->profileDialogPointer()->lookupProfile() == SessionManager::instance()->sessionProfile(_session)) ) { controller->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(); } } } // This is called upon Menu->Close Sesssion and right-click on tab->Close Tab 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"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("CloseSingleTab")); 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 auto* 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 ... const QList views = session->views(); for (const TerminalDisplay *terminalDisplay : 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 auto mode = action->data().toInt(); 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); const QSet completeGroup = newGroup | currentGroup; for (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(), QOverload<>::of(&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 < 0 || _searchStartLine > _view->screenWindow()->lineCount()) { 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(), QOverload<>::of(&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 _view->repaint(); } 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::resetFontSize() { _view->resetFontSize(); } void SessionController::monitorActivity(bool monitor) { _session->setMonitorActivity(monitor); } void SessionController::monitorSilence(bool monitor) { _session->setMonitorSilence(monitor); } void SessionController::updateSessionIcon() { // If the default profile icon is being used, don't put it on the tab // Only show the icon if the user specifically chose one if (_session->iconName() == QStringLiteral("utilities-terminal")) { _sessionIconName = QString(); } else { _sessionIconName = _session->iconName(); } _sessionIcon = QIcon::fromTheme(_sessionIconName); // 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::sessionAttributeChanged() { if (_sessionIconName != _session->iconName()) { 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 sessionAttributeChanged(); updateReadOnlyActionStates(); // Update all views const QList viewsList = session()->views(); for (TerminalDisplay *terminalDisplay : viewsList) { if (terminalDisplay != _view.data()) { terminalDisplay->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(); auto contentSeparator = new QAction(popup); contentSeparator->setSeparator(true); // prepend content-specific actions such as "Open Link", "Copy Email Address" etc. QSharedPointer hotSpot = _view->filterActions(position); if (hotSpot) { popup->insertActions(popup->actions().value(0, nullptr), hotSpot->actions() << contentSeparator ); } // 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); } } // they are here. // qDebug() << popup->actions().indexOf(contentActions[0]) << popup->actions().indexOf(contentActions[1]) << popup->actions()[3]; QAction* chosen = popup->exec(QCursor::pos()); // check for validity of the pointer to the popup menu if (!popup.isNull()) { 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) +void SessionController::sessionNotificationsChanged(Session::Notification notification, bool enabled) { - if (state == _previousState) { - return; - } - - if (state == NOTIFYACTIVITY) { + if (notification == Session::Notification::Activity && enabled) { setIcon(*_activityIcon); _keepIconUntilInteraction = true; - } else if (state == NOTIFYSILENCE) { + } else if (notification == Session::Notification::Silence && enabled) { setIcon(*_silenceIcon); _keepIconUntilInteraction = true; - } else if (state == NOTIFYBELL) { + } else if (notification == Session::Notification::Bell && enabled) { setIcon(*_bellIcon); _keepIconUntilInteraction = true; - } else if (state == NOTIFYNORMAL) { + } else { updateSessionIcon(); } - - _previousState = state; + emit notificationChanged(this, notification, enabled); } 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(); } void SessionController::zmodemUpload() { if (_session->isZModemBusy()) { KMessageBox::sorry(_view, i18n("

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

")); return; } 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")); } QString SessionController::userTitle() const { if (!_session.isNull()) { return _session->userTitle(); } else { return QString(); } } diff --git a/src/SessionController.h b/src/SessionController.h index 9dd83a75..a266e144 100644 --- a/src/SessionController.h +++ b/src/SessionController.h @@ -1,365 +1,366 @@ /* 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 // KDE #include // Konsole #include "ViewProperties.h" #include "Profile.h" #include "Enumeration.h" +#include "Session.h" class QAction; class QTextCodec; class QKeyEvent; class QTimer; class QUrl; class KCodecAction; 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; using SessionPtr = QPointer; /** * 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() 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 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 override; QString currentDir() const override; void rename() override; bool confirmClose() const override; virtual bool confirmForceClose() const; /** 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 viewFocused(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(); /** Reset font size */ void resetFontSize(); /** Close the incremental search */ void searchClosed(); // called when the user clicks on the 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(const Profile::Ptr &profile); void handleWebShortcutAction(); void configureWebShortcuts(); void sendSignal(QAction *action); void sendForegroundColor(); void sendBackgroundColor(); void toggleReadOnly(); // other void setupSearchBar(); void prepareSwitchProfileMenu(); void updateCodecAction(); void showDisplayContextMenu(const QPoint &position); void movementKeyFromSearchBarReceived(QKeyEvent *event); - void sessionStateChanged(int state); + void sessionNotificationsChanged(Session::Notification notification, bool enabled); void sessionAttributeChanged(); void sessionReadOnlyChanged(); void searchTextChanged(const QString &text); void searchCompleted(bool success); void updateFilterList(Profile::Ptr profile); // Called when the profile has changed, so we might need to change the list of filters void viewFocusChangeHandler(bool focused); 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; 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; QPointer _searchBar; }; inline bool SessionController::isValid() const { return !_session.isNull() && !_view.isNull(); } } #endif //SESSIONCONTROLLER_H diff --git a/src/ViewProperties.h b/src/ViewProperties.h index 22b0fafd..eaf7bf3b 100644 --- a/src/ViewProperties.h +++ b/src/ViewProperties.h @@ -1,128 +1,131 @@ /* Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef VIEWPROPERTIES_H #define VIEWPROPERTIES_H // Qt #include #include #include #include // Konsole #include "konsoleprivate_export.h" +#include "Session.h" namespace Konsole { /** * Encapsulates user-visible information about the terminal session currently being displayed in a view, * such as the associated title and icon. * * This can be used by navigation widgets in a ViewContainer sub-class to provide a tab, label or other * item for switching between views. */ class KONSOLEPRIVATE_EXPORT ViewProperties : public QObject { Q_OBJECT public: explicit ViewProperties(QObject *parent); ~ViewProperties() override; /** Returns the icon associated with a view */ QIcon icon() const; /** Returns the title associated with a view */ QString title() const; /** * Returns the URL current associated with a view. * The default implementation returns an empty URL. */ virtual QUrl url() const; /** * Returns the current directory associated with a view. * This may be the same as url() * The default implementation returns an empty string. */ virtual QString currentDir() const; /** * A unique identifier associated with this * ViewProperties instance. */ int identifier() const; /** * Sub-classes may re-implement this method to display a message to the user * to allow them to confirm whether to close a view. * The default implementation always returns true */ virtual bool confirmClose() const { return true; } /** Finds a ViewProperties instance given its numeric identifier. */ static ViewProperties *propertiesById(int id); Q_SIGNALS: /** Emitted when the icon for a view changes */ void iconChanged(ViewProperties *properties); /** Emitted when the title for a view changes */ void titleChanged(ViewProperties *properties); /** Emitted when activity has occurred in this view. */ void activity(ViewProperties *item); + /** Emitted when notification for a view changes */ + void notificationChanged(ViewProperties *item, Session::Notification notification, bool enabled); public Q_SLOTS: /** * Requests the renaming of this view. * The default implementation does nothing. */ virtual void rename(); protected Q_SLOTS: /** Emits the activity() signal. */ void fireActivity(); protected: /** * Subclasses may call this method to change the title. This causes * a titleChanged() signal to be emitted */ void setTitle(const QString &title); /** * Subclasses may call this method to change the icon. This causes * an iconChanged() signal to be emitted */ void setIcon(const QIcon &icon); /** Subclasses may call this method to change the identifier. */ void setIdentifier(int id); private: Q_DISABLE_COPY(ViewProperties) QIcon _icon; QString _title; int _identifier; static QHash _viewProperties; }; } #endif //VIEWPROPERTIES_H diff --git a/src/Vt102Emulation.cpp b/src/Vt102Emulation.cpp index e8ddd91f..18b5b352 100644 --- a/src/Vt102Emulation.cpp +++ b/src/Vt102Emulation.cpp @@ -1,1530 +1,1530 @@ /* 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()), _pendingSessionAttributesUpdates(QHash()), _sessionAttributesUpdateTimer(new QTimer(this)), _reportFocusEvents(false) { _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(uint cc) { tokenBufferPos = qMin(tokenBufferPos, MAX_TOKEN_LENGTH - 1); tokenBuffer[tokenBufferPos] = cc; tokenBufferPos++; } // 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 osc (tokenBufferPos >= 2 && tokenBuffer[1] == ']') #define ces(C) (cc < 256 && (charClass[cc] & (C)) == (C)) #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 osc (aka OSC) "ESC]" // escape sequences; this matches what XTERM docs say // Allow BEL and ESC here, it will either end the text or be removed later. if (osc && cc != 0x1b && cc != 0x07) { return; } if (!osc) { // 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); uint* 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; } // Operating System Command if (p > 2 && s[1] == ']') { // ']' ... '\' if (s[p-2] == ESC && s[p-1] == '\\') { processSessionAttributeRequest(p-1); resetTokenizer(); return; } // ']' ... + one character for reprocessing if (s[p-2] == ESC) { processSessionAttributeRequest(p-1); resetTokenizer(); receiveChar(cc); return; } // ']' ... if (s[p-1] == 0x07) { processSessionAttributeRequest(p); resetTokenizer(); return; } } // ']' ... if (osc ) { 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::processSessionAttributeRequest(int tokenSize) { // Describes the window or terminal session attribute to change // See Session::SessionAttributes for possible values int attribute = 0; int i; // ignore last character (ESC or BEL) --tokenSize; // skip first two characters (ESC, ']') for (i = 2; i < tokenSize && tokenBuffer[i] >= '0' && tokenBuffer[i] <= '9'; i++) { attribute = 10 * attribute + (tokenBuffer[i]-'0'); } if (tokenBuffer[i] != ';') { reportDecodingError(); return; } // skip ';' ++i; const QString value = QString::fromUcs4(&tokenBuffer[i], tokenSize - i); if (value == QLatin1String("?")) { emit sessionAttributeRequest(attribute); return; } _pendingSessionAttributesUpdates[attribute] = value; _sessionAttributesUpdateTimer->start(20); } void Vt102Emulation::updateSessionAttributes() { QListIterator iter(_pendingSessionAttributesUpdates.keys()); while (iter.hasNext()) { int arg = iter.next(); emit sessionAttributeChanged(arg , _pendingSessionAttributesUpdates[arg]); } _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); + case token_ctl('G' ) : emit bell(); 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' // Using 0 to reset to default is matching VTE, but not any official standard. case token_csi_sp ('q' ) : emit setCursorStyleRequest(Enum::BlockCursor, true); break; case token_csi_psp('q', 0) : emit resetCursorStyleRequest(); 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 https://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 change event can be used by Vim (or other terminal applications) * to recognize that the konsole window has changed 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/FocusGained autocmd: * https://github.com/sjl/vitality.vim */ void Vt102Emulation::focusChanged(bool focused) { if (_reportFocusEvents) { sendString(focused ? "\033[I" : "\033[O"); } } 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) // MODE_Mouse1007 (Alternate Scrolling) is not reset here, to maintain // the profile alternate scrolling property after reset() is called, which // makes more sense; also this matches XTerm behavior. 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_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, Qt::NoModifier, KeyboardTranslator::NoState); if (entry.text().count() > 0) { return entry.text().at(0); } else { return '\b'; } } // return contents of the scan buffer static QString hexdump2(uint *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; } QString outputError = QStringLiteral("Undecodable sequence: "); outputError.append(hexdump2(tokenBuffer, tokenBufferPos)); }