File indexing completed on 2024-05-05 05:57:01

0001 /*
0002   SPDX-FileCopyrightText: 2008-2014 Eike Hein <hein@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "terminal.h"
0008 #include "settings.h"
0009 
0010 #include <KActionCollection>
0011 #include <KColorScheme>
0012 #include <KLocalizedString>
0013 #include <KParts/PartLoader>
0014 #include <KXMLGUIBuilder>
0015 #include <KXMLGUIFactory>
0016 #include <kde_terminal_interface.h>
0017 
0018 #include <QAction>
0019 #include <QApplication>
0020 #include <QHBoxLayout>
0021 #include <QLabel>
0022 #include <QWidget>
0023 
0024 #include <QKeyEvent>
0025 
0026 int Terminal::m_availableTerminalId = 0;
0027 
0028 Terminal::Terminal(const QString &workingDir, QWidget *parent)
0029     : QObject(nullptr)
0030 {
0031     m_terminalId = m_availableTerminalId;
0032     m_availableTerminalId++;
0033     m_parentSplitter = parent;
0034 
0035     KPluginMetaData part(QStringLiteral("kf6/parts/konsolepart"));
0036 
0037     m_part = KParts::PartLoader::instantiatePart<KParts::Part>(part, parent).plugin;
0038 
0039     if (m_part) {
0040         connect(m_part, SIGNAL(setWindowCaption(QString)), this, SLOT(setTitle(QString)));
0041         connect(m_part, SIGNAL(overrideShortcut(QKeyEvent *, bool &)), this, SLOT(overrideShortcut(QKeyEvent *, bool &)));
0042         connect(m_part, &KParts::Part::destroyed, this, [this] {
0043             m_part = nullptr;
0044 
0045             if (!m_destroying) {
0046                 Q_EMIT closeRequested(m_terminalId);
0047             }
0048         });
0049 
0050         m_partWidget = m_part->widget();
0051 
0052         m_terminalWidget = m_part->widget()->focusWidget();
0053 
0054         if (m_terminalWidget) {
0055             m_terminalWidget->setFocusPolicy(Qt::WheelFocus);
0056             m_terminalWidget->installEventFilter(this);
0057 
0058             if (!m_part->factory() && m_partWidget) {
0059                 if (!m_part->clientBuilder()) {
0060                     m_part->setClientBuilder(new KXMLGUIBuilder(m_partWidget));
0061                 }
0062 
0063                 auto factory = new KXMLGUIFactory(m_part->clientBuilder(), this);
0064                 factory->addClient(m_part);
0065 
0066                 // Prevents the KXMLGui warning about removing the client
0067                 connect(m_partWidget, &QObject::destroyed, this, [factory, this] {
0068                     factory->removeClient(m_part);
0069                 });
0070             }
0071         }
0072 
0073         disableOffendingPartActions();
0074 
0075         m_terminalInterface = qobject_cast<TerminalInterface *>(m_part);
0076 
0077         if (!m_terminalInterface) {
0078             qFatal("Version of Konsole is outdated. Konsole didn't return a valid TerminalInterface.");
0079             return;
0080         }
0081 
0082         bool startInWorkingDir = m_terminalInterface->profileProperty(QStringLiteral("StartInCurrentSessionDir")).toBool();
0083         if (startInWorkingDir && !workingDir.isEmpty()) {
0084             m_terminalInterface->showShellInDir(workingDir);
0085         }
0086 
0087         QMetaObject::invokeMethod(m_part, "isBlurEnabled", Qt::DirectConnection, Q_RETURN_ARG(bool, m_wantsBlur));
0088 
0089         // Remove shortcut from close action because it conflicts with the shortcut from out own close action
0090         // https://bugs.kde.org/show_bug.cgi?id=319172
0091         const auto childClients = m_part->childClients();
0092         for (const auto childClient : childClients) {
0093             QAction *closeSessionAction = childClient->actionCollection()->action(QStringLiteral("close-session"));
0094             if (closeSessionAction) {
0095                 closeSessionAction->setShortcut(QKeySequence());
0096             }
0097         }
0098     } else
0099         displayKPartLoadError();
0100 }
0101 
0102 Terminal::~Terminal()
0103 {
0104     m_destroying = true;
0105     // The ownership of m_part is a mess
0106     // When the terminal exits, e.g. the user pressed Ctrl+D, the part deletes itself
0107     // When we close a terminal we need to delete the part ourselves
0108     if (m_part) {
0109         delete m_part;
0110     }
0111 }
0112 
0113 bool Terminal::eventFilter(QObject * /* watched */, QEvent *event)
0114 {
0115     if (event->type() == QEvent::FocusIn) {
0116         Q_EMIT activated(m_terminalId);
0117 
0118         QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
0119 
0120         if (focusEvent->reason() == Qt::MouseFocusReason || focusEvent->reason() == Qt::OtherFocusReason || focusEvent->reason() == Qt::BacktabFocusReason)
0121             Q_EMIT manuallyActivated(this);
0122     } else if (event->type() == QEvent::MouseMove) {
0123         if (Settings::focusFollowsMouse() && m_terminalWidget && !m_terminalWidget->hasFocus())
0124             m_terminalWidget->setFocus();
0125     }
0126 
0127     if (!m_keyboardInputEnabled) {
0128         if (event->type() == QEvent::KeyPress) {
0129             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0130 
0131             if (keyEvent->modifiers() == Qt::NoModifier)
0132                 Q_EMIT keyboardInputBlocked(this);
0133 
0134             return true;
0135         } else if (event->type() == QEvent::KeyRelease)
0136             return true;
0137     }
0138 
0139     return false;
0140 }
0141 
0142 void Terminal::displayKPartLoadError()
0143 {
0144     KColorScheme colorScheme(QPalette::Active);
0145     QColor warningColor = colorScheme.background(KColorScheme::NeutralBackground).color();
0146     QColor warningColorLight = KColorScheme::shade(warningColor, KColorScheme::LightShade, 0.1);
0147     QString gradient = QStringLiteral("qlineargradient(x1:0, y1:0, x2:0, y2:1,stop: 0 %1, stop: 0.6 %1, stop: 1.0 %2)");
0148     gradient = gradient.arg(warningColor.name(), warningColorLight.name());
0149     QString styleSheet = QStringLiteral("QLabel { background: %1; }");
0150 
0151     QWidget *widget = new QWidget(m_parentSplitter);
0152     widget->setStyleSheet(styleSheet.arg(gradient));
0153     m_partWidget = widget;
0154     m_terminalWidget = widget;
0155     m_terminalWidget->setFocusPolicy(Qt::WheelFocus);
0156     m_terminalWidget->installEventFilter(this);
0157 
0158     QLabel *label = new QLabel(widget);
0159     label->setContentsMargins(10, 10, 10, 10);
0160     label->setWordWrap(false);
0161     label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0162     label->setTextInteractionFlags(Qt::TextSelectableByMouse);
0163     label->setText(xi18nc("@info",
0164                           "<application>Yakuake</application> was unable to load "
0165                           "the <application>Konsole</application> component.<nl/> "
0166                           "A <application>Konsole</application> installation is "
0167                           "required to use Yakuake."));
0168 
0169     QLabel *icon = new QLabel(widget);
0170     icon->setContentsMargins(10, 10, 10, 10);
0171     icon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(QSize(48, 48)));
0172     icon->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0173 
0174     QHBoxLayout *layout = new QHBoxLayout(widget);
0175     layout->addWidget(icon);
0176     layout->addWidget(label);
0177     layout->setSpacing(0);
0178     layout->setContentsMargins(0, 0, 0, 0);
0179     layout->setStretchFactor(icon, 1);
0180     layout->setStretchFactor(label, 5);
0181 }
0182 
0183 void Terminal::disableOffendingPartActions()
0184 {
0185     // This is an unwelcome stop-gap that will be removed once we can
0186     // count on a Konsole version that doesn't pollute a KPart user's
0187     // shortcut "namespace".
0188 
0189     if (!m_part)
0190         return;
0191 
0192     KActionCollection *actionCollection = m_part->actionCollection();
0193 
0194     if (actionCollection) {
0195         QAction *action = nullptr;
0196 
0197         action = actionCollection->action(QStringLiteral("next-view"));
0198         if (action)
0199             action->setEnabled(false);
0200 
0201         action = actionCollection->action(QStringLiteral("previous-view"));
0202         if (action)
0203             action->setEnabled(false);
0204 
0205         action = actionCollection->action(QStringLiteral("close-active-view"));
0206         if (action)
0207             action->setEnabled(false);
0208 
0209         action = actionCollection->action(QStringLiteral("split-view-left-right"));
0210         if (action)
0211             action->setEnabled(false);
0212 
0213         action = actionCollection->action(QStringLiteral("split-view-top-bottom"));
0214         if (action)
0215             action->setEnabled(false);
0216 
0217         action = actionCollection->action(QStringLiteral("rename-session"));
0218         if (action)
0219             action->setEnabled(false);
0220 
0221         action = actionCollection->action(QStringLiteral("enlarge-font"));
0222         if (action)
0223             action->setEnabled(false);
0224 
0225         action = actionCollection->action(QStringLiteral("shrink-font"));
0226         if (action)
0227             action->setEnabled(false);
0228     }
0229 }
0230 
0231 void Terminal::setTitle(const QString &title)
0232 {
0233     m_title = title;
0234 
0235     Q_EMIT titleChanged(m_terminalId, m_title);
0236 }
0237 
0238 void Terminal::runCommand(const QString &command)
0239 {
0240     m_terminalInterface->sendInput(command + QStringLiteral("\n"));
0241 }
0242 
0243 void Terminal::manageProfiles()
0244 {
0245     QMetaObject::invokeMethod(m_part, "showManageProfilesDialog", Qt::QueuedConnection, Q_ARG(QWidget *, QApplication::activeWindow()));
0246 }
0247 
0248 void Terminal::editProfile()
0249 {
0250     QMetaObject::invokeMethod(m_part, "showEditCurrentProfileDialog", Qt::QueuedConnection, Q_ARG(QWidget *, QApplication::activeWindow()));
0251 }
0252 
0253 void Terminal::overrideShortcut(QKeyEvent * /* event */, bool &override)
0254 {
0255     override = false;
0256 }
0257 
0258 void Terminal::setMonitorActivityEnabled(bool enabled)
0259 {
0260     m_monitorActivityEnabled = enabled;
0261 
0262     if (enabled) {
0263         connect(m_part, SIGNAL(activityDetected()), this, SLOT(activityDetected()), Qt::UniqueConnection);
0264 
0265         QMetaObject::invokeMethod(m_part, "setMonitorActivityEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
0266     } else {
0267         disconnect(m_part, SIGNAL(activityDetected()), this, SLOT(activityDetected()));
0268 
0269         QMetaObject::invokeMethod(m_part, "setMonitorActivityEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
0270     }
0271 }
0272 
0273 void Terminal::setMonitorSilenceEnabled(bool enabled)
0274 {
0275     m_monitorSilenceEnabled = enabled;
0276 
0277     if (enabled) {
0278         connect(m_part, SIGNAL(silenceDetected()), this, SLOT(silenceDetected()), Qt::UniqueConnection);
0279 
0280         QMetaObject::invokeMethod(m_part, "setMonitorSilenceEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
0281     } else {
0282         disconnect(m_part, SIGNAL(silenceDetected()), this, SLOT(silenceDetected()));
0283 
0284         QMetaObject::invokeMethod(m_part, "setMonitorSilenceEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
0285     }
0286 }
0287 
0288 void Terminal::activityDetected()
0289 {
0290     Q_EMIT activityDetected(this);
0291 }
0292 
0293 void Terminal::silenceDetected()
0294 {
0295     Q_EMIT silenceDetected(this);
0296 }
0297 
0298 QString Terminal::currentWorkingDirectory() const
0299 {
0300     return m_terminalInterface->currentWorkingDirectory();
0301 }
0302 
0303 KActionCollection *Terminal::actionCollection()
0304 {
0305     if (m_part->factory()) {
0306         const auto guiClients = m_part->childClients();
0307         for (auto *client : guiClients) {
0308             if (client->actionCollection()->associatedWidgets().contains(m_terminalWidget)) {
0309                 return client->actionCollection();
0310             }
0311         }
0312     }
0313 
0314     return nullptr;
0315 }
0316 
0317 #include "moc_terminal.cpp"