File indexing completed on 2024-04-28 04:38:34
0001 /* 0002 SPDX-FileCopyrightText: 1999-2001 John Birch <jbb@kdevelop.org> 0003 SPDX-FileCopyrightText: 2001 Bernd Gehrmann <bernd@kdevelop.org> 0004 SPDX-FileCopyrightText: 2006 Vladimir Prus <ghost@cs.msu.su> 0005 SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org> 0006 SPDX-FileCopyrightText: 2016 Aetf <aetf@unlimitedcodeworks.xyz> 0007 0008 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0009 */ 0010 0011 #include "midebuggerplugin.h" 0012 0013 #include "midebugjobs.h" 0014 #include "dialogs/processselection.h" 0015 0016 #include <interfaces/context.h> 0017 #include <interfaces/contextmenuextension.h> 0018 #include <interfaces/icore.h> 0019 #include <interfaces/idebugcontroller.h> 0020 #include <interfaces/iruncontroller.h> 0021 #include <interfaces/iuicontroller.h> 0022 #include <language/interfaces/editorcontext.h> 0023 #include <sublime/message.h> 0024 #include <isession.h> 0025 0026 #include <KActionCollection> 0027 #include <KLocalizedString> 0028 #include <KMessageBox> 0029 #include <KMessageBox_KDevCompat> 0030 #include <KParts/MainWindow> 0031 #include <KStringHandler> 0032 0033 #include <QAction> 0034 #include <QApplication> 0035 #include <QDBusConnection> 0036 #include <QDBusConnectionInterface> 0037 #include <QDBusInterface> 0038 #include <QDBusServiceWatcher> 0039 #include <QPointer> 0040 #include <QTimer> 0041 0042 using namespace KDevelop; 0043 using namespace KDevMI; 0044 0045 class KDevMI::DBusProxy : public QObject 0046 { 0047 Q_OBJECT 0048 0049 public: 0050 DBusProxy(const QString& service, const QString& name, QObject* parent) 0051 : QObject(parent), 0052 m_dbusInterface(service, QStringLiteral("/debugger")), 0053 m_name(name), m_valid(true) 0054 {} 0055 0056 ~DBusProxy() override 0057 { 0058 if (m_valid) { 0059 m_dbusInterface.call(QStringLiteral("debuggerClosed"), m_name); 0060 } 0061 } 0062 0063 QDBusInterface* interface() 0064 { 0065 return &m_dbusInterface; 0066 } 0067 0068 void Invalidate() 0069 { 0070 m_valid = false; 0071 } 0072 0073 public Q_SLOTS: 0074 void debuggerAccepted(const QString& name) 0075 { 0076 if (name == m_name) { 0077 emit debugProcess(this); 0078 } 0079 } 0080 0081 void debuggingFinished() 0082 { 0083 m_dbusInterface.call(QStringLiteral("debuggingFinished"), m_name); 0084 } 0085 0086 Q_SIGNALS: 0087 void debugProcess(DBusProxy*); 0088 0089 private: 0090 QDBusInterface m_dbusInterface; 0091 QString m_name; 0092 bool m_valid; 0093 }; 0094 0095 MIDebuggerPlugin::MIDebuggerPlugin(const QString &componentName, const QString& displayName, QObject *parent) 0096 : KDevelop::IPlugin(componentName, parent), m_displayName(displayName) 0097 { 0098 core()->debugController()->initializeUi(); 0099 0100 setupActions(); 0101 setupDBus(); 0102 } 0103 0104 void MIDebuggerPlugin::setupActions() 0105 { 0106 KActionCollection* ac = actionCollection(); 0107 0108 auto * action = new QAction(this); 0109 action->setIcon(QIcon::fromTheme(QStringLiteral("core"))); 0110 action->setText(i18nc("@action", "Examine Core File with %1", m_displayName)); 0111 action->setWhatsThis(i18nc("@info:whatsthis", 0112 "<b>Examine core file</b>" 0113 "<p>This loads a core file, which is typically created " 0114 "after the application has crashed, e.g. with a " 0115 "segmentation fault. The core file contains an " 0116 "image of the program memory at the time it crashed, " 0117 "allowing you to do a post-mortem analysis.</p>")); 0118 connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotExamineCore); 0119 ac->addAction(QStringLiteral("debug_core"), action); 0120 0121 #if HAVE_KSYSGUARD 0122 action = new QAction(this); 0123 action->setIcon(QIcon::fromTheme(QStringLiteral("connect_creating"))); 0124 action->setText(i18nc("@action", "Attach to Process with %1", m_displayName)); 0125 action->setWhatsThis(i18nc("@info:whatsthis", 0126 "<b>Attach to process</b>" 0127 "<p>Attaches the debugger to a running process.</p>")); 0128 connect(action, &QAction::triggered, this, &MIDebuggerPlugin::slotAttachProcess); 0129 ac->addAction(QStringLiteral("debug_attach"), action); 0130 #endif 0131 } 0132 0133 void MIDebuggerPlugin::setupDBus() 0134 { 0135 auto serviceRegistered = [this](const QString& service) { 0136 if (m_drkonqis.contains(service)) 0137 return; 0138 // New registration 0139 const QString name = i18n("KDevelop (%1) - %2", m_displayName, core()->activeSession()->name()); 0140 auto drkonqiProxy = new DBusProxy(service, name, this); 0141 m_drkonqis.insert(service, drkonqiProxy); 0142 connect(drkonqiProxy->interface(), SIGNAL(acceptDebuggingApplication(QString)), 0143 drkonqiProxy, SLOT(debuggerAccepted(QString))); 0144 connect(drkonqiProxy, &DBusProxy::debugProcess, 0145 this, &MIDebuggerPlugin::slotDebugExternalProcess); 0146 0147 drkonqiProxy->interface()->call(QStringLiteral("registerDebuggingApplication"), name, QCoreApplication::applicationPid()); 0148 }; 0149 auto serviceUnregistered = [this](const QString& service) { 0150 // Deregistration 0151 if (auto* proxy = m_drkonqis.take(service)) { 0152 proxy->Invalidate(); 0153 delete proxy; 0154 } 0155 }; 0156 0157 m_watcher = new QDBusServiceWatcher(QStringLiteral("org.kde.drkonqi*"), QDBusConnection::sessionBus(), 0158 QDBusServiceWatcher::WatchForOwnerChange, this); 0159 connect(m_watcher, &QDBusServiceWatcher::serviceRegistered, this, serviceRegistered); 0160 connect(m_watcher, &QDBusServiceWatcher::serviceUnregistered, this, serviceUnregistered); 0161 0162 auto registeredServiceNames = QDBusConnection::sessionBus().interface()->registeredServiceNames(); 0163 if (!registeredServiceNames.isValid()) { 0164 return; 0165 } 0166 for (const auto &serviceName : registeredServiceNames.value()) { 0167 if (serviceName.startsWith(QStringLiteral("org.kde.drkonqi."))) { 0168 serviceRegistered(serviceName); 0169 } 0170 } 0171 } 0172 0173 void MIDebuggerPlugin::unload() 0174 { 0175 qDeleteAll(m_drkonqis.values()); 0176 m_drkonqis.clear(); 0177 unloadToolViews(); 0178 } 0179 0180 MIDebuggerPlugin::~MIDebuggerPlugin() { } 0181 0182 void MIDebuggerPlugin::slotDebugExternalProcess(DBusProxy* proxy) 0183 { 0184 QDBusReply<int> reply = proxy->interface()->call(QStringLiteral("pid")); 0185 if (reply.isValid()) { 0186 connect(attachProcess(reply.value()), &KJob::result, 0187 proxy, &DBusProxy::debuggingFinished); 0188 } 0189 0190 if (auto* mainWindow = core()->uiController()->activeMainWindow()) { 0191 mainWindow->raise(); 0192 } 0193 } 0194 0195 ContextMenuExtension MIDebuggerPlugin::contextMenuExtension(Context* context, QWidget* parent) 0196 { 0197 ContextMenuExtension menuExt = IPlugin::contextMenuExtension(context, parent); 0198 0199 if (context->type() != KDevelop::Context::EditorContext) 0200 return menuExt; 0201 0202 auto *econtext = dynamic_cast<EditorContext*>(context); 0203 if (!econtext) 0204 return menuExt; 0205 0206 QString contextIdent = econtext->currentWord(); 0207 0208 if (!contextIdent.isEmpty()) 0209 { 0210 QString squeezed = KStringHandler::csqueeze(contextIdent, 30); 0211 0212 auto* action = new QAction(parent); 0213 action->setText(i18nc("@action:inmenu", "Evaluate: %1", squeezed)); 0214 action->setWhatsThis(i18nc("@info:whatsthis", 0215 "<b>Evaluate expression</b>" 0216 "<p>Shows the value of the expression under the cursor.</p>")); 0217 connect(action, &QAction::triggered, this, [this, contextIdent](){ 0218 emit addWatchVariable(contextIdent); 0219 }); 0220 menuExt.addAction(ContextMenuExtension::DebugGroup, action); 0221 0222 action = new QAction(parent); 0223 action->setText(i18nc("@action:inmenu", "Watch: %1", squeezed)); 0224 action->setWhatsThis(i18nc("@info:whatsthis", 0225 "<b>Watch expression</b>" 0226 "<p>Adds the expression under the cursor to the Variables/Watch list.</p>")); 0227 connect(action, &QAction::triggered, this, [this, contextIdent](){ 0228 emit evaluateExpression(contextIdent); 0229 }); 0230 menuExt.addAction(ContextMenuExtension::DebugGroup, action); 0231 } 0232 0233 return menuExt; 0234 } 0235 0236 void MIDebuggerPlugin::slotExamineCore() 0237 { 0238 showStatusMessage(i18n("Choose a core file to examine..."), 1000); 0239 0240 if (core()->debugController()->currentSession() != nullptr) { 0241 KMessageBox::ButtonCode answer = KMessageBox::warningTwoActions( 0242 core()->uiController()->activeMainWindow(), 0243 i18n("A program is already being debugged. Do you want to abort the " 0244 "currently running debug session and continue?"), 0245 {}, KGuiItem(i18nc("@action:button", "Abort Current Session"), QStringLiteral("application-exit")), 0246 KStandardGuiItem::cancel()); 0247 if (answer == KMessageBox::SecondaryAction) 0248 return; 0249 } 0250 auto *job = new MIExamineCoreJob(this, core()->runController()); 0251 core()->runController()->registerJob(job); 0252 // job->start() is called in registerJob 0253 } 0254 0255 #if HAVE_KSYSGUARD 0256 void MIDebuggerPlugin::slotAttachProcess() 0257 { 0258 showStatusMessage(i18n("Choose a process to attach to..."), 1000); 0259 0260 if (core()->debugController()->currentSession() != nullptr) { 0261 KMessageBox::ButtonCode answer = KMessageBox::warningTwoActions( 0262 core()->uiController()->activeMainWindow(), 0263 i18n("A program is already being debugged. Do you want to abort the " 0264 "currently running debug session and continue?"), 0265 {}, KGuiItem(i18nc("@action:button", "Abort Current Session"), QStringLiteral("application-exit")), 0266 KStandardGuiItem::cancel()); 0267 if (answer == KMessageBox::SecondaryAction) 0268 return; 0269 } 0270 0271 QPointer<ProcessSelectionDialog> dlg = new ProcessSelectionDialog(core()->uiController()->activeMainWindow()); 0272 if (!dlg->exec() || !dlg->pidSelected()) { 0273 delete dlg; 0274 return; 0275 } 0276 0277 // TODO: move check into process selection dialog 0278 int pid = dlg->pidSelected(); 0279 delete dlg; 0280 if (QApplication::applicationPid() == pid) { 0281 const QString messageText = 0282 i18n("Not attaching to process %1: cannot attach the debugger to itself.", pid); 0283 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0284 ICore::self()->uiController()->postMessage(message); 0285 } 0286 else 0287 attachProcess(pid); 0288 } 0289 #endif 0290 0291 MIAttachProcessJob* MIDebuggerPlugin::attachProcess(int pid) 0292 { 0293 auto *job = new MIAttachProcessJob(this, pid, core()->runController()); 0294 core()->runController()->registerJob(job); 0295 // job->start() is called in registerJob 0296 0297 return job; 0298 } 0299 0300 QString MIDebuggerPlugin::statusName() const 0301 { 0302 return i18n("Debugger"); 0303 } 0304 0305 void MIDebuggerPlugin::showStatusMessage(const QString& msg, int timeout) 0306 { 0307 emit showMessage(this, msg, timeout); 0308 } 0309 0310 #include "midebuggerplugin.moc" 0311 #include "moc_midebuggerplugin.cpp"