diff --git a/src/Application.cpp b/src/Application.cpp index e02f7a16..6cd10b8f 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -1,604 +1,605 @@ /* Copyright 2006-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 "Application.h" // Qt +#include #include #include #include #include #include // KDE #include #include #include // Konsole #include "SessionManager.h" #include "ProfileManager.h" #include "MainWindow.h" #include "Session.h" #include "ShellCommand.h" #include "KonsoleSettings.h" #include "ViewManager.h" #include "SessionController.h" #include "WindowSystemInfo.h" using namespace Konsole; Application::Application(QSharedPointer parser, const QStringList &customCommand) : _backgroundInstance(nullptr), m_parser(parser), m_customCommand(customCommand) { } void Application::populateCommandLineParser(QCommandLineParser *parser) { auto options = QVector { { { QStringLiteral("profile") }, i18nc("@info:shell", "Name of profile to use for new Konsole instance"), QStringLiteral("name") }, { { QStringLiteral("fallback-profile") }, i18nc("@info:shell", "Use the internal FALLBACK profile") }, { { QStringLiteral("workdir") }, i18nc("@info:shell", "Set the initial working directory of the new tab or window to 'dir'"), QStringLiteral("dir") }, { { QStringLiteral("hold"), QStringLiteral("noclose") }, i18nc("@info:shell", "Do not close the initial session automatically when it ends.") }, { {QStringLiteral("new-tab") }, i18nc("@info:shell", "Create a new tab in an existing window rather than creating a new window") }, { { QStringLiteral("tabs-from-file") }, i18nc("@info:shell","Create tabs as specified in given tabs configuration"" file"), QStringLiteral("file") }, { { QStringLiteral("background-mode") }, i18nc("@info:shell", "Start Konsole in the background and bring to the front when Ctrl+Shift+F12 (by default) is pressed") }, { { QStringLiteral("separate"), QStringLiteral("nofork") }, i18nc("@info:shell", "Run in a separate process") }, { { QStringLiteral("show-menubar") }, i18nc("@info:shell", "Show the menubar, overriding the default setting") }, { { QStringLiteral("hide-menubar") }, i18nc("@info:shell", "Hide the menubar, overriding the default setting") }, { { QStringLiteral("show-tabbar") }, i18nc("@info:shell", "Show the tabbar, overriding the default setting") }, { { QStringLiteral("hide-tabbar") }, i18nc("@info:shell", "Hide the tabbar, overriding the default setting") }, { { QStringLiteral("fullscreen") }, i18nc("@info:shell", "Start Konsole in fullscreen mode") }, { { QStringLiteral("notransparency") }, i18nc("@info:shell", "Disable transparent backgrounds, even if the system supports them.") }, { { QStringLiteral("list-profiles") }, i18nc("@info:shell", "List the available profiles") }, { { QStringLiteral("list-profile-properties") }, i18nc("@info:shell", "List all the profile properties names and their type (for use with -p)") }, { { QStringLiteral("p") }, i18nc("@info:shell", "Change the value of a profile property."), QStringLiteral("property=value") }, { { QStringLiteral("e") }, i18nc("@info:shell", "Command to execute. This option will catch all following arguments, so use it as the last option."), QStringLiteral("cmd") } }; foreach(const auto& option, options) { parser->addOption(option); } parser->addPositionalArgument(QStringLiteral("[args]"), i18nc("@info:shell", "Arguments passed to command")); // Add a no-op compatibility option to make Konsole compatible with // Debian's policy on X terminal emulators. // -T is technically meant to set a title, that is not really meaningful // for Konsole as we have multiple user-facing options controlling // the title and overriding whatever is set elsewhere. // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=532029 // https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s11.8.3 auto titleOption = QCommandLineOption({ QStringLiteral("T") }, QStringLiteral("Debian policy compatibility, not used"), QStringLiteral("value")); titleOption.setHidden(true); parser->addOption(titleOption); } QStringList Application::getCustomCommand(QStringList &args) { int i = args.indexOf(QStringLiteral("-e")); QStringList customCommand; if ((0 < i) && (i < (args.size() - 1))) { // -e was specified with at least one extra argument // if -e was specified without arguments, QCommandLineParser will deal // with that args.removeAt(i); while (args.size() > i) { customCommand << args.takeAt(i); } } return customCommand; } Application::~Application() { SessionManager::instance()->closeAllSessions(); ProfileManager::instance()->saveSettings(); } MainWindow *Application::newMainWindow() { WindowSystemInfo::HAVE_TRANSPARENCY = !m_parser->isSet(QStringLiteral("notransparency")); auto window = new MainWindow(); connect(window, &Konsole::MainWindow::newWindowRequest, this, &Konsole::Application::createWindow); connect(window, &Konsole::MainWindow::viewDetached, this, &Konsole::Application::detachView); return window; } void Application::createWindow(Profile::Ptr profile, const QString &directory) { MainWindow *window = newMainWindow(); ViewManager *viewManager = window->viewManager(); window->createSession(viewManager->activeContainer(), profile, directory); finalizeNewMainWindow(window); } void Application::detachView(Session *session) { MainWindow *currentWindow = qobject_cast(sender()); MainWindow *window = newMainWindow(); ViewManager *manager = window->viewManager(); manager->createView(manager->activeContainer(), session); // Since user is dragging and dropping, move dnd window to where // the user has the cursor (correct multiple monitor setups). window->move(QCursor::pos()); window->resize(currentWindow->geometry().width(), currentWindow->geometry().height()); window->show(); } int Application::newInstance() { // handle session management // returns from processWindowArgs(args, createdNewMainWindow) // if a new window was created bool createdNewMainWindow = false; // check for arguments to print help or other information to the // terminal, quit if such an argument was found if (processHelpArgs()) { return 0; } // create a new window or use an existing one MainWindow *window = processWindowArgs(createdNewMainWindow); if (m_parser->isSet(QStringLiteral("tabs-from-file"))) { // create new session(s) as described in file if (!processTabsFromFileArgs(window)) { return 0; } } // select profile to use Profile::Ptr baseProfile = processProfileSelectArgs(); // process various command-line options which cause a property of the // selected profile to be changed Profile::Ptr newProfile = processProfileChangeArgs(baseProfile); // create new session ViewManager *viewManager = window->viewManager(); Session *session = window->createSession(viewManager->activeContainer(), newProfile, QString()); if (m_parser->isSet(QStringLiteral("noclose"))) { session->setAutoClose(false); } // if the background-mode argument is supplied, start the background // session ( or bring to the front if it already exists ) if (m_parser->isSet(QStringLiteral("background-mode"))) { startBackgroundMode(window); } else { // Qt constrains top-level windows which have not been manually // resized (via QWidget::resize()) to a maximum of 2/3rds of the // screen size. // // This means that the terminal display might not get the width/ // height it asks for. To work around this, the widget must be // manually resized to its sizeHint(). // // This problem only affects the first time the application is run. // run. After that KMainWindow will have manually resized the // window to its saved size at this point (so the Qt::WA_Resized // attribute will be set) // If not restoring size from last time or only adding new tab, // resize window to chosen profile size (see Bug:345403) if (createdNewMainWindow) { finalizeNewMainWindow(window); } else { window->show(); } } return 1; } /* Documentation for tab file: * * ;; is the token separator * # at the beginning of line results in line being ignored. * supported tokens: title, command, profile and workdir * * Note that the title is static and the tab will close when the * command is complete (do not use --noclose). You can start new tabs. * * Example below will create 6 tabs as listed and a 7th default tab title: This is the title;; command: ssh localhost title: This is the title;; command: ssh localhost;; profile: Shell title: Top this!;; command: top title: mc this!;; command: mc;; workdir: /tmp #this line is comment command: ssh localhost profile: Shell */ bool Application::processTabsFromFileArgs(MainWindow *window) { // Open tab configuration file const QString tabsFileName(m_parser->value(QStringLiteral("tabs-from-file"))); QFile tabsFile(tabsFileName); if (!tabsFile.open(QFile::ReadOnly)) { qWarning() << "ERROR: Cannot open tabs file " << tabsFileName.toLocal8Bit().data(); return false; } unsigned int sessions = 0; while (!tabsFile.atEnd()) { QString lineString(QString::fromUtf8(tabsFile.readLine()).trimmed()); if ((lineString.isEmpty()) || (lineString[0] == QLatin1Char('#'))) { continue; } QHash lineTokens; QStringList lineParts = lineString.split(QStringLiteral(";;"), QString::SkipEmptyParts); for (int i = 0; i < lineParts.size(); ++i) { QString key = lineParts.at(i).section(QLatin1Char(':'), 0, 0).trimmed().toLower(); QString value = lineParts.at(i).section(QLatin1Char(':'), 1, -1).trimmed(); lineTokens[key] = value; } // should contain at least one of 'command' and 'profile' if (lineTokens.contains(QStringLiteral("command")) || lineTokens.contains(QStringLiteral("profile"))) { createTabFromArgs(window, lineTokens); sessions++; } else { qWarning() << "Each line should contain at least one of 'command' and 'profile'."; } } tabsFile.close(); if (sessions < 1) { qWarning() << "No valid lines found in " << tabsFileName.toLocal8Bit().data(); return false; } return true; } void Application::createTabFromArgs(MainWindow *window, const QHash &tokens) { const QString &title = tokens[QStringLiteral("title")]; const QString &command = tokens[QStringLiteral("command")]; const QString &profile = tokens[QStringLiteral("profile")]; const QString &workdir = tokens[QStringLiteral("workdir")]; Profile::Ptr baseProfile; if (!profile.isEmpty()) { baseProfile = ProfileManager::instance()->loadProfile(profile); } if (!baseProfile) { // fallback to default profile baseProfile = ProfileManager::instance()->defaultProfile(); } Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile)); newProfile->setHidden(true); // FIXME: the method of determining whether to use newProfile does not // scale well when we support more fields in the future bool shouldUseNewProfile = false; if (!command.isEmpty()) { newProfile->setProperty(Profile::Command, command); newProfile->setProperty(Profile::Arguments, command.split(QLatin1Char(' '))); shouldUseNewProfile = true; } if (!title.isEmpty()) { newProfile->setProperty(Profile::LocalTabTitleFormat, title); newProfile->setProperty(Profile::RemoteTabTitleFormat, title); shouldUseNewProfile = true; } if (m_parser->isSet(QStringLiteral("workdir"))) { newProfile->setProperty(Profile::Directory, m_parser->value(QStringLiteral("workdir"))); shouldUseNewProfile = true; } if (!workdir.isEmpty()) { newProfile->setProperty(Profile::Directory, workdir); shouldUseNewProfile = true; } // Create the new session Profile::Ptr theProfile = shouldUseNewProfile ? newProfile : baseProfile; ViewManager *viewManager = window->viewManager(); Session *session = window->createSession(viewManager->activeContainer(), theProfile, QString()); if (m_parser->isSet(QStringLiteral("noclose"))) { session->setAutoClose(false); } if (!window->testAttribute(Qt::WA_Resized)) { window->resize(window->sizeHint()); } // FIXME: this ugly hack here is to make the session start running, so that // its tab title is displayed as expected. // // This is another side effect of the commit fixing BKO 176902. window->show(); window->hide(); } // Creates a new Konsole window. // If --new-tab is given, use existing window. MainWindow *Application::processWindowArgs(bool &createdNewMainWindow) { MainWindow *window = nullptr; if (m_parser->isSet(QStringLiteral("new-tab"))) { QListIterator iter(QApplication::topLevelWidgets()); iter.toBack(); while (iter.hasPrevious()) { window = qobject_cast(iter.previous()); if (window != nullptr) { break; } } } if (window == nullptr) { createdNewMainWindow = true; window = newMainWindow(); // override default menubar visibility if (m_parser->isSet(QStringLiteral("show-menubar"))) { window->setMenuBarInitialVisibility(true); } if (m_parser->isSet(QStringLiteral("hide-menubar"))) { window->setMenuBarInitialVisibility(false); } if (m_parser->isSet(QStringLiteral("fullscreen"))) { window->viewFullScreen(true); } if (m_parser->isSet(QStringLiteral("show-tabbar"))) { window->viewManager()->setNavigationVisibility(ViewManager::AlwaysShowNavigation); } else if (m_parser->isSet(QStringLiteral("hide-tabbar"))) { window->viewManager()->setNavigationVisibility(ViewManager::AlwaysHideNavigation); } } return window; } // Loads a profile. // If --profile is given, loads profile . // If --fallback-profile is given, loads profile FALLBACK/. // Else loads the default profile. Profile::Ptr Application::processProfileSelectArgs() { Profile::Ptr defaultProfile = ProfileManager::instance()->defaultProfile(); if (m_parser->isSet(QStringLiteral("profile"))) { Profile::Ptr profile = ProfileManager::instance()->loadProfile( m_parser->value(QStringLiteral("profile"))); if (profile) { return profile; } } else if (m_parser->isSet(QStringLiteral("fallback-profile"))) { Profile::Ptr profile = ProfileManager::instance()->loadProfile(QStringLiteral("FALLBACK/")); if (profile) { return profile; } } return defaultProfile; } bool Application::processHelpArgs() { if (m_parser->isSet(QStringLiteral("list-profiles"))) { listAvailableProfiles(); return true; } else if (m_parser->isSet(QStringLiteral("list-profile-properties"))) { listProfilePropertyInfo(); return true; } return false; } void Application::listAvailableProfiles() { QStringList paths = ProfileManager::instance()->availableProfilePaths(); foreach (const QString &path, paths) { QFileInfo info(path); printf("%s\n", info.completeBaseName().toLocal8Bit().constData()); } } void Application::listProfilePropertyInfo() { Profile::Ptr tempProfile = ProfileManager::instance()->defaultProfile(); const QStringList names = tempProfile->propertiesInfoList(); foreach (const QString &name, names) { printf("%s\n", name.toLocal8Bit().constData()); } } Profile::Ptr Application::processProfileChangeArgs(Profile::Ptr baseProfile) { bool shouldUseNewProfile = false; Profile::Ptr newProfile = Profile::Ptr(new Profile(baseProfile)); newProfile->setHidden(true); // change the initial working directory if (m_parser->isSet(QStringLiteral("workdir"))) { newProfile->setProperty(Profile::Directory, m_parser->value(QStringLiteral("workdir"))); shouldUseNewProfile = true; } // temporary changes to profile options specified on the command line const QStringList profileProperties = m_parser->values(QStringLiteral("p")); foreach (const QString &value, profileProperties) { ProfileCommandParser parser; QHashIterator iter(parser.parse(value)); while (iter.hasNext()) { iter.next(); newProfile->setProperty(iter.key(), iter.value()); } shouldUseNewProfile = true; } // run a custom command if (!m_customCommand.isEmpty()) { // Example: konsole -e man ls QString commandExec = m_customCommand[0]; QStringList commandArguments(m_customCommand); if ((m_customCommand.size() == 1) && (QStandardPaths::findExecutable(commandExec).isEmpty())) { // Example: konsole -e "man ls" ShellCommand shellCommand(commandExec); commandExec = shellCommand.command(); commandArguments = shellCommand.arguments(); } if (commandExec.startsWith(QLatin1String("./"))) { commandExec = QDir::currentPath() + commandExec.mid(1); } newProfile->setProperty(Profile::Command, commandExec); newProfile->setProperty(Profile::Arguments, commandArguments); shouldUseNewProfile = true; } if (shouldUseNewProfile) { return newProfile; } else { return baseProfile; } } void Application::startBackgroundMode(MainWindow *window) { if (_backgroundInstance != nullptr) { return; } KActionCollection* collection = window->actionCollection(); QAction* action = collection->addAction(QStringLiteral("toggle-background-window")); action->setObjectName(QStringLiteral("Konsole Background Mode")); action->setText(i18nc("@item", "Toggle Background Window")); KGlobalAccel::self()->setGlobalShortcut(action, QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_F12)); connect(action, &QAction::triggered, this, &Application::toggleBackgroundInstance); _backgroundInstance = window; } void Application::toggleBackgroundInstance() { Q_ASSERT(_backgroundInstance); if (!_backgroundInstance->isVisible()) { _backgroundInstance->show(); // ensure that the active terminal display has the focus. Without // this, an odd problem occurred where the focus widget would change // each time the background instance was shown _backgroundInstance->setFocus(); } else { _backgroundInstance->hide(); } } void Application::slotActivateRequested(QStringList args, const QString & /*workingDir*/) { // QCommandLineParser expects the first argument to be the executable name // In the current version it just strips it away args.prepend(qApp->applicationFilePath()); m_customCommand = getCustomCommand(args); // We can't re-use QCommandLineParser instances, it preserves earlier parsed values auto parser = new QCommandLineParser; populateCommandLineParser(parser); parser->parse(args); m_parser.reset(parser); newInstance(); } void Application::finalizeNewMainWindow(MainWindow *window) { if (!KonsoleSettings::saveGeometryOnExit()) { window->resize(window->sizeHint()); } window->show(); } diff --git a/src/Application.h b/src/Application.h index bdfface6..ff97a19d 100644 --- a/src/Application.h +++ b/src/Application.h @@ -1,98 +1,97 @@ /* 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 APPLICATION_H #define APPLICATION_H // Qt -#include #include // Konsole #include "Profile.h" namespace Konsole { class MainWindow; class Session; /** * The Konsole Application. * * The application consists of one or more main windows and a set of * factories to create new sessions and views. * * To create a new main window with a default terminal session, call * the newInstance(). Empty main windows can be created using newMainWindow(). * * The factory used to create new terminal sessions can be retrieved using * the sessionManager() accessor. */ class Application : public QObject { Q_OBJECT public: /** Constructs a new Konsole application. */ explicit Application(QSharedPointer parser, const QStringList &customCommand); static void populateCommandLineParser(QCommandLineParser *parser); static QStringList getCustomCommand(QStringList &args); ~Application() Q_DECL_OVERRIDE; /** Creates a new main window and opens a default terminal session */ int newInstance(); /** * Creates a new, empty main window and connects to its newSessionRequest() * and newWindowRequest() signals to trigger creation of new sessions or * windows when then they are emitted. */ MainWindow *newMainWindow(); private Q_SLOTS: void createWindow(Profile::Ptr profile, const QString &directory); void detachView(Session *session); void toggleBackgroundInstance(); public Q_SLOTS: void slotActivateRequested(QStringList args, const QString &workingDir); private: Q_DISABLE_COPY(Application) void listAvailableProfiles(); void listProfilePropertyInfo(); void startBackgroundMode(MainWindow *window); bool processHelpArgs(); MainWindow *processWindowArgs(bool &createdNewMainWindow); Profile::Ptr processProfileSelectArgs(); Profile::Ptr processProfileChangeArgs(Profile::Ptr baseProfile); bool processTabsFromFileArgs(MainWindow *window); void createTabFromArgs(MainWindow *window, const QHash &); void finalizeNewMainWindow(MainWindow *window); MainWindow *_backgroundInstance; QSharedPointer m_parser; QStringList m_customCommand; }; } #endif // APPLICATION_H diff --git a/src/EditProfileDialog.h b/src/EditProfileDialog.h index 0ec1649e..25f35daf 100644 --- a/src/EditProfileDialog.h +++ b/src/EditProfileDialog.h @@ -1,476 +1,475 @@ /* Copyright 2007-2008 by Robert Knight Copyright 2018 by Harald Sitter 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 EDITPROFILEDIALOG_H #define EDITPROFILEDIALOG_H // Qt #include #include #include -#include #include #include #include #include #include // KDE #include #include // Konsole #include "Profile.h" #include "Enumeration.h" #include "ColorScheme.h" #include "ColorSchemeEditor.h" #include "KeyboardTranslatorManager.h" #include "FontDialog.h" class KPluralHandlingSpinBox; class KLocalizedString; class QItemSelectionModel; namespace Ui { class EditProfileGeneralPage; class EditProfileTabsPage; class EditProfileAppearancePage; class EditProfileScrollingPage; class EditProfileKeyboardPage; class EditProfileMousePage; class EditProfileAdvancedPage; } namespace Konsole { /** * A dialog which allows the user to edit a profile. * After the dialog is created, it can be initialized with the settings * for a profile using setProfile(). When the user makes changes to the * dialog and accepts the changes, the dialog will update the * profile in the SessionManager by calling the SessionManager's * changeProfile() method. * * Some changes made in the dialog are preview-only changes which cause * the SessionManager's changeProfile() method to be called with * the persistent argument set to false. These changes are then * un-done when the dialog is closed. */ class KONSOLEPRIVATE_EXPORT EditProfileDialog: public KPageDialog { Q_OBJECT public: /** Constructs a new dialog with the specified parent. */ explicit EditProfileDialog(QWidget *parent = nullptr); ~EditProfileDialog() Q_DECL_OVERRIDE; /** * Initializes the dialog with the settings for the specified session * type. * * When the dialog closes, the profile will be updated in the SessionManager * with the altered settings. * * @param profile The profile to be edited */ void setProfile(Profile::Ptr profile); /** * Selects the text in the profile name edit area. * When the dialog is being used to create a new profile, * this can be used to draw the user's attention to the profile name * and make it easy for them to change it. */ void selectProfileName(); const Profile::Ptr lookupProfile() const; public Q_SLOTS: // reimplemented void accept() Q_DECL_OVERRIDE; // reimplemented void reject() Q_DECL_OVERRIDE; void apply(); protected: bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: QSize sizeHint() const override; // sets up the specified tab page if necessary void preparePage(KPageWidgetItem *current, KPageWidgetItem *before = nullptr); // saves changes to profile void save(); // general page void selectInitialDir(); void selectIcon(); void profileNameChanged(const QString &name); void initialDirChanged(const QString &dir); void startInSameDir(bool); void commandChanged(const QString &command); void tabTitleFormatChanged(const QString &format); void remoteTabTitleFormatChanged(const QString &format); void terminalColumnsEntryChanged(int); void terminalRowsEntryChanged(int); void showTerminalSizeHint(bool); void setDimWhenInactive(bool); void showEnvironmentEditor(); void silenceSecondsChanged(int); // appearance page void setAntialiasText(bool enable); void setBoldIntense(bool enable); void useFontLineCharacters(bool enable); void newColorScheme(); void editColorScheme(); void saveColorScheme(const ColorScheme &scheme, bool isNewScheme); void removeColorScheme(); void gotNewColorSchemes(const KNS3::Entry::List &changedEntries); void toggleBlinkingCursor(bool); void setCursorShape(int); void autoCursorColor(); void customCursorColor(); void customCursorColorChanged(const QColor &); void terminalMarginChanged(int margin); void lineSpacingChanged(int); void setTerminalCenter(bool enable); /** * Deletes the selected colorscheme from the user's home dir location * so that the original one from the system-wide location can be used * instead */ void resetColorScheme(); void colorSchemeSelected(); void previewColorScheme(const QModelIndex &index); void showFontDialog(); void toggleMouseWheelZoom(bool enable); // scrolling page void historyModeChanged(Enum::HistoryModeEnum mode); void historySizeChanged(int); void scrollFullPage(); void scrollHalfPage(); // keyboard page void editKeyBinding(); void newKeyBinding(); void keyBindingSelected(); void removeKeyBinding(); void resetKeyBindings(); // mouse page void toggleUnderlineFiles(bool enable); void toggleUnderlineLinks(bool); void toggleOpenLinksByDirectClick(bool); void toggleCtrlRequiredForDrag(bool); void toggleDropUrlsAsText(bool); void toggleCopyTextToClipboard(bool); void toggleCopyTextAsHTML(bool); void toggleTrimLeadingSpacesInSelectedText(bool); void toggleTrimTrailingSpacesInSelectedText(bool); void pasteFromX11Selection(); void pasteFromClipboard(); void toggleAlternateScrolling(bool enable); void TripleClickModeChanged(int); void wordCharactersChanged(const QString &); // advanced page void toggleBlinkingText(bool); void toggleFlowControl(bool); void togglebidiRendering(bool); void updateUrlHintsModifier(bool); void toggleReverseUrlHints(bool); void setDefaultCodec(QTextCodec *); // apply the first previewed changes stored up by delayedPreview() void delayedPreviewActivate(); private: Q_DISABLE_COPY(EditProfileDialog) enum PageID { GeneralPage = 0, TabsPage, AppearancePage, ScrollingPage, KeyboardPage, MousePage, AdvancedPage, PagesCount, }; // initialize various pages of the dialog void setupGeneralPage(const Profile::Ptr &profile); void setupTabsPage(const Profile::Ptr &profile); void setupAppearancePage(const Profile::Ptr &profile); void setupKeyboardPage(const Profile::Ptr &profile); void setupScrollingPage(const Profile::Ptr &profile); void setupAdvancedPage(const Profile::Ptr &profile); void setupMousePage(const Profile::Ptr &profile); int maxSpinBoxWidth(const KPluralHandlingSpinBox *spinBox, const KLocalizedString &suffix); // Returns the name of the colorScheme used in the current profile const QString currentColorSchemeName() const; // select @p selectedColorSchemeName after the changes are saved // in the colorScheme editor void updateColorSchemeList(const QString &selectedColorSchemeName = QString()); void updateColorSchemeButtons(); // Convenience method KeyboardTranslatorManager *_keyManager = KeyboardTranslatorManager::instance(); // Updates the key bindings list widget on the Keyboard tab and selects // @p selectKeyBindingsName void updateKeyBindingsList(const QString &selectKeyBindingsName = QString()); void updateKeyBindingsButtons(); void showKeyBindingEditor(bool isNewTranslator); void showColorSchemeEditor(bool isNewScheme); void closeColorSchemeEditor(); void preview(int property, const QVariant &value); void delayedPreview(int property, const QVariant &value); void unpreview(int property); void unpreviewAll(); void enableIfNonEmptySelection(QWidget *widget, QItemSelectionModel *selectionModel); void updateCaption(const Profile::Ptr profile); void updateTransparencyWarning(); void updateFontPreview(QFont font); // Update _tempProfile in a way of respecting the apply button. // When used with some previewed property, this method should // always come after the preview operation. void updateTempProfileProperty(Profile::Property, const QVariant &value); // helper method for creating an empty & hidden profile and assigning // it to _tempProfile. void createTempProfile(); // Enable or disable apply button, used only within // updateTempProfileProperty(). void updateButtonApply(); static QString groupProfileNames(const ProfileGroup::Ptr group, int maxLength = -1); struct RadioOption { QAbstractButton *button; int value; const char *slot; }; void setupRadio(const QVector& possibilities, int actual); struct BooleanOption { QAbstractButton *button; Profile::Property property; const char *slot; }; void setupCheckBoxes(const QVector& options, const Profile::Ptr &profile); struct ButtonGroupOption { QAbstractButton *button; int value; }; struct ButtonGroupOptions { QButtonGroup *group; Profile::Property profileProperty; bool preview; QVector buttons; }; void setupButtonGroup(const ButtonGroupOptions &options, const Profile::Ptr &profile); // returns false if: // - the profile name is empty // - the name matches the name of an already existing profile // - the existing profile config file is read-only // otherwise returns true. bool isValidProfileName(); Ui::EditProfileGeneralPage *_generalUi; Ui::EditProfileTabsPage *_tabsUi; Ui::EditProfileAppearancePage *_appearanceUi; Ui::EditProfileScrollingPage *_scrollingUi; Ui::EditProfileKeyboardPage *_keyboardUi; Ui::EditProfileMousePage *_mouseUi; Ui::EditProfileAdvancedPage *_advancedUi; using PageSetupMethod = void (EditProfileDialog::*)(const Profile::Ptr &); struct Page { Page(PageSetupMethod setupPage = nullptr, bool needsUpdate = false) : setupPage(setupPage) , needsUpdate(needsUpdate) {} PageSetupMethod setupPage; bool needsUpdate; }; QMap _pages; Profile::Ptr _tempProfile; Profile::Ptr _profile; QHash _previewedProperties; QHash _delayedPreviewProperties; QTimer *_delayedPreviewTimer; ColorSchemeEditor *_colorDialog; QDialogButtonBox *_buttonBox; FontDialog *_fontDialog; }; /** * A delegate which can display and edit color schemes in a view. */ class ColorSchemeViewDelegate : public QAbstractItemDelegate { Q_OBJECT public: explicit ColorSchemeViewDelegate(QObject *parent = nullptr); // reimplemented void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; }; /** * An utility class for aligning 0th column in multiple QGridLayouts. * * Limitations: * - a layout can't be nested in another layout * - reference widget must be an ancestor of all added layouts * - only 0th column is processed (widgets spanning multiple columns * are ignored) */ class LabelsAligner: public QObject { Q_OBJECT public: LabelsAligner(QWidget *refWidget): _refWidget(refWidget) {} void addLayout(QGridLayout *layout) { _layouts.append(layout); } void addLayouts(const QVector &layouts) { _layouts.append(layouts); } void setReferenceWidget(QWidget *refWidget) { _refWidget = refWidget; } public Q_SLOTS: void updateLayouts() { for (const auto *layout: qAsConst(_layouts)) { QWidget *widget = layout->parentWidget(); Q_ASSERT(widget); do { QLayout *widgetLayout = widget->layout(); if (widgetLayout) { widgetLayout->update(); widgetLayout->activate(); } widget = widget->parentWidget(); } while (widget != _refWidget && widget != nullptr); } } void align() { Q_ASSERT(_refWidget); if (_layouts.count() <= 1) { return; } int maxRight = 0; for (const auto *layout: qAsConst(_layouts)) { int left = getLeftMargin(layout); for (int row = 0; row < layout->rowCount(); ++row) { QLayoutItem *layoutItem = layout->itemAtPosition(row, LABELS_COLUMN); if (!layoutItem) { continue; } QWidget *widget = layoutItem->widget(); if (!widget) { continue; } const int idx = layout->indexOf(widget); int rows, cols, rowSpan, colSpan; layout->getItemPosition(idx, &rows, &cols, &rowSpan, &colSpan); if (colSpan > 1) { continue; } const int right = left + widget->sizeHint().width(); if (maxRight < right) { maxRight = right; } } } for (auto *l: qAsConst(_layouts)) { int left = getLeftMargin(l); l->setColumnMinimumWidth(LABELS_COLUMN, maxRight - left); } } private: int getLeftMargin(const QGridLayout *layout) { int left = layout->contentsMargins().left(); if (layout->parent()->isWidgetType()) { auto *parentWidget = layout->parentWidget(); Q_ASSERT(parentWidget); left += parentWidget->contentsMargins().left(); } else { auto *parentLayout = qobject_cast(layout->parent()); Q_ASSERT(parentLayout); left += parentLayout->contentsMargins().left(); } QWidget *parent = layout->parentWidget(); while (parent != _refWidget && parent != nullptr) { left = parent->mapToParent(QPoint(left, 0)).x(); parent = parent->parentWidget(); } return left; }; static constexpr int LABELS_COLUMN = 0; QWidget *_refWidget; QVector _layouts; }; } #endif // EDITPROFILEDIALOG_H diff --git a/src/KeyboardTranslator.h b/src/KeyboardTranslator.h index 12c5e53e..2f466d9d 100644 --- a/src/KeyboardTranslator.h +++ b/src/KeyboardTranslator.h @@ -1,532 +1,532 @@ /* This source file is part of Konsole, a terminal emulator. Copyright 2007-2008 by Robert Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef KEYBOARDTRANSLATOR_H #define KEYBOARDTRANSLATOR_H // Qt -#include #include -#include #include +#include +#include // Konsole #include "konsoleprivate_export.h" class QIODevice; class QTextStream; namespace Konsole { /** * A converter which maps between key sequences pressed by the user and the * character strings which should be sent to the terminal and commands * which should be invoked when those character sequences are pressed. * * Konsole supports multiple keyboard translators, allowing the user to * specify the character sequences which are sent to the terminal * when particular key sequences are pressed. * * A key sequence is defined as a key code, associated keyboard modifiers * (Shift,Ctrl,Alt,Meta etc.) and state flags which indicate the state * which the terminal must be in for the key sequence to apply. */ class KONSOLEPRIVATE_EXPORT KeyboardTranslator { public: /** * The meaning of a particular key sequence may depend upon the state which * the terminal emulation is in. Therefore findEntry() may return a different * Entry depending upon the state flags supplied. * * This enum describes the states which may be associated with a particular * entry in the keyboard translation entry. */ enum State { /** Indicates that no special state is active */ NoState = 0, /** * TODO More documentation */ NewLineState = 1, /** * Indicates that the terminal is in 'ANSI' mode. * TODO: More documentation */ AnsiState = 2, /** * TODO More documentation */ CursorKeysState = 4, /** * Indicates that the alternate screen ( typically used by interactive programs * such as screen or vim ) is active */ AlternateScreenState = 8, /** Indicates that any of the modifier keys is active. */ AnyModifierState = 16, /** Indicates that the numpad is in application mode. */ ApplicationKeypadState = 32 }; Q_DECLARE_FLAGS(States, State) /** * This enum describes commands which are associated with particular key sequences. */ enum Command { /** Indicates that no command is associated with this command sequence */ NoCommand = 0, /** TODO Document me */ SendCommand = 1, /** Scroll the terminal display up one page */ ScrollPageUpCommand = 2, /** Scroll the terminal display down one page */ ScrollPageDownCommand = 4, /** Scroll the terminal display up one line */ ScrollLineUpCommand = 8, /** Scroll the terminal display down one line */ ScrollLineDownCommand = 16, /** Scroll the terminal display up to the start of history */ ScrollUpToTopCommand = 32, /** Scroll the terminal display down to the end of history */ ScrollDownToBottomCommand = 64, /** Echos the operating system specific erase character. */ EraseCommand = 256 }; Q_DECLARE_FLAGS(Commands, Command) /** * Represents an association between a key sequence pressed by the user * and the character sequence and commands associated with it for a particular * KeyboardTranslator. */ class Entry { public: /** * Constructs a new entry for a keyboard translator. */ Entry(); /** * Returns true if this entry is null. * This is true for newly constructed entries which have no properties set. */ bool isNull() const; /** Returns the commands associated with this entry */ Command command() const; /** Sets the command associated with this entry. */ void setCommand(Command aCommand); /** * Returns the character sequence associated with this entry, optionally replacing * wildcard '*' characters with numbers to indicate the keyboard modifiers being pressed. * * TODO: The numbers used to replace '*' characters are taken from the Konsole/KDE 3 code. * Document them. * * @param expandWildCards Specifies whether wild cards (occurrences of the '*' character) in * the entry should be replaced with a number to indicate the modifier keys being pressed. * * @param keyboardModifiers The keyboard modifiers being pressed. */ QByteArray text(bool expandWildCards = false, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier) const; /** Sets the character sequence associated with this entry */ void setText(const QByteArray &aText); /** * Returns the character sequence associated with this entry, * with any non-printable characters replaced with escape sequences. * * eg. \\E for Escape, \\t for tab, \\n for new line. * * @param expandWildCards See text() * @param keyboardModifiers The keyboard modifiers being pressed. */ QByteArray escapedText(bool expandWildCards = false, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier) const; /** Returns the character code ( from the Qt::Key enum ) associated with this entry */ int keyCode() const; /** Sets the character code associated with this entry */ void setKeyCode(int aKeyCode); /** * Returns a bitwise-OR of the enabled keyboard modifiers associated with this entry. * If a modifier is set in modifierMask() but not in modifiers(), this means that the entry * only matches when that modifier is NOT pressed. * * If a modifier is not set in modifierMask() then the entry matches whether the modifier * is pressed or not. */ Qt::KeyboardModifiers modifiers() const; /** Returns the keyboard modifiers which are valid in this entry. See modifiers() */ Qt::KeyboardModifiers modifierMask() const; /** See modifiers() */ void setModifiers(Qt::KeyboardModifiers modifiers); /** See modifierMask() and modifiers() */ void setModifierMask(Qt::KeyboardModifiers mask); /** * Returns a bitwise-OR of the enabled state flags associated with this entry. * If flag is set in stateMask() but not in state(), this means that the entry only * matches when the terminal is NOT in that state. * * If a state is not set in stateMask() then the entry matches whether the terminal * is in that state or not. */ States state() const; /** Returns the state flags which are valid in this entry. See state() */ States stateMask() const; /** See state() */ void setState(States aState); /** See stateMask() */ void setStateMask(States aStateMask); /** * Returns this entry's conditions ( ie. its key code, modifier and state criteria ) * as a string. */ QString conditionToString() const; /** * Returns this entry's result ( ie. its command or character sequence ) * as a string. * * @param expandWildCards See text() * @param keyboardModifiers The keyboard modifiers being pressed. */ QString resultToString(bool expandWildCards = false, Qt::KeyboardModifiers keyboardModifiers = Qt::NoModifier) const; /** * Returns true if this entry matches the given key sequence, specified * as a combination of @p testKeyCode , @p testKeyboardModifiers and @p testState. */ bool matches(int testKeyCode, Qt::KeyboardModifiers testKeyboardModifiers, States testState) const; bool operator==(const Entry &rhs) const; private: void insertModifier(QString &item, int modifier) const; void insertState(QString &item, int state) const; QByteArray unescape(const QByteArray &text) const; int _keyCode; Qt::KeyboardModifiers _modifiers; Qt::KeyboardModifiers _modifierMask; States _state; States _stateMask; Command _command; QByteArray _text; }; /** Constructs a new keyboard translator with the given @p name */ explicit KeyboardTranslator(const QString &name); /** Returns the name of this keyboard translator */ QString name() const; /** Sets the name of this keyboard translator */ void setName(const QString &name); /** Returns the descriptive name of this keyboard translator */ QString description() const; /** Sets the descriptive name of this keyboard translator */ void setDescription(const QString &description); /** * Looks for an entry in this keyboard translator which matches the given * key code, keyboard modifiers and state flags. * * Returns the matching entry if found or a null Entry otherwise ( ie. * entry.isNull() will return true ) * * @param keyCode A key code from the Qt::Key enum * @param modifiers A combination of modifiers * @param state Optional flags which specify the current state of the terminal */ Entry findEntry(int keyCode, Qt::KeyboardModifiers modifiers, States state = NoState) const; /** * Adds an entry to this keyboard translator's table. Entries can be looked up according * to their key sequence using findEntry() */ void addEntry(const Entry &entry); /** * Replaces an entry in the translator. If the @p existing entry is null, * then this is equivalent to calling addEntry(@p replacement) */ void replaceEntry(const Entry &existing, const Entry &replacement); /** * Removes an entry from the table. */ void removeEntry(const Entry &entry); /** Returns a list of all entries in the translator. */ QList entries() const; private: // All entries in this translator, indexed by their keycode QMultiHash _entries; QString _name; QString _description; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::States) Q_DECLARE_OPERATORS_FOR_FLAGS(KeyboardTranslator::Commands) class KONSOLEPRIVATE_EXPORT FallbackKeyboardTranslator : public KeyboardTranslator { public: FallbackKeyboardTranslator(); }; /** * Parses the contents of a Keyboard Translator (.keytab) file and * returns the entries found in it. * * Usage example: * * @code * QFile source( "/path/to/keytab" ); * source.open( QIODevice::ReadOnly ); * * KeyboardTranslator* translator = new KeyboardTranslator( "name-of-translator" ); * * KeyboardTranslatorReader reader(source); * while ( reader.hasNextEntry() ) * translator->addEntry(reader.nextEntry()); * * source.close(); * * if ( !reader.parseError() ) * { * // parsing succeeded, do something with the translator * } * else * { * // parsing failed * } * @endcode */ class KONSOLEPRIVATE_EXPORT KeyboardTranslatorReader { public: /** Constructs a new reader which parses the given @p source */ explicit KeyboardTranslatorReader(QIODevice *source); ~KeyboardTranslatorReader(); KeyboardTranslatorReader(const KeyboardTranslatorReader &) = delete; KeyboardTranslatorReader &operator=(const KeyboardTranslatorReader &) = delete; /** * Returns the description text. * TODO: More documentation */ QString description() const; /** Returns true if there is another entry in the source stream */ bool hasNextEntry() const; /** Returns the next entry found in the source stream */ KeyboardTranslator::Entry nextEntry(); /** * Returns true if an error occurred whilst parsing the input or * false if no error occurred. */ bool parseError(); /** * Parses a condition and result string for a translator entry * and produces a keyboard translator entry. * * The condition and result strings are in the same format as in */ static KeyboardTranslator::Entry createEntry(const QString &condition, const QString &result); private: struct Token { enum Type { TitleKeyword, TitleText, KeyKeyword, KeySequence, Command, OutputText }; Type type; QString text; }; QList tokenize(const QString &); void readNext(); bool decodeSequence(const QString &, int &keyCode, Qt::KeyboardModifiers &modifiers, Qt::KeyboardModifiers &modifierMask, KeyboardTranslator::States &flags, KeyboardTranslator::States &flagMask); static bool parseAsModifier(const QString &item, Qt::KeyboardModifier &modifier); static bool parseAsStateFlag(const QString &item, KeyboardTranslator::State &flag); static bool parseAsKeyCode(const QString &item, int &keyCode); static bool parseAsCommand(const QString &text, KeyboardTranslator::Command &command); QIODevice *_source; QString _description; KeyboardTranslator::Entry _nextEntry; bool _hasNext; }; /** Writes a keyboard translation to disk. */ class KeyboardTranslatorWriter { public: /** * Constructs a new writer which saves data into @p destination. * The caller is responsible for closing the device when writing is complete. */ explicit KeyboardTranslatorWriter(QIODevice *destination); ~KeyboardTranslatorWriter(); KeyboardTranslatorWriter(const KeyboardTranslatorWriter &) = delete; KeyboardTranslatorWriter &operator=(const KeyboardTranslatorWriter &) = delete; /** * Writes the header for the keyboard translator. * @param description Description of the keyboard translator. */ void writeHeader(const QString &description); /** Writes a translator entry. */ void writeEntry(const KeyboardTranslator::Entry &entry); private: QIODevice *_destination; QTextStream *_writer; }; inline int KeyboardTranslator::Entry::keyCode() const { return _keyCode; } inline void KeyboardTranslator::Entry::setKeyCode(int aKeyCode) { _keyCode = aKeyCode; } inline void KeyboardTranslator::Entry::setModifiers(Qt::KeyboardModifiers modifiers) { _modifiers = modifiers; } inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifiers() const { return _modifiers; } inline void KeyboardTranslator::Entry::setModifierMask(Qt::KeyboardModifiers mask) { _modifierMask = mask; } inline Qt::KeyboardModifiers KeyboardTranslator::Entry::modifierMask() const { return _modifierMask; } inline bool KeyboardTranslator::Entry::isNull() const { return *this == Entry(); } inline void KeyboardTranslator::Entry::setCommand(Command aCommand) { _command = aCommand; } inline KeyboardTranslator::Command KeyboardTranslator::Entry::command() const { return _command; } inline void KeyboardTranslator::Entry::setText(const QByteArray &aText) { _text = unescape(aText); } inline int oneOrZero(int value) { return value ? 1 : 0; } inline QByteArray KeyboardTranslator::Entry::text(bool expandWildCards, Qt::KeyboardModifiers keyboardModifiers) const { QByteArray expandedText = _text; if (expandWildCards) { int modifierValue = 1; modifierValue += oneOrZero(keyboardModifiers & Qt::ShiftModifier); modifierValue += oneOrZero(keyboardModifiers & Qt::AltModifier) << 1; modifierValue += oneOrZero(keyboardModifiers & Qt::ControlModifier) << 2; for (int i = 0; i < _text.length(); i++) { if (expandedText[i] == '*') { expandedText[i] = '0' + modifierValue; } } } return expandedText; } inline void KeyboardTranslator::Entry::setState(States aState) { _state = aState; } inline KeyboardTranslator::States KeyboardTranslator::Entry::state() const { return _state; } inline void KeyboardTranslator::Entry::setStateMask(States aStateMask) { _stateMask = aStateMask; } inline KeyboardTranslator::States KeyboardTranslator::Entry::stateMask() const { return _stateMask; } } Q_DECLARE_METATYPE(Konsole::KeyboardTranslator::Entry) Q_DECLARE_METATYPE(const Konsole::KeyboardTranslator *) #endif // KEYBOARDTRANSLATOR_H diff --git a/src/ProcessInfo.h b/src/ProcessInfo.h index b51c639f..e00df04e 100644 --- a/src/ProcessInfo.h +++ b/src/ProcessInfo.h @@ -1,414 +1,413 @@ /* 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 PROCESSINFO_H #define PROCESSINFO_H // Qt #include -#include #include #include namespace Konsole { /** * Takes a snapshot of the state of a process and provides access to * information such as the process name, parent process, * the foreground process in the controlling terminal, * the arguments with which the process was started. * * To create a new snapshot, construct a new ProcessInfo instance, * using ProcessInfo::newInstance(), * passing the process identifier of the process you are interested in. * * After creating a new instance, call the update() method to take a * snapshot of the current state of the process. * * Before calling any additional methods, check that the process state * was read successfully using the isValid() method. * * Each accessor method which provides information about the process state ( such as pid(), * currentDir(), name() ) takes a pointer to a boolean as an argument. If the information * requested was read successfully then the boolean is set to true, otherwise it is set * to false, in which case the return value from the function should be ignored. * If this boolean is set to false, it may indicate an error reading the process information, * or it may indicate that the information is not available on the current platform. * * eg. * * @code * ProcessInfo* info = ProcessInfo::newInstance(pid); * info->update(); * * if ( info->isValid() ) * { * bool ok; * * QString name = info->name(&ok); * if ( ok ) qDebug() << "process name - " << name; * int parentPid = info->parentPid(&ok); * if ( ok ) qDebug() << "parent process - " << parentPid; * int foregroundPid = info->foregroundPid(&ok); * if ( ok ) qDebug() << "foreground process - " << foregroundPid; * } * @endcode */ class ProcessInfo { public: /** * Constructs a new instance of a suitable ProcessInfo sub-class for * the current platform which provides information about a given process. * * @param pid The pid of the process to examine * @param titleFormat The local title format - used to determine if it * contains %u which requires expensive prodecures to be used. */ static ProcessInfo *newInstance(int pid, const QString &titleFormat); virtual ~ProcessInfo() { } /** * Updates the information about the process. This must * be called before attempting to use any of the accessor methods. */ void update(); /** Returns true if the process state was read successfully. */ bool isValid() const; /** * Returns the process id. * * @param ok Set to true if the process id was read successfully or false otherwise */ int pid(bool *ok) const; /** * Returns the id of the parent process id was read successfully or false otherwise * * @param ok Set to true if the parent process id */ int parentPid(bool *ok) const; /** * Returns the id of the current foreground process * * NOTE: Using the foregroundProcessGroup() method of the Pty * instance associated with the terminal of interest is preferred * over using this method. * * @param ok Set to true if the foreground process id was read successfully or false otherwise */ int foregroundPid(bool *ok) const; /* Returns the user id of the process */ int userId(bool *ok) const; /** Returns the user's name of the process */ QString userName() const; /** Returns the user's home directory of the process */ QString userHomeDir() const; /** Returns the local host */ static QString localHost(); /** Returns the name of the current process */ QString name(bool *ok) const; /** * Returns the command-line arguments which the process * was started with. * * The first argument is the name used to launch the process. * * @param ok Set to true if the arguments were read successfully or false otherwise. */ QVector arguments(bool *ok) const; /** * Returns the current working directory of the process * * @param ok Set to true if the current working directory was read successfully or false otherwise */ QString currentDir(bool *ok) const; /** * Returns the current working directory of the process (or its parent) */ QString validCurrentDir() const; /** Forces the user home directory to be calculated */ void setUserHomeDir(); /** * This enum describes the errors which can occur when trying to read * a process's information. */ enum Error { /** No error occurred. */ NoError, /** The nature of the error is unknown. */ UnknownError, /** Konsole does not have permission to obtain the process information. */ PermissionsError }; /** * Returns the last error which occurred. */ Error error() const; enum Field { PROCESS_ID = 1, PARENT_PID = 2, FOREGROUND_PID = 4, ARGUMENTS = 8, NAME = 16, CURRENT_DIR = 32, UID = 64 }; Q_DECLARE_FLAGS(Fields, Field) // takes a full directory path and returns a // shortened version suitable for display in // space-constrained UI elements (eg. tabs) QString formatShortDir(const QString &dirPath) const; void setUserNameRequired(bool need); protected: /** * Constructs a new process instance. You should not call the constructor * of ProcessInfo or its subclasses directly. Instead use the * static ProcessInfo::newInstance() method which will return * a suitable ProcessInfo instance for the current platform. */ explicit ProcessInfo(int pid); /** * This is called on construction to read the process state * Subclasses should reimplement this function to provide * platform-specific process state reading functionality. * * When called, readProcessInfo() should attempt to read all * of the necessary state information. If the attempt is successful, * it should set the process id using setPid(), and update * the other relevant information using setParentPid(), setName(), * setArguments() etc. * * Calls to isValid() will return true only if the process id * has been set using setPid() * * @param pid The process id of the process to read */ virtual void readProcessInfo(int pid) = 0; /** * Determine the current directory of the process. * @param pid process ID to use * @return true on success */ virtual bool readCurrentDir(int pid) = 0; /* Read the user name */ virtual void readUserName(void) = 0; /** Sets the process id associated with this ProcessInfo instance */ void setPid(int pid); /** Sets the parent process id as returned by parentPid() */ void setParentPid(int pid); /** Sets the foreground process id as returned by foregroundPid() */ void setForegroundPid(int pid); /** Sets the user id associated with this ProcessInfo instance */ void setUserId(int uid); /** Sets the user name of the process as set by readUserName() */ void setUserName(const QString &name); /** Sets the name of the process as returned by name() */ void setName(const QString &name); /** Sets the current working directory for the process */ void setCurrentDir(const QString &dir); /** Sets the error */ void setError(Error error); /** Convenience method. Sets the error based on a QFile error code. */ void setFileError(QFile::FileError error); /** * Adds a commandline argument for the process, as returned * by arguments() */ void addArgument(const QString &argument); /** * clear the commandline arguments for the process, as returned * by arguments() */ void clearArguments(); bool userNameRequired() const; private: Fields _fields; int _pid; int _parentPid; int _foregroundPid; int _userId; Error _lastError; QString _name; QString _userName; QString _userHomeDir; QString _currentDir; bool _userNameRequired; QVector _arguments; static QSet commonDirNames(); static QSet _commonDirNames; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ProcessInfo::Fields) /** * Implementation of ProcessInfo which does nothing. * Used on platforms where a suitable ProcessInfo subclass is not * available. * * isValid() will always return false for instances of NullProcessInfo */ class NullProcessInfo : public ProcessInfo { public: /** * Constructs a new NullProcessInfo instance. * See ProcessInfo::newInstance() */ explicit NullProcessInfo(int pid, const QString &titleFormat); protected: void readProcessInfo(int pid) Q_DECL_OVERRIDE; bool readCurrentDir(int pid) Q_DECL_OVERRIDE; void readUserName(void) Q_DECL_OVERRIDE; }; #if !defined(Q_OS_WIN) /** * Implementation of ProcessInfo for Unix platforms which uses * the /proc filesystem */ class UnixProcessInfo : public ProcessInfo { public: /** * Constructs a new instance of UnixProcessInfo. * See ProcessInfo::newInstance() */ explicit UnixProcessInfo(int pid, const QString &titleFormat); protected: /** * Implementation of ProcessInfo::readProcessInfo(); calls the * four private methods below in turn. */ void readProcessInfo(int pid) Q_DECL_OVERRIDE; void readUserName(void) Q_DECL_OVERRIDE; private: /** * Read the standard process information -- PID, parent PID, foreground PID. * @param pid process ID to use * @return true on success */ virtual bool readProcInfo(int pid) = 0; /** * Determine what arguments were passed to the process. Sets _arguments. * @param pid process ID to use * @return true on success */ virtual bool readArguments(int pid) = 0; }; #endif /** * Lightweight class which provides additional information about SSH processes. */ class SSHProcessInfo { public: /** * Constructs a new SSHProcessInfo instance which provides additional * information about the specified SSH process. * * @param process A ProcessInfo instance for a SSH process. */ explicit SSHProcessInfo(const ProcessInfo &process); /** * Returns the user name which the user initially logged into on * the remote computer. */ QString userName() const; /** * Returns the host which the user has connected to. */ QString host() const; /** * Returns the port on host which the user has connected to. */ QString port() const; /** * Returns the command which the user specified to execute on the * remote computer when starting the SSH process. */ QString command() const; /** * Operates in the same way as ProcessInfo::format(), except * that the set of markers understood is different: * * %u - Replaced with user name which the user initially logged * into on the remote computer. * %h - Replaced with the first part of the host name which * is connected to. * %H - Replaced with the full host name of the computer which * is connected to. * %c - Replaced with the command which the user specified * to execute when starting the SSH process. */ QString format(const QString &input) const; private: const ProcessInfo &_process; QString _user; QString _host; QString _port; QString _command; }; } #endif //PROCESSINFO_H diff --git a/src/SessionController.cpp b/src/SessionController.cpp index eb1c48be..54a628fe 100644 --- a/src/SessionController.cpp +++ b/src/SessionController.cpp @@ -1,1790 +1,1791 @@ /* 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); foreach(QAction * action, actionCollection()->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } setIdentifier(++_lastControllerId); sessionAttributeChanged(); view->installEventFilter(this); view->setSessionController(this); // install filter on the view to highlight URLs and files updateFilterList(SessionManager::instance()->sessionProfile(_session)); // listen for changes in session, we might need to change the enabled filters connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::SessionController::updateFilterList); // listen for session resize requests connect(_session.data(), &Konsole::Session::resizeRequest, this, &Konsole::SessionController::sessionResizeRequest); // listen for popup menu requests connect(_view.data(), &Konsole::TerminalDisplay::configureRequest, this, &Konsole::SessionController::showDisplayContextMenu); // move view to newest output when keystrokes occur connect(_view.data(), &Konsole::TerminalDisplay::keyPressedSignal, this, &Konsole::SessionController::trackOutput); // listen to activity / silence notifications from session connect(_session.data(), &Konsole::Session::stateChanged, this, &Konsole::SessionController::sessionStateChanged); // listen to title and icon changes connect(_session.data(), &Konsole::Session::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::focusGained, this, &Konsole::SessionController::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 '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() { if (!_view.isNull()) { _view->setScreenWindow(nullptr); } _allControllers.remove(this); if (!_editProfileDialog.isNull()) { delete _editProfileDialog.data(); } } void SessionController::trackOutput(QKeyEvent* event) { Q_ASSERT(_view->screenWindow()); // Only jump to the bottom if the user actually typed something in, // not if the user e. g. just pressed a modifier. if (event->text().isEmpty() && (event->modifiers() != 0u)) { return; } _view->screenWindow()->setTrackOutput(true); } void SessionController::interactionHandler() { // This flag is used to make sure those special icons indicating interest // events (activity/silence/bell?) remain in the tab until user interaction // happens. Otherwise, those special icons will quickly be replaced by // normal icon when ::snapshot() is triggered _keepIconUntilInteraction = false; _interactionTimer->start(); } void SessionController::snapshot() { Q_ASSERT(!_session.isNull()); QString title = _session->getDynamicTitle(); title = title.simplified(); // Visualize that the session is broadcasting to others if ((_copyToGroup != nullptr) && _copyToGroup->sessions().count() > 1) { title.append(QLatin1Char('*')); } // use the fallback title if needed if (title.isEmpty()) { title = _session->title(Session::NameRole); } // apply new title _session->setTitle(Session::DisplayedTitleRole, title); // do not forget icon updateSessionIcon(); } QString SessionController::currentDir() const { return _session->currentWorkingDirectory(); } QUrl SessionController::url() const { return _session->getUrl(); } void SessionController::rename() { renameSession(); } void SessionController::openUrl(const QUrl& url) { // Clear shell's command line if (!_session->isForegroundProcessActive() && _bookmarkValidProgramsToClear.contains(_session->foregroundProcessName())) { _session->sendTextToTerminal(QChar(0x03), QLatin1Char('\n')); // Ctrl+C } // handle local paths if (url.isLocalFile()) { QString path = url.toLocalFile(); _session->sendTextToTerminal(QStringLiteral("cd ") + KShell::quoteArg(path), QLatin1Char('\r')); } else if (url.scheme().isEmpty()) { // QUrl couldn't parse what the user entered into the URL field // so just dump it to the shell QString command = url.toDisplayString(); if (!command.isEmpty()) { _session->sendTextToTerminal(command, QLatin1Char('\r')); } } else if (url.scheme() == QLatin1String("ssh")) { QString sshCommand = QStringLiteral("ssh "); if (url.port() > -1) { sshCommand += QStringLiteral("-p %1 ").arg(url.port()); } if (!url.userName().isEmpty()) { sshCommand += (url.userName() + QLatin1Char('@')); } if (!url.host().isEmpty()) { sshCommand += url.host(); } _session->sendTextToTerminal(sshCommand, QLatin1Char('\r')); } else if (url.scheme() == QLatin1String("telnet")) { QString telnetCommand = QStringLiteral("telnet "); if (!url.userName().isEmpty()) { telnetCommand += QStringLiteral("-l %1 ").arg(url.userName()); } if (!url.host().isEmpty()) { telnetCommand += (url.host() + QLatin1Char(' ')); } if (url.port() > -1) { telnetCommand += QString::number(url.port()); } _session->sendTextToTerminal(telnetCommand, QLatin1Char('\r')); } else { //TODO Implement handling for other Url types KMessageBox::sorry(_view->window(), i18n("Konsole does not know how to open the bookmark: ") + url.toDisplayString()); qCDebug(KonsoleDebug) << "Unable to open bookmark at url" << url << ", I do not know" << " how to handle the protocol " << url.scheme(); } } void SessionController::setupPrimaryScreenSpecificActions(bool use) { KActionCollection* collection = actionCollection(); QAction* clearAction = collection->action(QStringLiteral("clear-history")); QAction* resetAction = collection->action(QStringLiteral("clear-history-and-reset")); QAction* selectAllAction = collection->action(QStringLiteral("select-all")); QAction* selectLineAction = collection->action(QStringLiteral("select-line")); // these actions are meaningful only when primary screen is used. clearAction->setEnabled(use); resetAction->setEnabled(use); selectAllAction->setEnabled(use); selectLineAction->setEnabled(use); } void SessionController::selectionChanged(const QString& selectedText) { _selectedText = selectedText; updateCopyAction(selectedText); } void SessionController::updateCopyAction(const QString& selectedText) { QAction* copyAction = actionCollection()->action(QStringLiteral("edit_copy")); // copy action is meaningful only when some text is selected. copyAction->setEnabled(!selectedText.isEmpty()); } void SessionController::updateWebSearchMenu() { // reset _webSearchMenu->setVisible(false); _webSearchMenu->menu()->clear(); if (_selectedText.isEmpty()) { return; } QString searchText = _selectedText; searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified(); if (searchText.isEmpty()) { return; } KUriFilterData filterData(searchText); filterData.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly); if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::NormalTextFilter)) { const QStringList searchProviders = filterData.preferredSearchProviders(); if (!searchProviders.isEmpty()) { _webSearchMenu->setText(i18n("Search for '%1' with", KStringHandler::rsqueeze(searchText, 16))); QAction* action = nullptr; foreach(const QString& searchProvider, searchProviders) { action = new QAction(searchProvider, _webSearchMenu); action->setIcon(QIcon::fromTheme(filterData.iconNameForPreferredSearchProvider(searchProvider))); action->setData(filterData.queryForPreferredSearchProvider(searchProvider)); connect(action, &QAction::triggered, this, &Konsole::SessionController::handleWebShortcutAction); _webSearchMenu->addAction(action); } _webSearchMenu->addSeparator(); action = new QAction(i18n("Configure Web Shortcuts..."), _webSearchMenu); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(action, &QAction::triggered, this, &Konsole::SessionController::configureWebShortcuts); _webSearchMenu->addAction(action); _webSearchMenu->setVisible(true); } } } void SessionController::handleWebShortcutAction() { 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().value(); _session->sendSignal(signal); } 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); } } bool SessionController::eventFilter(QObject* watched , QEvent* event) { if (event->type() == QEvent::FocusIn && watched == _view) { // notify the world that the view associated with this session has been focused // used by the view manager to update the title of the MainWindow widget containing the view emit focused(this); // when the view is focused, set bell events from the associated session to be delivered // by the focused view // first, disconnect any other views which are listening for bell signals from the session disconnect(_session.data(), &Konsole::Session::bellRequest, nullptr, nullptr); // second, connect the newly focused view to listen for the session's bell signal connect(_session.data(), &Konsole::Session::bellRequest, _view.data(), &Konsole::TerminalDisplay::bell); if ((_copyInputToAllTabsAction != nullptr) && _copyInputToAllTabsAction->isChecked()) { // A session with "Copy To All Tabs" has come into focus: // Ensure that newly created sessions are included in _copyToGroup! copyInputToAllTabs(); } } return Konsole::ViewProperties::eventFilter(watched, event); } void SessionController::removeSearchFilter() { if (_searchFilter == nullptr) { return; } _view->filterChain()->removeFilter(_searchFilter); delete _searchFilter; _searchFilter = nullptr; } void SessionController::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())); if (isKonsolePart()) { action->setText(i18n("&Close Session")); } else { action->setText(i18n("&Close Tab")); } action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_W); // Open Browser action = collection->addAction(QStringLiteral("open-browser"), this, SLOT(openBrowser())); action->setText(i18n("Open File Manager")); action->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); // Copy and Paste action = KStandardAction::copy(this, SLOT(copy()), collection); #ifdef Q_OS_MACOS // Don't use the Konsole::ACCEL const here, we really want the Command key (Qt::META) // TODO: check what happens if we leave it to Qt to assign the default? collection->setDefaultShortcut(action, Qt::META + Qt::Key_C); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_C); #endif // disabled at first, since nothing has been selected now action->setEnabled(false); action = KStandardAction::paste(this, SLOT(paste()), collection); QList pasteShortcut; #ifdef Q_OS_MACOS pasteShortcut.append(QKeySequence(Qt::META + Qt::Key_V)); // No Insert key on Mac keyboards #else pasteShortcut.append(QKeySequence(Konsole::ACCEL + Qt::SHIFT + Qt::Key_V)); pasteShortcut.append(QKeySequence(Qt::SHIFT + Qt::Key_Insert)); #endif collection->setDefaultShortcuts(action, pasteShortcut); action = collection->addAction(QStringLiteral("paste-selection"), this, SLOT(pasteFromX11Selection())); action->setText(i18n("Paste Selection")); #ifdef Q_OS_MACOS collection->setDefaultShortcut(action, Qt::META + Qt::SHIFT + Qt::Key_V); #else collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Insert); #endif _webSearchMenu = new KActionMenu(i18n("Web Search"), this); _webSearchMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts"))); _webSearchMenu->setVisible(false); collection->addAction(QStringLiteral("web-search"), _webSearchMenu); action = collection->addAction(QStringLiteral("select-all"), this, SLOT(selectAll())); action->setText(i18n("&Select All")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); action = collection->addAction(QStringLiteral("select-line"), this, SLOT(selectLine())); action->setText(i18n("Select &Line")); action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection); action->setText(i18n("Save Output &As...")); #ifdef Q_OS_MACOS action->setShortcut(QKeySequence(Qt::META + Qt::Key_S)); #endif action = KStandardAction::print(this, SLOT(print_screen()), collection); action->setText(i18n("&Print Screen...")); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_P); action = collection->addAction(QStringLiteral("adjust-history"), this, SLOT(showHistoryOptions())); action->setText(i18n("Adjust Scrollback...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); action = collection->addAction(QStringLiteral("clear-history"), this, SLOT(clearHistory())); action->setText(i18n("Clear Scrollback")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action = collection->addAction(QStringLiteral("clear-history-and-reset"), this, SLOT(clearHistoryAndReset())); action->setText(i18n("Clear Scrollback and Reset")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::SHIFT + Qt::Key_K); // Profile Options action = collection->addAction(QStringLiteral("edit-current-profile"), this, SLOT(editCurrentProfile())); action->setText(i18n("Edit Current Profile...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); _switchProfileMenu = new KActionMenu(i18n("Switch Profile"), this); collection->addAction(QStringLiteral("switch-profile"), _switchProfileMenu); connect(_switchProfileMenu->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::prepareSwitchProfileMenu); // History _findAction = KStandardAction::find(this, SLOT(searchBarEvent()), collection); collection->setDefaultShortcut(_findAction, QKeySequence()); _findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection); collection->setDefaultShortcut(_findNextAction, QKeySequence()); _findNextAction->setEnabled(false); _findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection); collection->setDefaultShortcut(_findPreviousAction, QKeySequence()); _findPreviousAction->setEnabled(false); // Character Encoding _codecAction = new KCodecAction(i18n("Set &Encoding"), this); _codecAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); collection->addAction(QStringLiteral("set-encoding"), _codecAction); connect(_codecAction->menu(), &QMenu::aboutToShow, this, &Konsole::SessionController::updateCodecAction); connect(_codecAction, static_cast(&KCodecAction::triggered), this, &Konsole::SessionController::changeCodec); // Read-only action = collection->addAction(QStringLiteral("view-readonly"), this, SLOT(toggleReadOnly())); action->setText(i18nc("@item:inmenu A read only (locked) session", "Read-only")); action->setCheckable(true); updateReadOnlyActionStates(); } void SessionController::setupExtraActions() { KActionCollection* collection = actionCollection(); // Rename Session QAction* action = collection->addAction(QStringLiteral("rename-session"), this, SLOT(renameSession())); action->setText(i18n("&Rename Tab...")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_S); // Copy input to ==> all tabs 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, static_cast(&KSelectAction::triggered), this, &Konsole::SessionController::copyInputActionsTriggered); action = collection->addAction(QStringLiteral("zmodem-upload"), this, SLOT(zmodemUpload())); action->setText(i18n("&ZModem Upload...")); action->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::ALT + Qt::Key_U); // Monitor KToggleAction* toggleAction = new KToggleAction(i18n("Monitor for &Activity"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_A); action = collection->addAction(QStringLiteral("monitor-activity"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorActivity); toggleAction = new KToggleAction(i18n("Monitor for &Silence"), this); collection->setDefaultShortcut(toggleAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_I); action = collection->addAction(QStringLiteral("monitor-silence"), toggleAction); connect(action, &QAction::toggled, this, &Konsole::SessionController::monitorSilence); // Text Size action = collection->addAction(QStringLiteral("enlarge-font"), this, SLOT(increaseFontSize())); action->setText(i18n("Enlarge Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-more"))); QList enlargeFontShortcut; enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Plus)); enlargeFontShortcut.append(QKeySequence(Konsole::ACCEL + Qt::Key_Equal)); collection->setDefaultShortcuts(action, enlargeFontShortcut); action = collection->addAction(QStringLiteral("shrink-font"), this, SLOT(decreaseFontSize())); action->setText(i18n("Shrink Font")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-font-size-less"))); collection->setDefaultShortcut(action, Konsole::ACCEL + Qt::Key_Minus); 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, static_cast(&KSelectAction::triggered), this, &Konsole::SessionController::sendSignal); action = collection->addAction(QStringLiteral("sigstop-signal")); action->setText(i18n("&Suspend Task") + QStringLiteral(" (STOP)")); action->setData(SIGSTOP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigcont-signal")); action->setText(i18n("&Continue Task") + QStringLiteral(" (CONT)")); action->setData(SIGCONT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sighup-signal")); action->setText(i18n("&Hangup") + QStringLiteral(" (HUP)")); action->setData(SIGHUP); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigint-signal")); action->setText(i18n("&Interrupt Task") + QStringLiteral(" (INT)")); action->setData(SIGINT); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigterm-signal")); action->setText(i18n("&Terminate Task") + QStringLiteral(" (TERM)")); action->setData(SIGTERM); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigkill-signal")); action->setText(i18n("&Kill Task") + QStringLiteral(" (KILL)")); action->setData(SIGKILL); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr1-signal")); action->setText(i18n("User Signal &1") + QStringLiteral(" (USR1)")); action->setData(SIGUSR1); sendSignalActions->addAction(action); action = collection->addAction(QStringLiteral("sigusr2-signal")); action->setText(i18n("User Signal &2") + QStringLiteral(" (USR2)")); action->setData(SIGUSR2); sendSignalActions->addAction(action); #ifdef Q_OS_MACOS collection->setDefaultShortcut(_findAction, Qt::META + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::META + Qt::Key_G); collection->setDefaultShortcut(_findPreviousAction, Qt::META + Qt::SHIFT + Qt::Key_G); #else collection->setDefaultShortcut(_findAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_F); collection->setDefaultShortcut(_findNextAction, Qt::Key_F3); collection->setDefaultShortcut(_findPreviousAction, Qt::SHIFT + Qt::Key_F3); #endif } void SessionController::switchProfile(Profile::Ptr profile) { SessionManager::instance()->setSessionProfile(_session, profile); updateFilterList(profile); } void SessionController::prepareSwitchProfileMenu() { if (_switchProfileMenu->menu()->isEmpty()) { _profileList = new ProfileList(false, this); connect(_profileList, &Konsole::ProfileList::profileSelected, this, &Konsole::SessionController::switchProfile); } _switchProfileMenu->menu()->clear(); _switchProfileMenu->menu()->addActions(_profileList->actions()); } void SessionController::updateCodecAction() { _codecAction->setCurrentCodec(QString::fromUtf8(_session->codec())); } void SessionController::changeCodec(QTextCodec* codec) { _session->setCodec(codec); } EditProfileDialog* SessionController::profileDialogPointer() { return _editProfileDialog.data(); } void SessionController::editCurrentProfile() { // Searching for Edit profile dialog opened with the same profile const QList allSessionsControllers = _allControllers.values(); foreach (SessionController* session, allSessionsControllers) { if ((session->profileDialogPointer() != nullptr) && session->profileDialogPointer()->isVisible() && session->profileDialogPointer()->lookupProfile() == SessionManager::instance()->sessionProfile(_session)) { session->profileDialogPointer()->close(); } } // NOTE bug311270: For to prevent the crash, the profile must be reset. if (!_editProfileDialog.isNull()) { // exists but not visible delete _editProfileDialog.data(); } _editProfileDialog = new EditProfileDialog(QApplication::activeWindow()); _editProfileDialog.data()->setProfile(SessionManager::instance()->sessionProfile(_session)); _editProfileDialog.data()->show(); } void SessionController::renameSession() { const QString &sessionLocalTabTitleFormat = _session->tabTitleFormat(Session::LocalTabTitle); const QString &sessionRemoteTabTitleFormat = _session->tabTitleFormat(Session::RemoteTabTitle); QScopedPointer dialog(new RenameTabDialog(QApplication::activeWindow())); dialog->setTabTitleText(sessionLocalTabTitleFormat); dialog->setRemoteTabTitleText(sessionRemoteTabTitleFormat); if (_session->isRemote()) { dialog->focusRemoteTabTitleText(); } else { dialog->focusTabTitleText(); } QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result != 0) { const QString &tabTitle = dialog->tabTitleText(); const QString &remoteTabTitle = dialog->remoteTabTitleText(); if (tabTitle != sessionLocalTabTitleFormat) { _session->setTabTitleFormat(Session::LocalTabTitle, tabTitle); emit tabRenamedByUser(true); // trigger an update of the tab text snapshot(); } if(remoteTabTitle != sessionRemoteTabTitleFormat) { _session->setTabTitleFormat(Session::RemoteTabTitle, remoteTabTitle); emit tabRenamedByUser(true); snapshot(); } } } bool SessionController::confirmClose() const { if (_session->isForegroundProcessActive()) { QString title = _session->foregroundProcessName(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program is currently running in this session." " Are you sure you want to close it?"); } else { question = i18n("The program '%1' is currently running in this session." " Are you sure you want to close it?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } bool SessionController::confirmForceClose() const { if (_session->isRunning()) { QString title = _session->program(); // hard coded for now. In future make it possible for the user to specify which programs // are ignored when considering whether to display a confirmation QStringList ignoreList; ignoreList << QString::fromUtf8(qgetenv("SHELL")).section(QLatin1Char('/'), -1); if (ignoreList.contains(title)) { return true; } QString question; if (title.isEmpty()) { question = i18n("A program in this session would not die." " Are you sure you want to kill it by force?"); } else { question = i18n("The program '%1' is in this session would not die." " Are you sure you want to kill it by force?", title); } int result = KMessageBox::warningYesNo(_view->window(), question, i18n("Confirm Close")); return result == KMessageBox::Yes; } return true; } void SessionController::closeSession() { if (_preventClose) { return; } if (confirmClose()) { if (_session->closeInNormalWay()) { return; } else if (confirmForceClose()) { if (_session->closeInForceWay()) { return; } else { qCDebug(KonsoleDebug) << "Konsole failed to close a session in any way."; } } } } // Trying to open a remote Url may produce unexpected results. // Therefore, if a remote url, open the user's home path. // TODO consider: 1) disable menu upon remote session // 2) transform url to get the desired result (ssh -> sftp, etc) void SessionController::openBrowser() { const QUrl currentUrl = url(); if (currentUrl.isLocalFile()) { new KRun(currentUrl, QApplication::activeWindow(), true); } else { new KRun(QUrl::fromLocalFile(QDir::homePath()), QApplication::activeWindow(), true); } } void SessionController::copy() { _view->copyToClipboard(); } void SessionController::paste() { _view->pasteFromClipboard(); } void SessionController::pasteFromX11Selection() { _view->pasteFromX11Selection(); } void SessionController::selectAll() { _view->selectAll(); } void SessionController::selectLine() { _view->selectCurrentLine(); } static const KXmlGuiWindow* findWindow(const QObject* object) { // Walk up the QObject hierarchy to find a KXmlGuiWindow. while (object != nullptr) { const 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 ... foreach(const TerminalDisplay* terminalDisplay, session->views()) { // ... and check whether a TerminalDisplay has the same // window as given in the parameter if (window == findWindow(terminalDisplay)) { return(true); } } return(false); } void SessionController::copyInputActionsTriggered(QAction* action) { const auto mode = action->data().value(); switch (mode) { case CopyInputToAllTabsMode: copyInputToAllTabs(); break; case CopyInputToSelectedTabsMode: copyInputToSelectedTabs(); break; case CopyInputToNoneMode: copyInputToNone(); break; default: Q_ASSERT(false); } } void SessionController::copyInputToAllTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); } // Find our window ... const KXmlGuiWindow* myWindow = findWindow(_view); QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto session : group) { // First, ensure that the session is removed // (necessary to avoid duplicates on addSession()!) _copyToGroup->removeSession(session); // Add current session if it is displayed our window if (hasTerminalDisplayInSameWindow(session, myWindow)) { _copyToGroup->addSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } void SessionController::copyInputToSelectedTabs() { if (_copyToGroup == nullptr) { _copyToGroup = new SessionGroup(this); _copyToGroup->addSession(_session); _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); } QPointer dialog = new CopyInputDialog(_view); dialog->setMasterSession(_session); QSet currentGroup = QSet::fromList(_copyToGroup->sessions()); currentGroup.remove(_session); dialog->setChosenSessions(currentGroup); QPointer guard(_session); int result = dialog->exec(); if (guard.isNull()) { return; } if (result == QDialog::Accepted) { QSet newGroup = dialog->chosenSessions(); newGroup.remove(_session); QSet completeGroup = newGroup | currentGroup; foreach(Session * session, completeGroup) { if (newGroup.contains(session) && !currentGroup.contains(session)) { _copyToGroup->addSession(session); } else if (!newGroup.contains(session) && currentGroup.contains(session)) { _copyToGroup->removeSession(session); } } _copyToGroup->setMasterStatus(_session, true); _copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); snapshot(); } } void SessionController::copyInputToNone() { if (_copyToGroup == nullptr) { // No 'Copy To' is active return; } QSet group = QSet::fromList(SessionManager::instance()->sessions()); for (auto iterator : group) { Session* session = iterator; if (session != _session) { _copyToGroup->removeSession(iterator); } } delete _copyToGroup; _copyToGroup = nullptr; snapshot(); } void SessionController::searchClosed() { _isSearchBarEnabled = false; searchHistory(false); } void SessionController::updateFilterList(Profile::Ptr profile) { if (profile != SessionManager::instance()->sessionProfile(_session)) { return; } bool underlineFiles = profile->underlineFilesEnabled(); if (!underlineFiles && (_fileFilter != nullptr)) { _view->filterChain()->removeFilter(_fileFilter); delete _fileFilter; _fileFilter = nullptr; } else if (underlineFiles && (_fileFilter == nullptr)) { _fileFilter = new FileFilter(_session); _view->filterChain()->addFilter(_fileFilter); } bool underlineLinks = profile->underlineLinksEnabled(); if (!underlineLinks && (_urlFilter != nullptr)) { _view->filterChain()->removeFilter(_urlFilter); delete _urlFilter; _urlFilter = nullptr; } else if (underlineLinks && (_urlFilter == nullptr)) { _urlFilter = new UrlFilter(); _view->filterChain()->addFilter(_urlFilter); } } void SessionController::setSearchStartToWindowCurrentLine() { setSearchStartTo(-1); } void SessionController::setSearchStartTo(int line) { _searchStartLine = line; _prevSearchResultLine = line; } void SessionController::listenForScreenWindowUpdates() { if (_listenForScreenWindowUpdates) { return; } connect(_view->screenWindow(), &Konsole::ScreenWindow::outputChanged, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::scrolled, this, &Konsole::SessionController::updateSearchFilter); connect(_view->screenWindow(), &Konsole::ScreenWindow::currentResultLineChanged, _view.data(), static_cast(&Konsole::TerminalDisplay::update)); _listenForScreenWindowUpdates = true; } void SessionController::updateSearchFilter() { if ((_searchFilter != nullptr) && (!_searchBar.isNull())) { _view->processFilters(); } } void SessionController::searchBarEvent() { QString selectedText = _view->screenWindow()->selectedText(Screen::PreserveLineBreaks | Screen::TrimLeadingWhitespace | Screen::TrimTrailingWhitespace); if (!selectedText.isEmpty()) { _searchBar->setSearchText(selectedText); } if (_searchBar->isVisible()) { _searchBar->focusLineEdit(); } else { searchHistory(true); _isSearchBarEnabled = true; } } void SessionController::enableSearchBar(bool showSearchBar) { if (_searchBar.isNull()) { return; } if (showSearchBar && !_searchBar->isVisible()) { setSearchStartToWindowCurrentLine(); } _searchBar->setVisible(showSearchBar); if (showSearchBar) { connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); connect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); } else { disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchChanged, this, &Konsole::SessionController::searchTextChanged); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchReturnPressed, this, &Konsole::SessionController::findPreviousInHistory); disconnect(_searchBar.data(), &Konsole::IncrementalSearchBar::searchShiftPlusReturnPressed, this, &Konsole::SessionController::findNextInHistory); if ((!_view.isNull()) && (_view->screenWindow() != nullptr)) { _view->screenWindow()->setCurrentResultLine(-1); } } } bool SessionController::reverseSearchChecked() const { Q_ASSERT(_searchBar); QBitArray options = _searchBar->optionsChecked(); return options.at(IncrementalSearchBar::ReverseSearch); } QRegularExpression SessionController::regexpFromSearchBarOptions() const { QBitArray options = _searchBar->optionsChecked(); QString text(_searchBar->searchText()); QRegularExpression regExp; if (options.at(IncrementalSearchBar::RegExp)) { regExp.setPattern(text); } else { regExp.setPattern(QRegularExpression::escape(text)); } if (!options.at(IncrementalSearchBar::MatchCase)) { regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); } return regExp; } // searchHistory() may be called either as a result of clicking a menu item or // as a result of changing the search bar widget void SessionController::searchHistory(bool showSearchBar) { enableSearchBar(showSearchBar); if (!_searchBar.isNull()) { if (showSearchBar) { removeSearchFilter(); listenForScreenWindowUpdates(); _searchFilter = new RegExpFilter(); _searchFilter->setRegExp(regexpFromSearchBarOptions()); _view->filterChain()->addFilter(_searchFilter); _view->processFilters(); setFindNextPrevEnabled(true); } else { setFindNextPrevEnabled(false); removeSearchFilter(); _view->setFocus(Qt::ActiveWindowFocusReason); } } } void SessionController::setFindNextPrevEnabled(bool enabled) { _findNextAction->setEnabled(enabled); _findPreviousAction->setEnabled(enabled); } void SessionController::searchTextChanged(const QString& text) { Q_ASSERT(_view->screenWindow()); if (_searchText == text) { return; } _searchText = text; if (text.isEmpty()) { _view->screenWindow()->clearSelection(); _view->screenWindow()->scrollTo(_searchStartLine); } // update search. this is called even when the text is // empty to clear the view's filters beginSearch(text , reverseSearchChecked() ? Enum::BackwardsSearch : Enum::ForwardsSearch); } void SessionController::searchCompleted(bool success) { _prevSearchResultLine = _view->screenWindow()->currentResultLine(); if (!_searchBar.isNull()) { _searchBar->setFoundMatch(success); } } void SessionController::beginSearch(const QString& text, Enum::SearchDirection direction) { Q_ASSERT(_searchBar); Q_ASSERT(_searchFilter); QRegularExpression regExp = regexpFromSearchBarOptions(); _searchFilter->setRegExp(regExp); if (_searchStartLine < 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(), static_cast(&QPrintDialog::accepted), options, &Konsole::PrintOptions::saveSettings); if (dialog->exec() != QDialog::Accepted) { return; } QPainter painter; painter.begin(&printer); KConfigGroup configGroup(KSharedConfig::openConfig(), "PrintOptions"); if (configGroup.readEntry("ScaleOutput", true)) { double scale = qMin(printer.pageRect().width() / static_cast(_view->width()), printer.pageRect().height() / static_cast(_view->height())); painter.scale(scale, scale); } _view->printContent(painter, configGroup.readEntry("PrinterFriendly", true)); } void SessionController::saveHistory() { SessionTask* task = new SaveHistoryTask(this); task->setAutoDelete(true); task->addSession(_session); task->execute(); } void SessionController::clearHistory() { _session->clearHistory(); _view->updateImage(); // To reset view scrollbar _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 foreach (TerminalDisplay* view, session()->views()) { if (view != _view.data()) { view->updateReadOnlyState(isReadOnly()); } } } void SessionController::showDisplayContextMenu(const QPoint& position) { // needed to make sure the popup menu is available, even if a hosting // application did not merge our GUI. if (factory() == nullptr) { if (clientBuilder() == nullptr) { setClientBuilder(new KXMLGUIBuilder(_view)); } auto factory = new KXMLGUIFactory(clientBuilder(), this); factory->addClient(this); ////qDebug() << "Created xmlgui factory" << factory; } QPointer popup = qobject_cast(factory()->container(QStringLiteral("session-popup-menu"), this)); if (!popup.isNull()) { updateReadOnlyActionStates(); // prepend content-specific actions such as "Open Link", "Copy Email Address" etc. QList contentActions = _view->filterActions(position); auto contentSeparator = new QAction(popup); contentSeparator->setSeparator(true); contentActions << contentSeparator; popup->insertActions(popup->actions().value(0, nullptr), contentActions); // always update this submenu before showing the context menu, // because the available search services might have changed // since the context menu is shown last time updateWebSearchMenu(); _preventClose = true; if (_showMenuAction != nullptr) { if ( _showMenuAction->isChecked() ) { popup->removeAction( _showMenuAction); } else { popup->insertAction(_switchProfileMenu, _showMenuAction); } } QAction* chosen = popup->exec(_view->mapToGlobal(position)); // check for validity of the pointer to the popup menu if (!popup.isNull()) { // Remove content-specific actions // // If the close action was chosen, the popup menu will be partially // destroyed at this point, and the rest will be destroyed later by // 'chosen->trigger()' foreach(QAction * action, contentActions) { popup->removeAction(action); } delete contentSeparator; } _preventClose = false; if ((chosen != nullptr) && chosen->objectName() == QLatin1String("close-session")) { chosen->trigger(); } } else { qCDebug(KonsoleDebug) << "Unable to display popup menu for session" << _session->title(Session::NameRole) << ", no GUI factory available to build the popup."; } } void SessionController::movementKeyFromSearchBarReceived(QKeyEvent *event) { QCoreApplication::sendEvent(_view, event); setSearchStartToWindowCurrentLine(); } void SessionController::sessionStateChanged(int state) { if (state == _previousState) { return; } if (state == NOTIFYACTIVITY) { setIcon(*_activityIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYSILENCE) { setIcon(*_silenceIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYBELL) { setIcon(*_bellIcon); _keepIconUntilInteraction = true; } else if (state == NOTIFYNORMAL) { updateSessionIcon(); } _previousState = state; } void SessionController::zmodemDownload() { QString zmodem = QStandardPaths::findExecutable(QStringLiteral("rz")); if (zmodem.isEmpty()) { zmodem = QStandardPaths::findExecutable(QStringLiteral("lrz")); } if (!zmodem.isEmpty()) { const QString path = QFileDialog::getExistingDirectory(_view, i18n("Save ZModem Download to..."), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (!path.isEmpty()) { _session->startZModem(zmodem, path, QStringList()); return; } } else { KMessageBox::error(_view, i18n("

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

" "

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

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

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

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

No suitable ZModem software was found on this system.

" "

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

")); return; } QStringList files = QFileDialog::getOpenFileNames(_view, i18n("Select Files for ZModem Upload"), QDir::homePath()); if (!files.isEmpty()) { _session->startZModem(zmodem, QString(), files); } } bool SessionController::isKonsolePart() const { // Check to see if we are being called from Konsole or a KPart return !(qApp->applicationName() == QLatin1String("konsole")); } QString SessionController::userTitle() const { if (!_session.isNull()) { return _session->userTitle(); } else { return QString(); } } diff --git a/src/SessionController.h b/src/SessionController.h index 75cff877..2fdfe126 100644 --- a/src/SessionController.h +++ b/src/SessionController.h @@ -1,373 +1,371 @@ /* Copyright 2006-2008 by Robert Knight Copyright 2009 by Thomas Dreibholz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef SESSIONCONTROLLER_H #define SESSIONCONTROLLER_H // Qt -#include #include #include #include -#include #include // KDE #include // Konsole #include "ViewProperties.h" #include "Profile.h" #include "Enumeration.h" namespace KIO { class Job; } class QAction; class QTextCodec; class QKeyEvent; class QTimer; class QUrl; class KCodecAction; class KJob; class QAction; class KActionMenu; namespace Konsole { class Session; class SessionGroup; class ScreenWindow; class TerminalDisplay; class IncrementalSearchBar; class ProfileList; class RegExpFilter; class UrlFilter; class FileFilter; class EditProfileDialog; 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() Q_DECL_OVERRIDE; /** Returns the session associated with this controller */ QPointer session() { return _session; } /** Returns the view associated with this controller */ QPointer view() { return _view; } /** * Returns the "window title" of the associated session. */ QString userTitle() const; /** * Returns true if the controller is valid. * A valid controller is one which has a non-null session() and view(). * * Equivalent to "!session().isNull() && !view().isNull()" */ bool isValid() const; /** Set the start line from which the next search will be done **/ void setSearchStartTo(int line); /** set start line to the first or last line (depending on the reverse search * setting) in the terminal display **/ void setSearchStartToWindowCurrentLine(); /** * Sets the action displayed in the session's context menu to hide or * show the menu bar. */ void setShowMenuAction(QAction *action); EditProfileDialog *profileDialogPointer(); // reimplemented QUrl url() const Q_DECL_OVERRIDE; QString currentDir() const Q_DECL_OVERRIDE; void rename() Q_DECL_OVERRIDE; bool confirmClose() const Q_DECL_OVERRIDE; virtual bool confirmForceClose() const; // Reimplemented to watch for events happening to the view bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; /** Returns the set of all controllers that exist. */ static QSet allControllers() { return _allControllers; } /* Returns true if called within a KPart; false if called within Konsole. */ bool isKonsolePart() const; bool isReadOnly() const; Q_SIGNALS: /** * Emitted when the view associated with the controller is focused. * This can be used by other classes to plug the controller's actions into a window's * menus. */ void focused(SessionController *controller); void rawTitleChanged(); /** * Emitted when the current working directory of the session associated with * the controller is changed. */ void currentDirectoryChanged(const QString &dir); /** * Emitted when the user changes the tab title. */ void tabRenamedByUser(bool renamed) const; public Q_SLOTS: /** * Issues a command to the session to navigate to the specified URL. * This may not succeed if the foreground program does not understand * the command sent to it ( 'cd path' for local URLs ) or is not * responding to input. * * openUrl() currently supports urls for local paths and those * using the 'ssh' protocol ( eg. "ssh://joebloggs@hostname" ) */ void openUrl(const QUrl &url); /** * update actions which are meaningful only when primary screen is in use. */ void setupPrimaryScreenSpecificActions(bool use); /** * update actions which are closely related with the selected text. */ void selectionChanged(const QString &selectedText); /** * close the associated session. This might involve user interaction for * confirmation. */ void closeSession(); /** Increase font size */ void increaseFontSize(); /** Decrease font size */ void decreaseFontSize(); /** 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(Profile::Ptr profile); void handleWebShortcutAction(); void configureWebShortcuts(); void sendSignal(QAction *action); 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 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 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/ViewContainer.h b/src/ViewContainer.h index fc367a90..c960a99c 100644 --- a/src/ViewContainer.h +++ b/src/ViewContainer.h @@ -1,216 +1,213 @@ /* This file is part of the Konsole Terminal. Copyright 2006-2008 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 VIEWCONTAINER_H #define VIEWCONTAINER_H // Qt #include -#include -#include #include #include -#include // Konsole #include "Profile.h" #include "ViewManager.h" // Qt class QPoint; class QToolButton; class QMenu; class QDropEvent; class QDragEnterEvent; namespace Konsole { class IncrementalSearchBar; class ViewProperties; class ViewManager; class TabbedViewContainer; /** * An interface for container widgets which can hold one or more views. * * The container widget typically displays a list of the views which * it has and provides a means of switching between them. * * Subclasses should reimplement the addViewWidget() and removeViewWidget() functions * to actually add or remove view widgets from the container widget, as well * as updating any navigation aids. */ class KONSOLEPRIVATE_EXPORT TabbedViewContainer : public QTabWidget { Q_OBJECT public: /** * Constructs a new view container with the specified parent. * * @param connectedViewManager Connect the new view to this manager * @param parent The parent object of the container */ TabbedViewContainer(ViewManager *connectedViewManager, QWidget *parent); /** * Called when the ViewContainer is destroyed. When reimplementing this in * subclasses, use object->deleteLater() to delete any widgets or other objects * instead of 'delete object'. */ ~TabbedViewContainer() Q_DECL_OVERRIDE; /** Adds a new view to the container widget */ void addView(TerminalDisplay *view, int index = -1); /** Removes a view from the container */ void removeView(TerminalDisplay *view); void setTabActivity(int index, bool activity); void updateTitle(ViewProperties *item); void updateIcon(ViewProperties *item); void updateActivity(ViewProperties *item); /** Changes the active view to the next view */ void activateNextView(); /** Changes the active view to the previous view */ void activatePreviousView(); /** Changes the active view to the last view */ void activateLastView(); /** Changes the active view to the last used view */ void activateLastUsedView(bool reverse); void setCss(const QString& styleSheet = QString()); void setCssFromFile(const QUrl& url); /** * This enum describes the directions * in which views can be re-arranged within the container * using the moveActiveView() method. */ enum MoveDirection { /** Moves the view to the left. */ MoveViewLeft, /** Moves the view to the right. */ MoveViewRight }; /** * Moves the active view within the container and * updates the order in which the views are shown * in the container's navigation widget. * * The default implementation does nothing. */ void moveActiveView(MoveDirection direction); /** Sets the menu to be shown when the new view button is clicked. * Only valid if the QuickNewView feature is enabled. * The default implementation does nothing. */ // TODO: Reenable this later. // void setNewViewMenu(QMenu *menu); void renameTab(int index); ViewManager *connectedViewManager(); void currentTabChanged(int index); void closeCurrentTab(); void wheelScrolled(int delta); void tabDoubleClicked(int index); void openTabContextMenu(const QPoint &point); void setNavigationVisibility(ViewManager::NavigationVisibility navigationVisibility); void moveTabToWindow(int index, QWidget *window); /* return the widget(int index) casted to TerminalDisplay* * * The only thing that this class holds are TerminalDisplays, so * this is the only thing that should be used to retrieve widgets. */ TerminalDisplay *terminalAt(int index); Q_SIGNALS: /** Emitted when the container has no more children */ void empty(TabbedViewContainer *container); /** Emitted when the user requests to open a new view */ void newViewRequest(TabbedViewContainer *thisContainer); /** Requests creation of a new view, with the selected profile. */ void newViewWithProfileRequest(TabbedViewContainer *thisContainer, Profile::Ptr); /** * Emitted when the user requests to move a view from another container * into this container. If 'success' is set to true by a connected slot * then the original view will be removed. * * @param index Index at which to insert the new view in the container * or -1 to append it. This index should be passed to addView() when * the new view has been created. * @param sessionControllerId The identifier of the view. */ void moveViewRequest(int index, int sessionControllerId); /** Emitted when the active view changes */ void activeViewChanged(TerminalDisplay *view); /** Emitted when a view is added to the container. */ void viewAdded(TerminalDisplay *view); /** Emitted when a view is removed from the container. */ void viewRemoved(TerminalDisplay *view); /** detach the specific tab */ void detachTab(TabbedViewContainer *self, TerminalDisplay *activeView); protected: /** * Rearranges the order of widgets in the container. * * @param fromIndex Current index of the widget to move * @param toIndex New index for the widget */ void moveViewWidget(int fromIndex, int toIndex); // close tabs and unregister void closeTerminalTab(int idx); void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: void viewDestroyed(QObject *view); void konsoleConfigChanged(); private: void forgetView(TerminalDisplay *view); void updateTabHistory(TerminalDisplay *view, bool remove = false); QList _tabHistory; ViewManager *_connectedViewManager; QMenu *_contextPopupMenu; QToolButton *_newTabButton; QToolButton *_closeTabButton; int _contextMenuTabIndex; ViewManager::NavigationVisibility _navigationVisibility; int _tabHistoryIndex; }; } #endif //VIEWCONTAINER_H diff --git a/src/ViewManager.cpp b/src/ViewManager.cpp index 609e432d..93a2365e 100644 --- a/src/ViewManager.cpp +++ b/src/ViewManager.cpp @@ -1,1131 +1,1132 @@ /* Copyright 2006-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 "ViewManager.h" #include // Qt #include #include +#include // KDE #include #include #include #include // Konsole #include #include "ColorScheme.h" #include "ColorSchemeManager.h" #include "Session.h" #include "TerminalDisplay.h" #include "SessionController.h" #include "SessionManager.h" #include "ProfileManager.h" #include "ViewSplitter.h" #include "Enumeration.h" #include "ViewContainer.h" using namespace Konsole; int ViewManager::lastManagerId = 0; ViewManager::ViewManager(QObject *parent, KActionCollection *collection) : QObject(parent), _viewSplitter(nullptr), _pluggedController(nullptr), _sessionMap(QHash()), _actionCollection(collection), _navigationMethod(NoNavigation), _navigationVisibility(NavigationNotSet), _newTabBehavior(PutNewTabAtTheEnd), _managerId(0) { // create main view area _viewSplitter = new ViewSplitter(nullptr); KAcceleratorManager::setNoAccel(_viewSplitter); // the ViewSplitter class supports both recursive and non-recursive splitting, // in non-recursive mode, all containers are inserted into the same top-level splitter // widget, and all the divider lines between the containers have the same orientation // // the ViewManager class is not currently able to handle a ViewSplitter in recursive-splitting // mode _viewSplitter->setRecursiveSplitting(false); _viewSplitter->setFocusPolicy(Qt::NoFocus); // setup actions which are related to the views setupActions(); // emit a signal when all of the views held by this view manager are destroyed connect(_viewSplitter.data(), &Konsole::ViewSplitter::allContainersEmpty, this, &Konsole::ViewManager::empty); connect(_viewSplitter.data(), &Konsole::ViewSplitter::empty, this, &Konsole::ViewManager::empty); // listen for profile changes connect(ProfileManager::instance(), &Konsole::ProfileManager::profileChanged, this, &Konsole::ViewManager::profileChanged); connect(SessionManager::instance(), &Konsole::SessionManager::sessionUpdated, this, &Konsole::ViewManager::updateViewsForSession); //prepare DBus communication new WindowAdaptor(this); _managerId = ++lastManagerId; QDBusConnection::sessionBus().registerObject(QLatin1String("/Windows/") + QString::number(_managerId), this); _viewSplitter->addContainer(createContainer(), Qt::Vertical); } ViewManager::~ViewManager() = default; int ViewManager::managerId() const { return _managerId; } QWidget *ViewManager::activeView() const { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { return container->currentWidget(); } else { return nullptr; } } QWidget *ViewManager::widget() const { return _viewSplitter; } void ViewManager::setupActions() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; QAction *nextViewAction = new QAction(i18nc("@action Shortcut entry", "Next Tab"), this); QAction *previousViewAction = new QAction(i18nc("@action Shortcut entry", "Previous Tab"), this); QAction *lastViewAction = new QAction(i18nc("@action Shortcut entry", "Switch to Last Tab"), this); QAction *lastUsedViewAction = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs"), this); QAction *lastUsedViewReverseAction = new QAction(i18nc("@action Shortcut entry", "Last Used Tabs (Reverse)"), this); QAction *nextContainerAction = new QAction(i18nc("@action Shortcut entry", "Next View Container"), this); QAction *moveViewLeftAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Left"), this); QAction *moveViewRightAction = new QAction(i18nc("@action Shortcut entry", "Move Tab Right"), this); // list of actions that should only be enabled when there are multiple view // containers open QList multiViewOnlyActions; multiViewOnlyActions << nextContainerAction; QAction *splitLeftRightAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-left-right")), i18nc("@action:inmenu", "Split View Left/Right"), this); collection->setDefaultShortcut(splitLeftRightAction, Konsole::ACCEL + Qt::Key_ParenLeft); collection->addAction(QStringLiteral("split-view-left-right"), splitLeftRightAction); connect(splitLeftRightAction, &QAction::triggered, this, &Konsole::ViewManager::splitLeftRight); QAction *splitTopBottomAction = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18nc("@action:inmenu", "Split View Top/Bottom"), this); collection->setDefaultShortcut(splitTopBottomAction, Konsole::ACCEL + Qt::Key_ParenRight); collection->addAction(QStringLiteral("split-view-top-bottom"), splitTopBottomAction); connect(splitTopBottomAction, &QAction::triggered, this, &Konsole::ViewManager::splitTopBottom); QAction *closeActiveAction = new QAction(i18nc("@action:inmenu Close Active View", "Close Active"), this); closeActiveAction->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); collection->setDefaultShortcut(closeActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_X); closeActiveAction->setEnabled(false); collection->addAction(QStringLiteral("close-active-view"), closeActiveAction); connect(closeActiveAction, &QAction::triggered, this, &Konsole::ViewManager::closeActiveContainer); multiViewOnlyActions << closeActiveAction; QAction *closeOtherAction = new QAction(i18nc("@action:inmenu Close Other Views", "Close Others"), this); collection->setDefaultShortcut(closeOtherAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_O); closeOtherAction->setEnabled(false); collection->addAction(QStringLiteral("close-other-views"), closeOtherAction); connect(closeOtherAction, &QAction::triggered, this, &Konsole::ViewManager::closeOtherContainers); multiViewOnlyActions << closeOtherAction; // Expand & Shrink Active View QAction *expandActiveAction = new QAction(i18nc("@action:inmenu", "Expand View"), this); collection->setDefaultShortcut(expandActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); expandActiveAction->setEnabled(false); collection->addAction(QStringLiteral("expand-active-view"), expandActiveAction); connect(expandActiveAction, &QAction::triggered, this, &Konsole::ViewManager::expandActiveContainer); multiViewOnlyActions << expandActiveAction; QAction *shrinkActiveAction = new QAction(i18nc("@action:inmenu", "Shrink View"), this); collection->setDefaultShortcut(shrinkActiveAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); shrinkActiveAction->setEnabled(false); collection->addAction(QStringLiteral("shrink-active-view"), shrinkActiveAction); connect(shrinkActiveAction, &QAction::triggered, this, &Konsole::ViewManager::shrinkActiveContainer); multiViewOnlyActions << shrinkActiveAction; // Crashes on Mac. #if defined(ENABLE_DETACHING) QAction *detachViewAction = collection->addAction(QStringLiteral("detach-view")); detachViewAction->setEnabled(true); detachViewAction->setIcon(QIcon::fromTheme(QStringLiteral("tab-detach"))); detachViewAction->setText(i18nc("@action:inmenu", "D&etach Current Tab")); // Ctrl+Shift+D is not used as a shortcut by default because it is too close // to Ctrl+D - which will terminate the session in many cases collection->setDefaultShortcut(detachViewAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_H); connect(this, &Konsole::ViewManager::splitViewToggle, this, &Konsole::ViewManager::updateDetachViewState); connect(detachViewAction, &QAction::triggered, this, &Konsole::ViewManager::detachActiveView); #endif // Next / Previous View , Next Container collection->addAction(QStringLiteral("next-view"), nextViewAction); collection->addAction(QStringLiteral("previous-view"), previousViewAction); collection->addAction(QStringLiteral("last-tab"), lastViewAction); collection->addAction(QStringLiteral("last-used-tab"), lastUsedViewAction); collection->addAction(QStringLiteral("last-used-tab-reverse"), lastUsedViewReverseAction); collection->addAction(QStringLiteral("next-container"), nextContainerAction); collection->addAction(QStringLiteral("move-view-left"), moveViewLeftAction); collection->addAction(QStringLiteral("move-view-right"), moveViewRightAction); // Switch to tab N shortcuts const int SWITCH_TO_TAB_COUNT = 19; for (int i = 0; i < SWITCH_TO_TAB_COUNT; i++) { QAction *switchToTabAction = new QAction(i18nc("@action Shortcut entry", "Switch to Tab %1", i + 1), this); connect(switchToTabAction, &QAction::triggered, this, [this, i]() { switchToView(i); }); collection->addAction(QStringLiteral("switch-to-tab-%1").arg(i), switchToTabAction); } foreach (QAction *action, multiViewOnlyActions) { connect(this, &Konsole::ViewManager::splitViewToggle, action, &QAction::setEnabled); } // keyboard shortcut only actions const QList nextViewActionKeys{Qt::SHIFT + Qt::Key_Right, Qt::CTRL + Qt::Key_PageDown}; collection->setDefaultShortcuts(nextViewAction, nextViewActionKeys); connect(nextViewAction, &QAction::triggered, this, &Konsole::ViewManager::nextView); _viewSplitter->addAction(nextViewAction); const QList previousViewActionKeys{Qt::SHIFT + Qt::Key_Left, Qt::CTRL + Qt::Key_PageUp}; collection->setDefaultShortcuts(previousViewAction, previousViewActionKeys); connect(previousViewAction, &QAction::triggered, this, &Konsole::ViewManager::previousView); _viewSplitter->addAction(previousViewAction); collection->setDefaultShortcut(nextContainerAction, Qt::SHIFT + Qt::Key_Tab); connect(nextContainerAction, &QAction::triggered, this, &Konsole::ViewManager::nextContainer); _viewSplitter->addAction(nextContainerAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketLeft); #else collection->setDefaultShortcut(moveViewLeftAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Left); #endif connect(moveViewLeftAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewLeft); _viewSplitter->addAction(moveViewLeftAction); #ifdef Q_OS_MACOS collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_BracketRight); #else collection->setDefaultShortcut(moveViewRightAction, Konsole::ACCEL + Qt::SHIFT + Qt::Key_Right); #endif connect(moveViewRightAction, &QAction::triggered, this, &Konsole::ViewManager::moveActiveViewRight); _viewSplitter->addAction(moveViewRightAction); connect(lastViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastView); _viewSplitter->addAction(lastViewAction); collection->setDefaultShortcut(lastUsedViewAction, Qt::CTRL + Qt::Key_Tab); connect(lastUsedViewAction, &QAction::triggered, this, &Konsole::ViewManager::lastUsedView); _viewSplitter->addAction(lastUsedViewAction); collection->setDefaultShortcut(lastUsedViewReverseAction, Qt::CTRL + Qt::SHIFT + Qt::Key_Tab); connect(lastUsedViewReverseAction, &QAction::triggered, this, &Konsole::ViewManager::lastUsedViewReverse); _viewSplitter->addAction(lastUsedViewReverseAction); } void ViewManager::switchToView(int index) { _viewSplitter->activeContainer()->setCurrentIndex(index); } void ViewManager::updateDetachViewState() { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } const bool splitView = _viewSplitter->containers().count() >= 2; auto activeContainer = _viewSplitter->activeContainer(); const bool shouldEnable = splitView || ((activeContainer != nullptr) && activeContainer->count() >= 2); QAction *detachAction = _actionCollection->action(QStringLiteral("detach-view")); if ((detachAction != nullptr) && shouldEnable != detachAction->isEnabled()) { detachAction->setEnabled(shouldEnable); } } void ViewManager::moveActiveViewLeft() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewLeft); } void ViewManager::moveActiveViewRight() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->moveActiveView(TabbedViewContainer::MoveViewRight); } void ViewManager::nextContainer() { _viewSplitter->activateNextContainer(); } void ViewManager::nextView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateNextView(); } void ViewManager::previousView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activatePreviousView(); } void ViewManager::lastView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastView(); } void ViewManager::lastUsedView() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastUsedView(false); } void ViewManager::lastUsedViewReverse() { TabbedViewContainer *container = _viewSplitter->activeContainer(); Q_ASSERT(container); container->activateLastUsedView(true); } void ViewManager::detachActiveView() { // find the currently active view and remove it from its container TabbedViewContainer *container = _viewSplitter->activeContainer(); detachView(container, container->currentWidget()); } void ViewManager::detachView(TabbedViewContainer *container, QWidget *view) { #if !defined(ENABLE_DETACHING) return; #endif auto *viewToDetach = qobject_cast(view); if (viewToDetach == nullptr) { return; } // BR390736 - some instances are sending invalid session to viewDetached() Session *sessionToDetach = _sessionMap[viewToDetach]; if (sessionToDetach == nullptr) { return; } emit viewDetached(sessionToDetach); _sessionMap.remove(viewToDetach); // remove the view from this window container->removeView(viewToDetach); viewToDetach->deleteLater(); // if the container from which the view was removed is now empty then it can be deleted, // unless it is the only container in the window, in which case it is left empty // so that there is always an active container if (_viewSplitter->containers().count() > 1 && container->count() == 0) { removeContainer(container); } } void ViewManager::sessionFinished() { // if this slot is called after the view manager's main widget // has been destroyed, do nothing if (_viewSplitter.isNull()) { return; } auto *session = qobject_cast(sender()); Q_ASSERT(session); // close attached views QList children = _viewSplitter->findChildren(); foreach (TerminalDisplay *view, children) { if (_sessionMap[view] == session) { _sessionMap.remove(view); view->deleteLater(); } } // Only remove the controller from factory() if it's actually controlling // the session from the sender. // This fixes BUG: 348478 - messed up menus after a detached tab is closed if ((!_pluggedController.isNull()) && (_pluggedController->session() == session)) { // This is needed to remove this controller from factory() in // order to prevent BUG: 185466 - disappearing menu popup emit unplugController(_pluggedController); } } void ViewManager::viewActivated(QWidget *view) { Q_ASSERT(view != nullptr); // focus the activated view, this will cause the SessionController // to notify the world that the view has been focused and the appropriate UI // actions will be plugged in. view->setFocus(Qt::OtherFocusReason); } void ViewManager::splitLeftRight() { splitView(Qt::Horizontal); } void ViewManager::splitTopBottom() { splitView(Qt::Vertical); } void ViewManager::splitView(Qt::Orientation orientation) { TabbedViewContainer *container = createContainer(); if (_viewSplitter->activeContainer()->count()) { // get the currently applied profile and use it to create the new tab. auto *activeContainer= _viewSplitter->activeContainer(); auto *currentDisplay = qobject_cast(activeContainer->currentWidget()); auto profile = SessionManager::instance()->sessionProfile(_sessionMap[currentDisplay]); // Create a new session with the selected profile. auto *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(session, container, 0); } _viewSplitter->addContainer(container, orientation); emit splitViewToggle(_viewSplitter->containers().count() > 0); // focus the new container container->currentWidget()->setFocus(); // ensure that the active view is focused after the split / unsplit TabbedViewContainer *activeContainer = _viewSplitter->activeContainer(); QWidget *activeView = activeContainer != nullptr ? activeContainer->currentWidget() : nullptr; if (activeView != nullptr) { activeView->setFocus(Qt::OtherFocusReason); } } void ViewManager::removeContainer(TabbedViewContainer *container) { // remove session map entries for views in this container for(int i = 0, end = container->count(); i < end; i++) { auto view = container->widget(i); auto *display = qobject_cast(view); Q_ASSERT(display); _sessionMap.remove(display); } _viewSplitter->removeContainer(container); container->deleteLater(); emit splitViewToggle(_viewSplitter->containers().count() > 1); } void ViewManager::expandActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), 10); } void ViewManager::shrinkActiveContainer() { _viewSplitter->adjustContainerSize(_viewSplitter->activeContainer(), -10); } void ViewManager::closeActiveContainer() { // only do something if there is more than one container active if (_viewSplitter->containers().count() > 1) { TabbedViewContainer *container = _viewSplitter->activeContainer(); removeContainer(container); // focus next container so that user can continue typing // without having to manually focus it themselves nextContainer(); } } void ViewManager::closeOtherContainers() { TabbedViewContainer *active = _viewSplitter->activeContainer(); foreach (TabbedViewContainer *container, _viewSplitter->containers()) { if (container != active) { removeContainer(container); } } } SessionController *ViewManager::createController(Session *session, TerminalDisplay *view) { // create a new controller for the session, and ensure that this view manager // is notified when the view gains the focus auto controller = new SessionController(session, view, this); connect(controller, &Konsole::SessionController::focused, this, &Konsole::ViewManager::controllerChanged); connect(session, &Konsole::Session::destroyed, controller, &Konsole::SessionController::deleteLater); connect(session, &Konsole::Session::primaryScreenInUse, controller, &Konsole::SessionController::setupPrimaryScreenSpecificActions); connect(session, &Konsole::Session::selectionChanged, controller, &Konsole::SessionController::selectionChanged); connect(view, &Konsole::TerminalDisplay::destroyed, controller, &Konsole::SessionController::deleteLater); // if this is the first controller created then set it as the active controller if (_pluggedController.isNull()) { controllerChanged(controller); } return controller; } void ViewManager::controllerChanged(SessionController *controller) { if (controller == _pluggedController) { return; } _viewSplitter->setFocusProxy(controller->view()); _pluggedController = controller; emit activeViewChanged(controller); } SessionController *ViewManager::activeViewController() const { return _pluggedController; } void ViewManager::createView(Session *session, TabbedViewContainer *container, int index) { // notify this view manager when the session finishes so that its view // can be deleted // // Use Qt::UniqueConnection to avoid duplicate connection connect(session, &Konsole::Session::finished, this, &Konsole::ViewManager::sessionFinished, Qt::UniqueConnection); TerminalDisplay *display = createTerminalDisplay(session); const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); applyProfileToView(display, profile); // set initial size const QSize &preferredSize = session->preferredSize(); display->setSize(preferredSize.width(), preferredSize.height()); createController(session, display); _sessionMap[display] = session; container->addView(display, index); session->addView(display); // tell the session whether it has a light or dark background session->setDarkBackground(colorSchemeForProfile(profile)->hasDarkBackground()); container->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); updateDetachViewState(); } void ViewManager::createView(TabbedViewContainer *tabWidget, Session *session) { const int index = _newTabBehavior == PutNewTabAfterCurrentTab ? _viewSplitter->activeContainer()->currentIndex() + 1 : -1; createView(session, tabWidget, index); } TabbedViewContainer *ViewManager::createContainer() { auto *container = new TabbedViewContainer(this, _viewSplitter); container->setNavigationVisibility(_navigationVisibility); //TODO: Fix Detaching. connect(container, &TabbedViewContainer::detachTab, this, &ViewManager::detachView); // connect signals and slots connect(container, &Konsole::TabbedViewContainer::viewAdded, this, [this, container]() { containerViewsChanged(container); }); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, [this, container]() { containerViewsChanged(container); }); connect(container, &TabbedViewContainer::newViewRequest, this, &ViewManager::newViewRequest); connect(container, &Konsole::TabbedViewContainer::newViewWithProfileRequest, this, &Konsole::ViewManager::newViewWithProfileRequest); connect(container, &Konsole::TabbedViewContainer::moveViewRequest, this, &Konsole::ViewManager::containerMoveViewRequest); connect(container, &Konsole::TabbedViewContainer::viewRemoved, this, &Konsole::ViewManager::viewDestroyed); connect(container, &Konsole::TabbedViewContainer::activeViewChanged, this, &Konsole::ViewManager::viewActivated); return container; } void ViewManager::containerMoveViewRequest(int index, int id) { auto *container = qobject_cast(sender()); auto *controller = qobject_cast(ViewProperties::propertiesById(id)); Q_ASSERT(container); Q_ASSERT(controller); createView(controller->session(), container, index); controller->session()->refresh(); container->currentWidget()->setFocus(); } void ViewManager::setNavigationMethod(NavigationMethod method) { Q_ASSERT(_actionCollection); if (_actionCollection == nullptr) { return; } KActionCollection *collection = _actionCollection; _navigationMethod = method; // FIXME: The following disables certain actions for the KPart that it // doesn't actually have a use for, to avoid polluting the action/shortcut // namespace of an application using the KPart (otherwise, a shortcut may // be in use twice, and the user gets to see an "ambiguous shortcut over- // load" error dialog). However, this approach sucks - it's the inverse of // what it should be. Rather than disabling actions not used by the KPart, // a method should be devised to only enable those that are used, perhaps // by using a separate action collection. const bool enable = (method != NoNavigation); auto enableAction = [&enable, &collection](const QString& actionName) { auto *action = collection->action(actionName); if (action != nullptr) { action->setEnabled(enable); } }; enableAction(QStringLiteral("next-view")); enableAction(QStringLiteral("previous-view")); enableAction(QStringLiteral("last-tab")); enableAction(QStringLiteral("last-used-tab")); enableAction(QStringLiteral("last-used-tab-reverse")); enableAction(QStringLiteral("split-view-left-right")); enableAction(QStringLiteral("split-view-top-bottom")); enableAction(QStringLiteral("rename-session")); enableAction(QStringLiteral("move-view-left")); enableAction(QStringLiteral("move-view-right")); } ViewManager::NavigationMethod ViewManager::navigationMethod() const { return _navigationMethod; } void ViewManager::containerViewsChanged(TabbedViewContainer *container) { if ((!_viewSplitter.isNull()) && container == _viewSplitter->activeContainer()) { emit viewPropertiesChanged(viewProperties()); } } void ViewManager::viewDestroyed(QWidget *view) { // Note: the received QWidget has already been destroyed, so // using dynamic_cast<> or qobject_cast<> does not work here // We only need the pointer address to look it up below auto *display = reinterpret_cast(view); // 1. detach view from session // 2. if the session has no views left, close it Session *session = _sessionMap[ display ]; _sessionMap.remove(display); if (session != nullptr) { if (session->views().count() == 0) { session->close(); } } //we only update the focus if the splitter is still alive if (!_viewSplitter.isNull()) { updateDetachViewState(); } // The below causes the menus to be messed up // Only happens when using the tab bar close button // if (_pluggedController) // emit unplugController(_pluggedController); } TerminalDisplay *ViewManager::createTerminalDisplay(Session *session) { auto display = new TerminalDisplay(nullptr); display->setRandomSeed(session->sessionId() * 31); return display; } const ColorScheme *ViewManager::colorSchemeForProfile(const Profile::Ptr profile) { const ColorScheme *colorScheme = ColorSchemeManager::instance()-> findColorScheme(profile->colorScheme()); if (colorScheme == nullptr) { colorScheme = ColorSchemeManager::instance()->defaultColorScheme(); } Q_ASSERT(colorScheme); return colorScheme; } bool ViewManager::profileHasBlurEnabled(const Profile::Ptr profile) { return colorSchemeForProfile(profile)->blur(); } void ViewManager::applyProfileToView(TerminalDisplay *view, const Profile::Ptr profile) { Q_ASSERT(profile); emit updateWindowIcon(); // load color scheme ColorEntry table[TABLE_COLORS]; const ColorScheme *colorScheme = colorSchemeForProfile(profile); colorScheme->getColorTable(table, view->randomSeed()); view->setColorTable(table); view->setOpacity(colorScheme->opacity()); view->setWallpaper(colorScheme->wallpaper()); emit blurSettingChanged(colorScheme->blur()); // load font view->setAntialias(profile->antiAliasFonts()); view->setBoldIntense(profile->boldIntense()); view->setUseFontLineCharacters(profile->useFontLineCharacters()); view->setVTFont(profile->font()); // set scroll-bar position view->setScrollBarPosition(Enum::ScrollBarPositionEnum(profile->property(Profile::ScrollBarPosition))); view->setScrollFullPage(profile->property(Profile::ScrollFullPage)); // show hint about terminal size after resizing view->setShowTerminalSizeHint(profile->showTerminalSizeHint()); view->setDimWhenInactive(profile->dimWhenInactive()); // terminal features view->setBlinkingCursorEnabled(profile->blinkingCursorEnabled()); view->setBlinkingTextEnabled(profile->blinkingTextEnabled()); view->setTripleClickMode(Enum::TripleClickModeEnum(profile->property(Profile::TripleClickMode))); view->setAutoCopySelectedText(profile->autoCopySelectedText()); view->setControlDrag(profile->property(Profile::CtrlRequiredForDrag)); view->setDropUrlsAsText(profile->property(Profile::DropUrlsAsText)); view->setBidiEnabled(profile->bidiRenderingEnabled()); view->setLineSpacing(profile->lineSpacing()); view->setTrimLeadingSpaces(profile->property(Profile::TrimLeadingSpacesInSelectedText)); view->setTrimTrailingSpaces(profile->property(Profile::TrimTrailingSpacesInSelectedText)); view->setOpenLinksByDirectClick(profile->property(Profile::OpenLinksByDirectClickEnabled)); view->setUrlHintsModifiers(profile->property(Profile::UrlHintsModifiers)); view->setReverseUrlHintsEnabled(profile->property(Profile::ReverseUrlHints)); view->setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum(profile->property(Profile::MiddleClickPasteMode))); view->setCopyTextAsHTML(profile->property(Profile::CopyTextAsHTML)); // margin/center view->setMargin(profile->property(Profile::TerminalMargin)); view->setCenterContents(profile->property(Profile::TerminalCenter)); // cursor shape view->setKeyboardCursorShape(Enum::CursorShapeEnum(profile->property(Profile::CursorShape))); // cursor color // an invalid QColor is used to inform the view widget to // draw the cursor using the default color( matching the text) view->setKeyboardCursorColor(profile->useCustomCursorColor() ? profile->customCursorColor() : QColor()); // word characters view->setWordCharacters(profile->wordCharacters()); // bell mode view->setBellMode(profile->property(Profile::BellMode)); // mouse wheel zoom view->setMouseWheelZoom(profile->mouseWheelZoomEnabled()); view->setAlternateScrolling(profile->property(Profile::AlternateScrolling)); } void ViewManager::updateViewsForSession(Session *session) { const Profile::Ptr profile = SessionManager::instance()->sessionProfile(session); const QList sessionMapKeys = _sessionMap.keys(session); foreach (TerminalDisplay *view, sessionMapKeys) { applyProfileToView(view, profile); } } void ViewManager::profileChanged(Profile::Ptr profile) { // update all views associated with this profile QHashIterator iter(_sessionMap); while (iter.hasNext()) { iter.next(); // if session uses this profile, update the display if (iter.key() != nullptr && iter.value() != nullptr && SessionManager::instance()->sessionProfile(iter.value()) == profile) { applyProfileToView(iter.key(), profile); } } } QList ViewManager::viewProperties() const { QList list; TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container == nullptr) { return {}; } list.reserve(container->count()); for(int i = 0, end = container->count(); i < end; i++) { auto view = container->terminalAt(i); ViewProperties *properties = view->sessionController(); Q_ASSERT(properties); list << properties; } return list; } void ViewManager::saveSessions(KConfigGroup &group) { // find all unique session restore IDs QList ids; QSet unique; int tab = 1; TabbedViewContainer *container = _viewSplitter->activeContainer(); // first: sessions in the active container, preserving the order Q_ASSERT(container); if (container == nullptr) { return; } ids.reserve(container->count()); auto *activeview = qobject_cast(container->currentWidget()); for (int i = 0, end = container->count(); i < end; i++) { auto *view = qobject_cast(container->widget(i)); Q_ASSERT(view); Session *session = _sessionMap[view]; ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); if (view == activeview) { group.writeEntry("Active", tab); } tab++; } // second: all other sessions, in random order // we don't want to have sessions restored that are not connected foreach (Session *session, _sessionMap) { if (!unique.contains(session)) { ids << SessionManager::instance()->getRestoreId(session); unique.insert(session); } } group.writeEntry("Sessions", ids); } TabbedViewContainer *ViewManager::activeContainer() { return _viewSplitter->activeContainer(); } void ViewManager::restoreSessions(const KConfigGroup &group) { QList ids = group.readEntry("Sessions", QList()); int activeTab = group.readEntry("Active", 0); TerminalDisplay *display = nullptr; int tab = 1; foreach (int id, ids) { Session *session = SessionManager::instance()->idToSession(id); if (session == nullptr) { qWarning() << "Unable to load session with id" << id; // Force a creation of a default session below ids.clear(); break; } createView(activeContainer(), session); if (!session->isRunning()) { session->run(); } if (tab++ == activeTab) { display = qobject_cast(activeView()); } } if (display != nullptr) { _viewSplitter->activeContainer()->setCurrentWidget(display); display->setFocus(Qt::OtherFocusReason); } if (ids.isEmpty()) { // Session file is unusable, start default Profile Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); createView(activeContainer(), session); if (!session->isRunning()) { session->run(); } } } int ViewManager::sessionCount() { return _sessionMap.size(); } QStringList ViewManager::sessionList() { QStringList ids; QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { ids.append(QString::number(i.value()->sessionId())); } return ids; } int ViewManager::currentSession() { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.key()->isVisible()) { return i.value()->sessionId(); } } return -1; } void ViewManager::setCurrentSession(int sessionId) { QHash::const_iterator i; for (i = _sessionMap.constBegin(); i != _sessionMap.constEnd(); ++i) { if (i.value()->sessionId() == sessionId) { TabbedViewContainer *container = _viewSplitter->activeContainer(); if (container != nullptr) { container->setCurrentWidget(i.key()); } } } } int ViewManager::newSession() { Profile::Ptr profile = ProfileManager::instance()->defaultProfile(); Session *session = SessionManager::instance()->createSession(profile); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(activeContainer(), session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(activeContainer(), session); session->run(); return session->sessionId(); } int ViewManager::newSession(const QString &profile, const QString &directory) { const QList profilelist = ProfileManager::instance()->allProfiles(); Profile::Ptr profileptr = ProfileManager::instance()->defaultProfile(); for (const auto &i : profilelist) { if (i->name() == profile) { profileptr = i; break; } } Session *session = SessionManager::instance()->createSession(profileptr); session->setInitialWorkingDirectory(directory); session->addEnvironmentEntry(QStringLiteral("KONSOLE_DBUS_WINDOW=/Windows/%1").arg(managerId())); createView(activeContainer(), session); session->run(); return session->sessionId(); } QString ViewManager::defaultProfile() { return ProfileManager::instance()->defaultProfile()->name(); } QStringList ViewManager::profileList() { return ProfileManager::instance()->availableProfileNames(); } void ViewManager::nextSession() { nextView(); } void ViewManager::prevSession() { previousView(); } void ViewManager::moveSessionLeft() { moveActiveViewLeft(); } void ViewManager::moveSessionRight() { moveActiveViewRight(); } void ViewManager::setTabWidthToText(bool setTabWidthToText) { for(auto container : _viewSplitter->containers()) { container->tabBar()->setExpanding(!setTabWidthToText); container->tabBar()->update(); } } void ViewManager::setNavigationVisibility(NavigationVisibility navigationVisibility) { if (_navigationVisibility != navigationVisibility) { _navigationVisibility = navigationVisibility; for(auto *container : _viewSplitter->containers()) { container->setNavigationVisibility(navigationVisibility); } } } void ViewManager::setNavigationBehavior(int behavior) { _newTabBehavior = static_cast(behavior); } diff --git a/src/ViewProperties.h b/src/ViewProperties.h index 340bf499..c128605c 100644 --- a/src/ViewProperties.h +++ b/src/ViewProperties.h @@ -1,129 +1,128 @@ /* 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 #include // Konsole #include "konsoleprivate_export.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() Q_DECL_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); 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/main.cpp b/src/main.cpp index bcd54ee9..de678d1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,373 +1,374 @@ /* Copyright 2006-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 "Application.h" #include "MainWindow.h" #include "config-konsole.h" //krazy:exclude=includes #include "KonsoleSettings.h" // OS specific #include +#include #include #include #include // KDE #include #include #include #include #include #include using Konsole::Application; // fill the KAboutData structure with information about contributors to Konsole. void fillAboutData(KAboutData &aboutData); // check and report whether this konsole instance should use a new konsole // process, or re-use an existing konsole process. bool shouldUseNewProcess(int argc, char *argv[]); // restore sessions saved by KDE. void restoreSession(Application &app); // Workaround for a bug in KDBusService: https://bugs.kde.org/show_bug.cgi?id=355545 // It calls exit(), but the program can't exit before the QApplication is deleted: // https://bugreports.qt.io/browse/QTBUG-48709 static bool needToDeleteQApplication = false; void deleteQApplication() { if (needToDeleteQApplication) { delete qApp; } } // *** // Entry point into the Konsole terminal application. // *** extern "C" int Q_DECL_EXPORT kdemain(int argc, char *argv[]) { // Check if any of the arguments makes it impossible to re-use an existing process. // We need to do this manually and before creating a QApplication, because // QApplication takes/removes the Qt specific arguments that are incompatible. KDBusService::StartupOption startupOption = KDBusService::Unique; if (shouldUseNewProcess(argc, argv)) { startupOption = KDBusService::Multiple; } else { needToDeleteQApplication = true; } #if defined(Q_OS_LINUX) && (QT_VERSION < QT_VERSION_CHECK(5, 11, 2)) // Workaround for https://bugreports.qt.io/browse/QTBUG-48344 // See also https://bugs.kde.org/show_bug.cgi?id=230184 // The Qt glib event loop doesn't let timers deliver events if there are a // lot of other events. const QByteArray qtUseGLibOld = qgetenv("QT_NO_GLIB"); qputenv("QT_NO_GLIB", "1"); #endif auto app = new QApplication(argc, argv); #if defined(Q_OS_LINUX) && (QT_VERSION < QT_VERSION_CHECK(5, 11, 2)) if (qtUseGLibOld.isNull()) { qunsetenv("QT_NO_GLIB"); } else { qputenv("QT_NO_GLIB", qtUseGLibOld); } #endif // enable high dpi support app->setAttribute(Qt::AA_UseHighDpiPixmaps, true); #if defined(Q_OS_MACOS) // this ensures that Ctrl and Meta are not swapped, so CTRL-C and friends // will work correctly in the terminal app->setAttribute(Qt::AA_MacDontSwapCtrlAndMeta); // KDE's menuBar()->isTopLevel() hasn't worked in a while. // For now, put menus inside Konsole window; this also make // the keyboard shortcut to show menus look reasonable. app->setAttribute(Qt::AA_DontUseNativeMenuBar); #endif KLocalizedString::setApplicationDomain("konsole"); KAboutData about(QStringLiteral("konsole"), i18nc("@title", "Konsole"), QStringLiteral(KONSOLE_VERSION), i18nc("@title", "Terminal emulator"), KAboutLicense::GPL_V2, i18nc("@info:credit", "(c) 1997-2017, The Konsole Developers"), QString(), QStringLiteral("https://konsole.kde.org/")); fillAboutData(about); KAboutData::setApplicationData(about); KCrash::initialize(); QSharedPointer parser(new QCommandLineParser); parser->setApplicationDescription(about.shortDescription()); parser->addHelpOption(); parser->addVersionOption(); about.setupCommandLine(parser.data()); QStringList args = app->arguments(); QStringList customCommand = Application::getCustomCommand(args); Application::populateCommandLineParser(parser.data()); parser->process(args); about.processCommandLine(parser.data()); // Enable user to force multiple instances, unless a new tab is requested if (!Konsole::KonsoleSettings::useSingleInstance() && !parser->isSet(QStringLiteral("new-tab"))) { startupOption = KDBusService::Multiple; } atexit(deleteQApplication); // Ensure that we only launch a new instance if we need to // If there is already an instance running, we will quit here KDBusService dbusService(startupOption | KDBusService::NoExitOnFailure); needToDeleteQApplication = false; Kdelibs4ConfigMigrator migrate(QStringLiteral("konsole")); migrate.setConfigFiles(QStringList() << QStringLiteral("konsolerc") << QStringLiteral("konsole.notifyrc")); migrate.setUiFiles(QStringList() << QStringLiteral("sessionui.rc") << QStringLiteral("partui.rc") << QStringLiteral("konsoleui.rc")); if (migrate.migrate()) { Kdelibs4Migration dataMigrator; const QString sourceBasePath = dataMigrator.saveLocation("data", QStringLiteral("konsole")); const QString targetBasePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/konsole/"); QString targetFilePath; QDir sourceDir(sourceBasePath); QDir targetDir(targetBasePath); if (sourceDir.exists()) { if (!targetDir.exists()) { QDir().mkpath(targetBasePath); } QStringList fileNames = sourceDir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach (const QString &fileName, fileNames) { targetFilePath = targetBasePath + fileName; if (!QFile::exists(targetFilePath)) { QFile::copy(sourceBasePath + fileName, targetFilePath); } } } } // If we reach this location, there was no existing copy of Konsole // running, so create a new instance. Application konsoleApp(parser, customCommand); // The activateRequested() signal is emitted when a second instance // of Konsole is started. QObject::connect(&dbusService, &KDBusService::activateRequested, &konsoleApp, &Application::slotActivateRequested); if (app->isSessionRestored()) { restoreSession(konsoleApp); } else { // Do not finish starting Konsole due to: // 1. An argument was given to just printed info // 2. An invalid situation occurred const bool continueStarting = (konsoleApp.newInstance() != 0); if (!continueStarting) { delete app; return 0; } } // Since we've allocated the QApplication on the heap for the KDBusService workaround, // we need to delete it manually before returning from main(). int ret = app->exec(); delete app; return ret; } bool shouldUseNewProcess(int argc, char *argv[]) { // The "unique process" model of konsole is incompatible with some or all // Qt/KDE options. When those incompatible options are given, konsole must // use new process // // TODO: make sure the existing list is OK and add more incompatible options. // We need to manually parse the arguments because QApplication removes the // Qt specific arguments (like --reverse) QStringList arguments; arguments.reserve(argc); for (int i = 0; i < argc; i++) { arguments.append(QString::fromLocal8Bit(argv[i])); } // take Qt options into consideration QStringList qtProblematicOptions; qtProblematicOptions << QStringLiteral("--session") << QStringLiteral("--name") << QStringLiteral("--reverse") << QStringLiteral("--stylesheet") << QStringLiteral("--graphicssystem"); #if HAVE_X11 qtProblematicOptions << QStringLiteral("--display") << QStringLiteral("--visual"); #endif foreach (const QString &option, qtProblematicOptions) { if (arguments.contains(option)) { return true; } } // take KDE options into consideration QStringList kdeProblematicOptions; kdeProblematicOptions << QStringLiteral("--config") << QStringLiteral("--style"); #if HAVE_X11 kdeProblematicOptions << QStringLiteral("--waitforwm"); #endif foreach (const QString &option, kdeProblematicOptions) { if (arguments.contains(option)) { return true; } } // if users have explictly requested starting a new process // Support --nofork to retain argument compatibility with older // versions. if (arguments.contains(QStringLiteral("--separate")) || arguments.contains(QStringLiteral("--nofork"))) { return true; } // the only way to create new tab is to reuse existing Konsole process. if (arguments.contains(QStringLiteral("--new-tab"))) { return false; } // when starting Konsole from a terminal, a new process must be used // so that the current environment is propagated into the shells of the new // Konsole and any debug output or warnings from Konsole are written to // the current terminal bool hasControllingTTY = false; const int fd = QT_OPEN("/dev/tty", O_RDONLY); if (fd != -1) { hasControllingTTY = true; close(fd); } return hasControllingTTY; } void fillAboutData(KAboutData &aboutData) { aboutData.setProgramIconName(QStringLiteral("utilities-terminal")); aboutData.setOrganizationDomain("kde.org"); aboutData.addAuthor(i18nc("@info:credit", "Kurt Hindenburg"), i18nc("@info:credit", "General maintainer, bug fixes and general" " improvements"), QStringLiteral("kurt.hindenburg@gmail.com")); aboutData.addAuthor(i18nc("@info:credit", "Robert Knight"), i18nc("@info:credit", "Previous maintainer, ported to KDE4"), QStringLiteral("robertknight@gmail.com")); aboutData.addAuthor(i18nc("@info:credit", "Lars Doelle"), i18nc("@info:credit", "Original author"), QStringLiteral("lars.doelle@on-line.de")); aboutData.addCredit(i18nc("@info:credit", "Jekyll Wu"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("adaptee@gmail.com")); aboutData.addCredit(i18nc("@info:credit", "Waldo Bastian"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("bastian@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Stephan Binner"), i18nc("@info:credit", "Bug fixes and general improvements"), QStringLiteral("binner@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Thomas Dreibholz"), i18nc("@info:credit", "General improvements"), QStringLiteral("dreibh@iem.uni-due.de")); aboutData.addCredit(i18nc("@info:credit", "Chris Machemer"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("machey@ceinetworks.com")); aboutData.addCredit(i18nc("@info:credit", "Francesco Cecconi"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("francesco.cecconi@gmail.com")); aboutData.addCredit(i18nc("@info:credit", "Stephan Kulow"), i18nc("@info:credit", "Solaris support and history"), QStringLiteral("coolo@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Alexander Neundorf"), i18nc("@info:credit", "Bug fixes and improved startup performance"), QStringLiteral("neundorf@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Peter Silva"), i18nc("@info:credit", "Marking improvements"), QStringLiteral("Peter.A.Silva@gmail.com")); aboutData.addCredit(i18nc("@info:credit", "Lotzi Boloni"), i18nc("@info:credit", "Embedded Konsole\n" "Toolbar and session names"), QStringLiteral("boloni@cs.purdue.edu")); aboutData.addCredit(i18nc("@info:credit", "David Faure"), i18nc("@info:credit", "Embedded Konsole\n" "General improvements"), QStringLiteral("faure@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Antonio Larrosa"), i18nc("@info:credit", "Visual effects"), QStringLiteral("larrosa@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Matthias Ettrich"), i18nc("@info:credit", "Code from the kvt project\n" "General improvements"), QStringLiteral("ettrich@kde.org")); aboutData.addCredit(i18nc("@info:credit", "Warwick Allison"), i18nc("@info:credit", "Schema and text selection improvements"), QStringLiteral("warwick@troll.no")); aboutData.addCredit(i18nc("@info:credit", "Dan Pilone"), i18nc("@info:credit", "SGI port"), QStringLiteral("pilone@slac.com")); aboutData.addCredit(i18nc("@info:credit", "Kevin Street"), i18nc("@info:credit", "FreeBSD port"), QStringLiteral("street@iname.com")); aboutData.addCredit(i18nc("@info:credit", "Sven Fischer"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("herpes@kawo2.renditionwth-aachen.de")); aboutData.addCredit(i18nc("@info:credit", "Dale M. Flaven"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("dflaven@netport.com")); aboutData.addCredit(i18nc("@info:credit", "Martin Jones"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("mjones@powerup.com.au")); aboutData.addCredit(i18nc("@info:credit", "Lars Knoll"), i18nc("@info:credit", "Bug fixes"), QStringLiteral("knoll@mpi-hd.mpg.de")); aboutData.addCredit(i18nc("@info:credit", "Thanks to many others.\n")); } void restoreSession(Application &app) { int n = 1; while (KMainWindow::canBeRestored(n)) { app.newMainWindow()->restore(n++); } }