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"