File indexing completed on 2024-04-21 05:51:24

0001 /*
0002     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 // Own
0008 #include "Part.h"
0009 
0010 // Qt
0011 #include <QDir>
0012 #include <QKeyEvent>
0013 #include <QMetaEnum>
0014 #include <QStringList>
0015 #include <QUrl>
0016 
0017 // KDE
0018 #include <KActionCollection>
0019 #include <KConfigDialog>
0020 #include <KLocalizedString>
0021 #include <KPluginFactory>
0022 #include <QAction>
0023 
0024 // Konsole
0025 #include "Emulation.h"
0026 #include "KonsoleSettings.h"
0027 #include "ViewManager.h"
0028 #include "profile/ProfileManager.h"
0029 #include "session/SessionController.h"
0030 #include "session/SessionManager.h"
0031 #include "settings/PartInfo.h"
0032 #include "settings/ProfileSettings.h"
0033 #include "terminalDisplay/TerminalDisplay.h"
0034 #include "widgets/EditProfileDialog.h"
0035 #include "widgets/ViewContainer.h"
0036 
0037 using namespace Konsole;
0038 
0039 K_PLUGIN_FACTORY_WITH_JSON(KonsolePartFactory, "konsolepart.json", registerPlugin<Konsole::Part>();)
0040 
0041 Part::Part(QObject *parent, const QVariantList &)
0042     : KParts::ReadOnlyPart(parent)
0043     , _viewManager(nullptr)
0044     , _pluggedController(nullptr)
0045 {
0046     // create view widget
0047     _viewManager = new ViewManager(this, actionCollection());
0048     _viewManager->setNavigationMethod(ViewManager::NoNavigation);
0049 
0050     connect(_viewManager, &Konsole::ViewManager::activeViewChanged, this, &Konsole::Part::activeViewChanged);
0051     connect(_viewManager, &Konsole::ViewManager::empty, this, &Konsole::Part::terminalExited);
0052     connect(_viewManager, &Konsole::ViewManager::newViewRequest, this, &Konsole::Part::newTab);
0053 
0054     _viewManager->widget()->setParent(widget());
0055 
0056     setWidget(_viewManager->widget());
0057     actionCollection()->addAssociatedWidget(_viewManager->widget());
0058     const QList<QAction *> actionsList = actionCollection()->actions();
0059     for (QAction *action : actionsList) {
0060         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0061     }
0062 
0063     // Enable translucency support if supported by the app.
0064     if (_viewManager->widget()->window() && _viewManager->widget()->window()->testAttribute(Qt::WA_TranslucentBackground)) {
0065         _viewManager->widget()->setAttribute(Qt::WA_TranslucentBackground, true);
0066     }
0067 
0068     // create basic session
0069     createSession();
0070 }
0071 
0072 Part::~Part() = default;
0073 
0074 bool Part::openFile()
0075 {
0076     return false;
0077 }
0078 
0079 void Part::terminalExited()
0080 {
0081     deleteLater();
0082 }
0083 
0084 void Part::newTab()
0085 {
0086     createSession();
0087 }
0088 
0089 Session *Part::activeSession() const
0090 {
0091     if (_viewManager->activeViewController() != nullptr) {
0092         Q_ASSERT(_viewManager->activeViewController()->session());
0093 
0094         return _viewManager->activeViewController()->session();
0095     }
0096     return nullptr;
0097 }
0098 
0099 void Part::startProgram(const QString &program, const QStringList &arguments)
0100 {
0101     Q_ASSERT(activeSession());
0102 
0103     // do nothing if the session has already started running
0104     if (activeSession()->isRunning()) {
0105         return;
0106     }
0107 
0108     if (!program.isEmpty() && !arguments.isEmpty()) {
0109         activeSession()->setProgram(program);
0110         activeSession()->setArguments(arguments);
0111     }
0112 
0113     activeSession()->run();
0114 }
0115 
0116 void Part::openTeletype(int ptyMasterFd, bool runShell)
0117 {
0118     Q_ASSERT(activeSession());
0119 
0120     activeSession()->openTeletype(ptyMasterFd, runShell);
0121 }
0122 
0123 void Part::showShellInDir(const QString &dir)
0124 {
0125     Q_ASSERT(activeSession());
0126 
0127     // do nothing if the session has already started running
0128     if (activeSession()->isRunning()) {
0129         return;
0130     }
0131 
0132     // All other checking is done in setInitialWorkingDirectory()
0133     if (!dir.isEmpty()) {
0134         activeSession()->setInitialWorkingDirectory(dir);
0135     }
0136 
0137     activeSession()->run();
0138 }
0139 
0140 void Part::sendInput(const QString &text)
0141 {
0142     Q_ASSERT(activeSession());
0143     activeSession()->sendTextToTerminal(text);
0144 }
0145 
0146 int Part::terminalProcessId()
0147 {
0148     Q_ASSERT(activeSession());
0149 
0150     return activeSession()->processId();
0151 }
0152 
0153 int Part::foregroundProcessId()
0154 {
0155     Q_ASSERT(activeSession());
0156 
0157     if (activeSession()->isForegroundProcessActive()) {
0158         return activeSession()->foregroundProcessId();
0159     }
0160     return -1;
0161 }
0162 
0163 QString Part::foregroundProcessName()
0164 {
0165     Q_ASSERT(activeSession());
0166 
0167     if (activeSession()->isForegroundProcessActive()) {
0168         return activeSession()->foregroundProcessName();
0169     }
0170     return QString();
0171 }
0172 
0173 QString Part::currentWorkingDirectory() const
0174 {
0175     Q_ASSERT(activeSession());
0176 
0177     return activeSession()->currentWorkingDirectory();
0178 }
0179 
0180 QVariant Part::profileProperty(const QString &profileProperty) const
0181 {
0182     const auto metaEnum = QMetaEnum::fromType<Profile::Property>();
0183     const auto value = metaEnum.keyToValue(profileProperty.toStdString().c_str());
0184 
0185     if (value == -1) {
0186         return QString();
0187     }
0188 
0189     const auto p = static_cast<Profile::Property>(value);
0190     return SessionManager::instance()->sessionProfile(activeSession())->property<QVariant>(p);
0191 }
0192 
0193 QStringList Part::availableProfiles() const
0194 {
0195     return ProfileManager::instance()->availableProfileNames();
0196 }
0197 
0198 QString Part::currentProfileName() const
0199 {
0200     return SessionManager::instance()->sessionProfile(activeSession())->name();
0201 }
0202 
0203 bool Part::setCurrentProfile(const QString &profileName)
0204 {
0205     Profile::Ptr profile;
0206     for (auto p : ProfileManager::instance()->allProfiles()) {
0207         if (p->name() == profileName) {
0208             profile = p;
0209             break;
0210         }
0211     }
0212 
0213     if (!profile) {
0214         profile = ProfileManager::instance()->loadProfile(profileName);
0215     }
0216 
0217     SessionManager::instance()->setSessionProfile(activeSession(), profile);
0218     return currentProfileName() == profileName;
0219 }
0220 
0221 void Part::createSession(const QString &profileName, const QString &directory)
0222 {
0223     Profile::Ptr profile = ProfileManager::instance()->defaultProfile();
0224     if (!profileName.isEmpty()) {
0225         profile = ProfileManager::instance()->loadProfile(profileName);
0226     }
0227 
0228     Q_ASSERT(profile);
0229 
0230     Session *session = SessionManager::instance()->createSession(profile);
0231 
0232     // override the default directory specified in the profile
0233     if (!directory.isEmpty() && profile->startInCurrentSessionDir()) {
0234         session->setInitialWorkingDirectory(directory);
0235     }
0236 
0237     auto newView = _viewManager->createView(session);
0238     _viewManager->activeContainer()->addView(newView);
0239 }
0240 
0241 void Part::activeViewChanged(SessionController *controller)
0242 {
0243     Q_ASSERT(controller);
0244     Q_ASSERT(controller->view());
0245 
0246     // remove existing controller
0247     if (_pluggedController != nullptr) {
0248         removeChildClient(_pluggedController);
0249         disconnect(_pluggedController, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged);
0250         disconnect(_pluggedController, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged);
0251     }
0252 
0253     // insert new controller
0254     insertChildClient(controller);
0255 
0256     connect(controller, &Konsole::SessionController::titleChanged, this, &Konsole::Part::activeViewTitleChanged);
0257     activeViewTitleChanged(controller);
0258     connect(controller, &Konsole::SessionController::currentDirectoryChanged, this, &Konsole::Part::currentDirectoryChanged);
0259 
0260     disconnect(controller->view(), &TerminalDisplay::overrideShortcutCheck, this, &Part::overrideTerminalShortcut);
0261     connect(controller->view(), &TerminalDisplay::overrideShortcutCheck, this, &Part::overrideTerminalShortcut);
0262 
0263     _pluggedController = controller;
0264 }
0265 
0266 void Part::overrideTerminalShortcut(QKeyEvent *event, bool &override)
0267 {
0268     // Shift+Insert is commonly used as the alternate shortcut for
0269     // pasting in KDE apps(including konsole), so it deserves some
0270     // special treatment.
0271     if (((event->modifiers() & Qt::ShiftModifier) != 0U) && (event->key() == Qt::Key_Insert)) {
0272         override = false;
0273         return;
0274     }
0275 
0276     // override all shortcuts in the embedded terminal by default
0277     override = true;
0278     Q_EMIT overrideShortcut(event, override);
0279 }
0280 
0281 void Part::activeViewTitleChanged(ViewProperties *properties)
0282 {
0283     Q_EMIT setWindowCaption(properties->title());
0284 }
0285 
0286 void Part::showManageProfilesDialog(QWidget *parent)
0287 {
0288     // Make sure this string is unique among all users of this part
0289     if (KConfigDialog::showDialog(QStringLiteral("konsolepartmanageprofiles"))) {
0290         return;
0291     }
0292 
0293     KConfigDialog *settingsDialog = new KConfigDialog(parent, QStringLiteral("konsolepartmanageprofiles"), KonsoleSettings::self());
0294     settingsDialog->setFaceType(KPageDialog::Tabbed);
0295 
0296     auto profileSettings = new ProfileSettings(settingsDialog);
0297     settingsDialog->addPage(profileSettings, i18nc("@title Preferences page name", "Profiles"), QStringLiteral("configure"));
0298 
0299     auto partInfoSettings = new PartInfoSettings(settingsDialog);
0300     settingsDialog->addPage(partInfoSettings, i18nc("@title Preferences page name", "Part Info"), QStringLiteral("dialog-information"));
0301 
0302     settingsDialog->show();
0303 }
0304 
0305 void Part::showEditCurrentProfileDialog(QWidget *parent)
0306 {
0307     Q_ASSERT(activeSession());
0308 
0309     auto dialog = new EditProfileDialog(parent);
0310     dialog->setAttribute(Qt::WA_DeleteOnClose);
0311     dialog->setProfile(SessionManager::instance()->sessionProfile(activeSession()));
0312     dialog->show();
0313 }
0314 
0315 void Part::changeSessionSettings(const QString &text)
0316 {
0317     Q_ASSERT(activeSession());
0318 
0319     // send a profile change command, the escape code format
0320     // is the same as the normal X-Term commands used to change the window title or icon,
0321     // but with a magic value of '50' for the parameter which specifies what to change
0322     QString command = QStringLiteral("\033]50;%1\a").arg(text);
0323 
0324     sendInput(command);
0325 }
0326 
0327 // Konqueror integration
0328 bool Part::openUrl(const QUrl &url)
0329 {
0330     if (KParts::ReadOnlyPart::url() == url) {
0331         Q_EMIT completed();
0332         return true;
0333     }
0334 
0335     setUrl(url);
0336     Q_EMIT setWindowCaption(url.toDisplayString(QUrl::PreferLocalFile));
0337     ////qDebug() << "Set Window Caption to " << url.pathOrUrl();
0338     Q_EMIT started(nullptr);
0339 
0340     if (url.isLocalFile()) {
0341         showShellInDir(url.path());
0342     } else {
0343         showShellInDir(QDir::homePath());
0344     }
0345 
0346     Q_EMIT completed();
0347     return true;
0348 }
0349 
0350 void Part::setMonitorSilenceEnabled(bool enabled)
0351 {
0352     Q_ASSERT(activeSession());
0353 
0354     if (enabled) {
0355         activeSession()->setMonitorSilence(true);
0356         connect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged, Qt::UniqueConnection);
0357     } else {
0358         activeSession()->setMonitorSilence(false);
0359         if (!activeSession()->isMonitorActivity()) {
0360             disconnect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged);
0361         }
0362     }
0363 }
0364 
0365 void Part::setMonitorActivityEnabled(bool enabled)
0366 {
0367     Q_ASSERT(activeSession());
0368 
0369     if (enabled) {
0370         activeSession()->setMonitorActivity(true);
0371         connect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged, Qt::UniqueConnection);
0372     } else {
0373         activeSession()->setMonitorActivity(false);
0374         if (!activeSession()->isMonitorSilence()) {
0375             disconnect(activeSession(), &Konsole::Session::notificationsChanged, this, &Konsole::Part::notificationChanged);
0376         }
0377     }
0378 }
0379 
0380 bool Part::isBlurEnabled()
0381 {
0382     return ViewManager::profileHasBlurEnabled(SessionManager::instance()->sessionProfile(activeSession()));
0383 }
0384 
0385 void Part::notificationChanged(Session::Notification notification, bool enabled)
0386 {
0387     if (notification == Session::Notification::Silence && enabled) {
0388         Q_EMIT silenceDetected();
0389     } else if (notification == Session::Notification::Activity && enabled) {
0390         Q_EMIT activityDetected();
0391     }
0392 }
0393 
0394 #include "Part.moc"
0395 #include "moc_Part.cpp"